服务器迁移踩坑记录

前言 国内这些云厂商服务器续费价格好贵呀,一个2核心2G的服务器,首年 99元/年 第二年续费就能涨到 200+元/年 。😭所以只能每年服务器快到期了就在买一台服务器,所有服务从旧服务器迁移到新服务器上。 这篇文章就记录一下自己在今年迁服务器的流程和踩的坑 1. 打包并迁移数据 服务器中文件结构如下 docker/ ├── hugo-blog/ │ ├── blog/ │ └── docker-compose.yaml ├── netbird/ │ ├── data/ │ └── docker-compose.yaml ├── nginx/ │ ├── data/ │ └── docker-compose.yaml ... └── restdesk-sercer/ ├── data/ └── docker-compose.yaml 因为所有服务的数据都在 docker/ 文件下,所以只需要将 docker/ 文件夹整个打包后传到新服务器上就行。 在打包 docker/ 前需要先停止所有服务 # 进入 docker/ 目录 # 逐个compose目录停止服务 for d in */; do echo "stop $d" cd "$d" docker compose down cd .. done 打包 docker/ 目录 ...

2026年8月26日

在 Mac 上使用 launchtcl 部署服务

1. 前言 最近在公司做了个内部提效的飞书机器人,公司有台公共的mac mini,之前一直是手动打包好代码上传到mac mini上用 uv run python main.py 运行,可以说是非常不优雅了。 以前都是在 Linux 上用 systemctl 部署服务,没在 mac 弄过,查了一下 mac 系统上有个 launchd 用于管理服务,这篇文档记录一下第一次在 mac 上用 launchd 部署服务 2. launchctl 与 systemctl 的区别 功能 systemctl launchctl 主要配置文件 .service .plist 启动服务 systemctl start [name] launchctl bootstrap gui/$(id -u) [path] 停止服务 systemctl stop [name] launchctl bootout gui/$(id -u) [path] 开机自启 systemctl enable [name] 只要 .plist 文件放到特定目录,默认加载 查看状态 systemctl status [name] launchctl list [label] 服务配置文件位置 macOS 使用 Property List (.plist) 文件来定义服务。根据你想让服务以什么权限运行,存放路径也不同: ...

2026年4月9日

使用 OpenClaw + Lume 搭建 MacOS 自动化环境

OpenClaw 最近很火,正好公司有台空闲的 Mac Mini,可以在上面捣鼓一下这个新东西。 1. 安装 Lume Lume 是 MacOS 上的原生虚拟化框架,让 OpenClaw 在虚拟环境下运行不会搞乱我们的系统。 1.1 安装 Lume 方式一:脚本安装 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/trycua/cua/main/libs/lume/scripts/install.sh)" 方式二:Homebrew 安装 brew tap trycua/lume brew install lume 验证安装 使用以下命令验证是否安装成功: lume --version 1.2 下载 MacOS 镜像 lume 安装成功后,需要获取 MacOS 镜像,使用下面指令将镜像下载到 Downloads 文件夹中: curl -L -o ~/Downloads/macos.ipsw "$(lume ipsw 2>/dev/null | tail -1)" 1.3 运行虚拟机 使用下面指令即可运行虚拟机 lume create openclaw --os macos --ipsw ~/Downloads/macos.ipsw 2. 安装 OpenClaw openclaw 中文文档 openclaw 的安装方式在其官方文档中已经很清楚了,我这里只列出几个我安装时遇到的问题 2.1 踩坑记录 2.1.1 对接飞书频道 安装飞书插件 ...

2026年2月3日

懒人脚本集

1. 文件夹内容变化对比脚本 这个脚本是我在研究淘宝上超低价格Steam兑换码的作用原理时写的。 ⚠️不要买盗版!不要买盗版!不要买盗版⚠️ 当你淘宝上购买低价兑换后,商家会让你下载一个他们的程序,然后在你电脑上运行,他们的程序会向steam客户端注入木马,从而实现虚假的兑换游戏。 ‼️这种方式会导致你的steam账号泄露‼️ file_ckecker.py import os import hashlib import json import argparse import sys from tqdm import tqdm # 导入进度条库 def calculate_file_hash(file_path, block_size=65536): """计算文件的 SHA-256 哈希值""" sha256 = hashlib.sha256() try: with open(file_path, 'rb') as f: for block in iter(lambda: f.read(block_size), b''): sha256.update(block) return sha256.hexdigest() except (PermissionError, FileNotFoundError): return None def get_all_files(folder_path): """预扫描文件夹,获取所有文件列表用于初始化进度条""" all_files = [] for root, _, files in os.walk(folder_path): for name in files: all_files.append(os.path.join(root, name)) return all_files def record_folder_state(folder_path, output_file): """记录文件夹状态""" if not os.path.isdir(folder_path): print(f"错误: 路径 '{folder_path}' 不是文件夹。") return print(f"正在预扫描目录结构...") file_list = get_all_files(folder_path) file_data = {} print(f"开始计算哈希值:") for full_path in tqdm(file_list, desc="进度", unit="file"): rel_path = os.path.relpath(full_path, folder_path) file_hash = calculate_file_hash(full_path) if file_hash: file_data[rel_path] = file_hash with open(output_file, 'w', encoding='utf-8') as f: json.dump(file_data, f, indent=4, ensure_ascii=False) print(f"\n完成!已记录 {len(file_data)} 个文件。") def compare_folder_state(current_folder, record_file): """对比差异""" if not os.path.exists(record_file): print(f"错误: 找不到记录文件 '{record_file}'。") return with open(record_file, 'r', encoding='utf-8') as f: old_data = json.load(f) print(f"正在预扫描当前目录...") file_list = get_all_files(current_folder) current_data = {} print(f"正在分析当前文件内容:") for full_path in tqdm(file_list, desc="对比中", unit="file"): rel_path = os.path.relpath(full_path, current_folder) file_hash = calculate_file_hash(full_path) if file_hash: current_data[rel_path] = file_hash old_files = set(old_data.keys()) new_files = set(current_data.keys()) added = sorted(list(new_files - old_files)) removed = sorted(list(old_files - new_files)) common = old_files & new_files changed = sorted([f for f in common if old_data[f] != current_data[f]]) print(f"\n{'='*20} 对比报告 {'='*20}") print(f"新增 [{len(added)}], 删除 [{len(removed)}], 变动 [{len(changed)}]") if added: print("\n新增文件:") for f in added: print(f" [+] {f}") if removed: print("\n删除文件:") for f in removed: print(f" [-] {f}") if changed: print("\n变动文件:") for f in changed: print(f" [*] {f}") if not any([added, removed, changed]): print("\n结果: 没有任何变化。") print(f"{'='*49}\n") def main(): parser = argparse.ArgumentParser(description="文件夹变更检测工具") subparsers = parser.add_subparsers(dest="command") save_parser = subparsers.add_parser("save", help="记录状态") save_parser.add_argument("-p", "--path", required=True) save_parser.add_argument("-o", "--out", default="snapshot.json") check_parser = subparsers.add_parser("check", help="对比差异") check_parser.add_argument("-p", "--path", required=True) check_parser.add_argument("-d", "--db", required=True) args = parser.parse_args() if args.command == "save": record_folder_state(args.path, args.out) elif args.command == "check": compare_folder_state(args.path, args.db) else: parser.print_help() if __name__ == "__main__": main() 效果 ...

