--- name: gitea-code-sync description: 通过 Gitea 仓库进行代码同步的工作流 — agent 在云端写代码推送到仓库,用户在本地拉取 version: 1.0.0 author: Hermes Agent license: MIT metadata: hermes: tags: [git, gitea, workflow, deployment] platforms: [qqbot] --- # Gitea 代码同步工作流 ## 背景 Agent 部署在云端服务器,用户在本机。简单项目完全交给 agent 开发,agent 写完后 push 到 Gitea 仓库,用户在本地 pull。 ## Gitea 凭证 - **平台**: https://gitea.ephron.ren - **用户**: Elaina - **Token**: 存储在 `~/.netrc` - **配置**: `git config --global credential.helper store` ## 工作流程 ### Agent 端(云端) 1. 收到项目开发任务后,在 `/home/ubuntu/projects/` 目录下创建项目 2. 开发完成后,初始化 git(如果还没有): ```bash cd /home/ubuntu/projects/ git init git remote add origin https://gitea.ephron.ren/Elaina/.git git add . git commit -m "Initial commit" git push -u origin main ``` ### 通过 Gitea API 创建仓库 ```bash # Token 从 ~/.netrc 读取 TOKEN=$(grep gitea.ephron.ren -A1 ~/.netrc | grep password | awk '{print $2}') curl -s -u "token:$TOKEN" "https://gitea.ephron.ren/api/v1/user/repos" \ -X POST -H "Content-Type: application/json" \ -d '{"name": "repo_name", "private": true, "description": "项目描述"}' ``` ### 用户端(本地) ```bash # 克隆仓库 git clone https://gitea.ephron.ren/Elaina/.git # 后续更新 git pull origin main ``` ## 交付物类型判断 用户说"推送到仓库"时,先判断交付物类型,不要默认推源代码: | 用户用词 | 期望交付物 | 推送内容 | |---------|-----------|---------| | 修复方案、分析报告、方案文档 | Markdown 文档(问题描述 + 根因 + diff + 验证) | `.md` 文件到新仓库 | | 代码、实现、开发 | 源代码 | 项目代码到仓库 | | 测试结果、测试报告 | 测试报告文档 | `.md` 文件到仓库 | **教训**:用户说"修复方案推送到新仓库",意思是推送一份修复方案**文档**(分析+方案),不是把修改后的源代码推过去。 ## 仓库隐私性规则(用户偏好) 创建仓库时根据内容隐私性判断: - **私有库**: 内部测试、敏感数据、个人项目、QA 测试 - **公开库**: 开源项目、公开文档、展示性内容 **私有库添加协作者**: 创建后将用户的 Gitea 用户名添加为 write 权限协作者。Gitea 用户名需通过 API 确认(如 `curl -u "token:TOKEN" "https://gitea.ephron.ren/api/v1/repos/{owner}/{repo}/collaborators"` 列出现有协作者可作为参考)。已确认有效的 Gitea 用户名: `ephron_ren`。 ## Gitea API 操作 ### 盘点所有仓库(含私有) ```bash TOKEN=$(awk '/gitea.ephron.ren/{found=1} found && /password/{print $2; exit}' ~/.netrc) for user in Elaina ephron_ren; do echo "=== $user ===" curl -s -H "Authorization: token $TOKEN" \ "https://gitea.ephron.ren/api/v1/users/$user/repos?limit=100" \ | jq -r '.[] | "\(.full_name) | \(.private) | \(.updated_at[:10])"' done ``` ⚠️ `/api/v1/repos/search` 不返回私有仓库,盘点必须用 `/api/v1/user/repos` 或 `/api/v1/users/{username}/repos`。详见 `references/repo-inventory.md`。 ### 修改仓库可见性 ```bash # 改为私有 curl -s -X PATCH -H "Authorization: token $TOKEN" \ -H "Content-Type: application/json" \ -d '{"private": true}' \ "https://gitea.ephron.ren/api/v1/repos/{owner}/{repo}" # 改为公开 curl -s -X PATCH -H "Authorization: token $TOKEN" \ -H "Content-Type: application/json" \ -d '{"private": false}' \ "https://gitea.ephron.ren/api/v1/repos/{owner}/{repo}" ``` ### 添加协作者 ```bash curl -s -X PUT -H "Authorization: token $TOKEN" \ -H "Content-Type: application/json" \ -d '{"permission": "write"}' \ "https://gitea.ephron.ren/api/v1/repos/{owner}/{repo}/collaborators/{username}" ``` ### 常见错误 - `remote: Push to create is not enabled for users.` + HTTP 403: 远程仓库不存在,需要先通过 API 创建。参见上方"通过 Gitea API 创建仓库" - `user should be an owner or a collaborator with admin write`: 当前 token 用户不是仓库 owner,需要 owner 操作或 fork 到自己账号下 - `user does not exist`: 用户名拼写错误,Gitea 用户名区分大小写 - `User permission denied for writing`: 当前 token 对目标仓库没有写权限(例如 `ephron_ren/ephron.ren` 对 Elaina 是只读的) ### Push 被拒后的排错流程 当 `git push` 返回 403 时,先确认远程仓库是否存在: ```bash # 检查仓库是否存在 curl -s -o /dev/null -w "%{http_code}" \ -H "Authorization: token $TOKEN" \ "https://gitea.ephron.ren/api/v1/repos/{owner}/{repo}" # 404 = 仓库不存在 → 需要创建 # 200 = 仓库存在 → 权限问题 ``` 如果仓库不存在,用 API 创建后再 push。如果仓库存在但 push 被拒,检查协作者权限。 ### 推送权限被拒时的解决方案 当 `git push` 返回 `User permission denied for writing` 时: **方案 A:创建新仓库(推荐)** ```bash # 在 Elaina 账号下创建新仓库 TOKEN=$(grep gitea.ephron.ren -A1 ~/.netrc | grep password | awk '{print $2}') curl -s -X POST "https://gitea.ephron.ren/api/v1/user/repos" \ -H "Authorization: Token $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "", "description": "<描述>", "private": false, "auto_init": true, "default_branch": "main" }' # 然后推送到新仓库 cd /path/to/project git remote set-url origin https://gitea.ephron.ren/Elaina/.git # 如果远程有 auto_init 的 README,先拉取合并 git pull origin main --allow-unrelated-histories --no-rebase git push -u origin main ``` **方案 B:请仓库 owner 添加协作者权限** - 需要 `ephron_ren` 用户在 Gitea 上给 Elaina 添加 write 权限 - 适合需要长期协作的场景 ## 注意事项 - 项目目录放在 `/home/ubuntu/projects/` 下 - Token 有写权限,可以 push 也可以创建仓库 - 简单项目直接交给 agent 开发,无需用户介入代码层面 - **权限问题**: 只有仓库 owner 或 admin 权限协作者才能修改仓库设置和添加协作者。Agent 账号 (Elaina) 只能操作自己创建的仓库。 ## ⚠️ 破坏性操作必须先确认 **删除仓库、强制推送、覆盖分支等破坏性操作,必须先向用户确认,不能擅自执行。** 用户曾明确要求:"清理无用仓库,清理这种危险动作要先让我确认" 正确流程: 1. 列出待删除/修改的仓库或分支 2. 向用户展示清单并请求确认 3. 用户确认后再执行 ❌ 错误:直接执行 `curl -X DELETE ...` 删除仓库 ✅ 正确:先问"发现旧仓库 X,需要删除吗?请确认" ## Feature Branch 工作流 当开发复杂功能时,使用 feature branch 避免影响 main 分支: ### 创建 feature branch ```bash cd /home/ubuntu/projects/ git checkout -b feature/ ``` ### 开发并提交 ```bash # 开发完成后 git add . git commit -m "feat: " git push origin feature/ ``` ### 合并到 main ```bash # 方法1: 直接合并(简单项目) git checkout main git merge feature/ git push origin main # 方法2: 通过 Gitea API 创建 Pull Request(推荐) TOKEN=$(grep gitea.ephron.ren -A1 ~/.netrc | grep password | awk '{print $2}') curl -s -X POST "https://gitea.ephron.ren/api/v1/repos/{owner}/{repo}/pulls" \ -H "Authorization: token $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "title": "feat: ", "head": "feature/", "base": "main", "body": "## 变更说明\n\n- 变更1\n- 变更2" }' ``` ### 清理 feature branch ```bash # 合并后删除本地分支 git branch -d feature/ # 删除远程分支 git push origin --delete feature/ ``` ## 常见模式 详细的 API 参考和脱敏模式见 `references/gitea-api.md`、`references/redaction-patterns.md`、`references/repo-inventory.md`(仓库盘点与枚举模式)。 ### 克隆已存在的仓库 ```bash # 方法1: 目录已存在但为空 git clone https://gitea.ephron.ren/Elaina/.git /home/ubuntu/projects/ # 会报错 "directory not empty" → 手动处理 # 方法2: 目录已存在,直接进去操作 cd /home/ubuntu/projects/ git pull origin master # 确保最新 # 然后正常 add/commit/push ``` ### 增量提交(每个模块完成后) ```bash cd /home/ubuntu/projects/ git add git commit -m "模块N测试完成: X用例通过, 发现Y个问题" git push origin master ``` ### 推送 Hermes 核心文件到私有库 ```bash # 创建仓库 curl -s -u "token:TOKEN" "https://gitea.ephron.ren/api/v1/user/repos" \ -X POST -H "Content-Type: application/json" \ -d '{"name":"hermes-core","private":true,"description":"Hermes Agent 核心配置"}' # 打包核心文件(包含 SOUL.md、config.yaml、memories、scripts) cd /home/ubuntu/projects/hermes-core git init # ... add and commit ... git push -u origin master ``` ### Hermes 核心文件备份(完整版) 用于服务器数据完全丢失后的完整恢复。备份所有核心配置文件,敏感信息脱敏存储。 **需要备份的核心文件清单:** | 文件 | 说明 | 敏感度 | |------|------|--------| | `SOUL.md` | 人格定义 | 低 | | `memories/MEMORY.md` | 持久化记忆 | 低 | | `memories/USER.md` | 用户偏好 | 低 | | `config.yaml` | 主配置 | 中 | | `.env` | 环境变量(含 API Key)| 高 | | `auth.json` | 凭证池 | 高 | | `providers/*.json` | 模型提供商配置 | 低 | | `scripts/mimo_*.py` | 能力脚本 | 低 | | `channel_directory.json` | 渠道配置 | 低 | | `gateway_state.json` | 网关运行时状态 | 低 | | `models_dev_cache.json` | 模型缓存信息 | 低 | **脱敏方案(备份前执行):** ❌ 不要用纯 regex 字符串替换,会漏字段、会弄坏 JSON 格式。 ✅ 正确做法: ```python import re, json # 1. .env — 用 Python 逐行处理 with open('.env', 'r') as f: content = f.read() lines = [] for line in content.split('\n'): stripped = line.lstrip() if stripped.startswith('#'): lines.append(line) continue m = re.match(r'^([A-Z_]+)=(.+)$', line) if m and any(s in m.group(1) for s in ['API_KEY', 'SECRET', 'TOKEN', 'PASSWORD']): lines.append(f"{m.group(1)}=***") else: lines.append(line) with open('.env', 'w') as f: f.write('\n'.join(lines)) # 2. auth.json — 用 json 模块序列化,保持格式正确 with open('auth.json', 'r') as f: auth = json.load(f) for provider, creds in auth['credential_pool'].items(): for c in creds: c['access_token'] = '***' with open('auth.json', 'w') as f: json.dump(auth, f, indent=2, ensure_ascii=False) ``` ⚠️ 容易遗漏的字段(必须覆盖): - `WEIXIN_TOKEN`、`WEIXIN_ACCOUNT_ID` - `WEIXIN_ALLOWED_USERS`、`WEIXIN_HOME_CHANNEL`(含用户 openid) - `QQ_CLIENT_SECRET` - `access_token` 中 `tp-` / `sk-` 前缀的完整 token **验证脱敏是否干净:** ```bash grep -v "^#" .env | grep -E "tp-[a-zA-Z0-9]{20,}|sk-[a-zA-Z0-9]{20,}|bq6New[A-Za-z0-9]+|[a-f0-9]{30,}|o9cq" && echo "有泄露" || echo "干净" ``` **恢复时需要手动补充的字段(写入 RESTORE.md):** - `.env`: `XIAOMI_API_KEY`、`MINIMAX_CODING_API_KEY`、`QQ_CLIENT_SECRET`、`WEIXIN_TOKEN`、`WEIXIN_ACCOUNT_ID`、`WEIXIN_ALLOWED_USERS`、`WEIXIN_HOME_CHANNEL` - `auth.json`: `credential_pool` 中各 provider 的 `access_token` - `~/.netrc`: Gitea 访问令牌(machine/login/password) **RESTORE.md 模板:** ```markdown # 恢复说明 仓库中包含脱敏后的配置文件。恢复时需要手动补充以下敏感信息: ## .env | 字段 | 说明 | 获取方式 | |------|------|----------| | `XIAOMI_API_KEY` | Xiaomi MiMo API Key | https://platform.xiaomimimo.com | | `MINIMAX_CODING_API_KEY` | MiniMax 编码 API Key | https://api.minimaxi.com | | `QQ_CLIENT_SECRET` | QQ 机器人客户端密钥 | https://connect.qq.com | | `WEIXIN_TOKEN` | 微信机器人 Token | 微信开放平台 | | `WEIXIN_ACCOUNT_ID` | 微信机器人账号 ID | 微信开放平台 | | `WEIXIN_ALLOWED_USERS` | 微信允许的用户列表(openid) | - | | `WEIXIN_HOME_CHANNEL` | 微信主页频道 ID | - | ## auth.json `credential_pool` 中各 provider 的 `access_token` 字段需填入真实 API Key。 ## .netrc `~/.netrc` 包含 Gitea 访问令牌。恢复后重新配置: ``` echo 'machine gitea.ephron.ren login password ' > ~/.netrc chmod 600 ~/.netrc ``` ``` **完整备份流程:** ```bash TOKEN=$(grep gitea.ephron.ren -A1 ~/.netrc | grep password | awk '{print $2}') # 1. 创建仓库 curl -s -u "token:$TOKEN" "https://gitea.ephron.ren/api/v1/user/repos" \ -X POST -H "Content-Type: application/json" \ -d '{"name":"hermes-core","private":true,"description":"Hermes Agent 核心配置"}' # 2. 打包并脱敏 mkdir -p /home/ubuntu/projects/hermes-core cd /home/ubuntu/projects/hermes-core git init cp ~/.hermes/SOUL.md . cp ~/.hermes/config.yaml . cp ~/.hermes/memories/MEMORY.md . cp ~/.hermes/memories/USER.md . cp ~/.hermes/.env . && python3 redact_env.py .env cp ~/.hermes/auth.json . && python3 redact_auth.py auth.json cp -r ~/.hermes/providers . cp -r ~/.hermes/scripts . cp ~/.hermes/channel_directory.json . cp ~/.hermes/gateway_state.json . # 3. 添加 RESTORE.md # ... 编写恢复说明 ... # 4. 推送 git add -A && git commit -m "Backup $(date +%Y-%m-%d)" && git push -u origin master ``` ### QA报告推送到仓库(推荐工作流) ```bash # 每完成一个模块就推送一次,不用等全部完成 git add test-results-v3.md git commit -m "模块X测试完成: N/M用例" git push origin master ```