2026年1月21日

使用Hugo搭建博客

这篇文章也是用来记录我自己搭建博客博客的经历 1. 项目 1.1 实现链路 下面是整个博客的链路,因为我要考虑服务器迁移的问题,为了方便迁移所以会使用容器化方案。 最终实现我写好一篇文章,在本地使用 git push命令即可实现服务器自动更新内容。 1.2 文件结构 hugo-blog/ ├── blog/ │ ├── source/ │ ├── repo.git/ └── docker-compose.yaml source:存放hugo原始数据以及hugo编译后的public文件夹 repo.git:Git仓库 2. 博客搭建 2.1 创建Git仓库 在服务器上创建Git裸仓库,用于接收推送的代码 # 创建 git 文件夹 mkdir -p blog/repo.git cd blog/repo.git # 初始化裸仓库 git init --bare 配置自动化钩子,当本地终端通过git push推送文章时,服务器自动触发这个脚本 vim hooks/post-receive #!/bin/bash # 定义路径 GIT_DIR=$HOME/docker/hugo-blog/blog/repo.git SRC_DIR=$HOME/docker/hugo-blog/blog/source # 1. 检出源码到临时目录 git --work-tree=$SRC_DIR --git-dir=$GIT_DIR checkout -f main echo "迁出分支成功" # 2. 使用 Docker 运行 Hugo 构建 # --rm 表示构建完后自动删除容器 docker run --rm \ -u $(id -u):$(id -g) \ -v $SRC_DIR:/src \ hugomods/hugo:latest build echo "Hugo构建成功" 给脚本执行权限chmod +x hooks/post-receive ...

2026年1月13日

RPA Agent 探索

这次尝试的宗旨是 AI可替换,不和特定AI绑定 不考虑Token消耗 设计出通用的 AI 驱动框架,并不局限于 RPA 这个方面 1. 浅尝 最开始的想法是实现两个Agent分别是·、任务规划Agent和任务执行Agent 任务规划Agent根据用户需求列出步骤清单,任务执行Agent负责循环步骤清单执行步骤 1.1 任务规划Agent 我在实现完任务规划Agent后就发现,任务规划和执行分离不是一个好设计,规划的任务准确性会越来越低,所以这个方案我就没有继续实现执行Agent rask_planner_agent.py import base64 from langchain_deepseek import ChatDeepSeek from langchain_openai import ChatOpenAI from langchain.agents import create_agent from .output_schema import TaskSchema class TaskPlannerAgent: def __init__(self): self.llm = ChatOpenAI( openai_api_key="sk-or-v1-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, openai_api_base="https://openrouter.ai/api/v1", model_name="google/gemini-3-flash-preview" ) # System prompt self.system_prompt = """ 你是一个专业的任务规划助手。用户将提供任务描述或图片,你需要结合这些信息将其拆解为结构化步骤。 """ self.agent = create_agent( model=self.llm, system_prompt=self.system_prompt, response_format=TaskSchema ) def plan(self, instruction: str, image_path: str = None) -> TaskSchema: content = [{"type": "text", "text": instruction}] if image_path: image_data = self._encode_image(image_path) content.append({ "type": "image_url", "image_url": { "url": f"data:image/jpeg;base64,{image_data}" } }) result = self.agent.invoke( {"messages": [{"role": "user", "content": content}]} ) return result["structured_response"] def _encode_image(self, image_path: str): """将本地图片转为 base64编码""" with open(image_path, 'rb') as image_file: return base64.b64encode(image_file.read()).decode('utf-8') def main(args): agent = TaskPlannerAgent() for step in agent.plan("搜索熊猫,并进入搜索结果的第一项内容", r"C:\Users\Levit\Desktop\1.jpg").steps: print(step) output_schema.py 规范Agent输出格式 ...

2025年12月23日