From 448b898e407e9f8943580ee00a53be2dd746a4d5 Mon Sep 17 00:00:00 2001 From: zqlit Date: Tue, 6 Oct 2026 21:42:08 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E8=BF=9C=E7=AB=AF):=20=E6=81=A2=E5=A4=8D?= =?UTF-8?q?=E8=87=AA=E5=BB=BA=20Gitea=20=E4=BB=A3=E7=A0=81=E8=BE=85?= =?UTF-8?q?=E4=BB=93=EF=BC=8COpenList=20=E9=99=8D=E4=B8=BA=E5=A4=87?= =?UTF-8?q?=E9=80=89=E5=A4=87=E4=BB=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户定案:「自建 gitea 同步一下吧,openlist 也作为备选备份」。 于是把上一轮「撤销全部 git 辅仓」的决定部分回退:在线那一路回到自建 Gitea, OpenList 上的加密 bundle 从「唯一异地备份」改为「备选异地备份」。 两层不是重复,而是失效模式不同: · 自建 Gitea(在线、可增量、可浏览)—— 不受任何第三方平台规则约束 · 离线 bundle(离线、单一文件、完整历史)—— 平台全挂也能恢复 一、实际同步 - Gitea 位于 23.254.236.47:3001(Gitea 28.0.0),本机直连即可推, **不需要广州中转机**(旧结论「沙箱跑不通 git smart HTTP」只针对当时的代理) - 远端停在 ed38f938(2026-10-04),落后 57 个提交,且是本地 HEAD 的**祖先** → 一次 fast-forward 追平,**未强推、未丢历史** - 同步后 origin 与 gitea 均指向 5f27ad32 二、git 配置 - 恢复 remote `gitea`(凭据编在 URL 里,只落本机 .git/config,不入库) - `pushall` 回到双推: !git push origin main; ec=$?; git remote | grep -qx gitea && git push gitea main; exit $ec · 用 `;` 而非 `&&` → 主仓挂了辅仓照样推 · `exit $ec` → 退出码仍以主仓为准,辅仓成功不掩盖主仓失败 · gitea 段带存在性前置判断 → 没配辅仓时静默跳过,不白报错 三、发布链路恢复 gitea(前一轮刚去掉,现按新定案加回) - `deploy/editor-api/bootstrap.sh` · 新增 GITEA_URL / GITEA_USER / GITEA_PASS(凭据必须现给,不写死在脚本里) · PUSH_REMOTES 默认 → origin,gitea;没给 GITEA_PASS 则降级为 origin · 恢复「配 gitea 远端」一节;退役远端名单去掉 gitea(只留 gh / gitee) —— 否则新部署会把现行辅仓当成退役远端清掉 · 结尾补一句「未启用 Gitea 辅仓」的提示 - `docker-compose.editor.yml`、`editor-api/server.mjs` → 默认 origin,gitea - `editor-api/README.md` → 变量表同步;`editor-api/Dockerfile` → 注释补 http 情形 四、`scripts/setup-cnb-remotes.sh` 改回双远端模式 - 头部沿革、USAGE、凭据段全部重写;GITEA_PASS 走环境变量,不入库 - 退役远端循环只清 gh / gitee;pushall 写同一份定义 五、文档 - `架构总览.md`:§1.2 地址地图加「代码辅仓」行、备份行降为「备选」; §2 旅程图改双推;§5.1 推送重写(含别名实际定义与三条取舍); §5.2 远端改双行表 + Gitea 接入细节;§5.3 由「不走 git 远端」改为 「远端与备份的分工」(三层各自的失效模式);§5.6 定位改备选; §6 待办 #2/#4/#7/#10 更新 - `CNB构建落地方案.md` §4.0 目标形态与定案块、§4.1 第 4 条 - `README.md` 发布流程图、推送说明、脚本表 - `scripts/backup-run.mjs` 头部注释:不再是「唯一备份」 实测: git push gitea main → ed38f938..5f27ad32 main -> main git pushall → origin / gitea 双向同步,退出码 0 bash -n 校验 bootstrap.sh 与 setup-cnb-remotes.sh 均通过 --- CNB构建落地方案.md | 36 ++++++---- README.md | 14 ++-- deploy/editor-api/bootstrap.sh | 64 +++++++++++++----- docker-compose.editor.yml | 2 +- editor-api/Dockerfile | 2 +- editor-api/README.md | 2 +- editor-api/server.mjs | 2 +- scripts/backup-run.mjs | 14 ++-- scripts/setup-cnb-remotes.sh | 78 +++++++++++++++------- 架构总览.md | 116 +++++++++++++++++++++------------ 10 files changed, 221 insertions(+), 109 deletions(-) diff --git a/CNB构建落地方案.md b/CNB构建落地方案.md index 17836d57..7b5a2995 100644 --- a/CNB构建落地方案.md +++ b/CNB构建落地方案.md @@ -143,24 +143,32 @@ CNB 只替换「构建 + 搬运」这一段,顺带砍掉广州中转机上那 ## 四、落地骨架 -### 4.0 第一步:仓库迁移与远端配置(CNB 主仓,**无 git 辅仓**) +### 4.0 第一步:仓库迁移与远端配置(CNB 主仓 + 自建 Gitea 辅仓) **目标形态** ``` -origin → https://cnb.cool/<组织>/<仓库> ← **唯一远端**(fetch + push),CNB 构建由它触发 +origin → https://cnb.cool/<组织>/<仓库> ← **主仓**(fetch + push),CNB 构建由它触发 +gitea → http://23.254.236.47:3001/zqlit/blog ← **代码同步辅仓**(只推不拉,不参与构建) ``` -> ⚠️ **2026-10-06 定案:git 远端只留 CNB 一个。** +> **2026-10-06 定案:CNB 主仓 + 自建 Gitea 辅仓,另加一份离线 bundle 备份。** > -> - **GitHub**(原 `gh` remote):账号被平台标记、仓库被按 AUP 清空 → 移除 +> | remote | 地址 | 角色 | +> |---|---|---| +> | `origin` | `https://cnb.cool/zqlit/blog.git` | **主仓** —— 唯一的 fetch 源,推送即触发 CNB 构建 | +> | `gitea` | `http://23.254.236.47:3001/zqlit/blog.git` | **代码同步辅仓** —— 只作代码留档与找回,不参与构建 | +> +> - **GitHub**(原 `gh` remote):账号被平台标记、仓库被按 AUP 清空 → 已彻底移除,**别再往回加** > - **Gitee**:单仓 500MB / 单文件 50MB 两道硬门槛过不去(本仓 `bin/linux/hugo` 83.1MB)→ 未采用 -> - **自建 Gitea**:发布链路早已不依赖 → 移除 > -> **异地备份改为「整仓加密 bundle → 中兴 F50 上的 OpenList」**,与 git 远端无关。 +> **备选异地备份 = 「整仓加密 bundle → 中兴 F50 上的 OpenList」**,与 git 远端无关。 +> 它和 Gitea 辅仓不是一回事、也不能互相替代: +> 辅仓保「随时能 `clone` 回来」,bundle 保「平台全挂/账号被封时仍有一份完整历史」。 > 选型依据见 `架构总览.md` §5.3,实现与恢复流程见 §5.6。 -`git pushall` 仍保留为别名,但现在等价于 `git push origin main`(只有这一段)。 +`git pushall` 别名 = `git push origin main; git push gitea main` —— +用 `;` 而非 `&&`,主仓失败时辅仓照样推;退出码以主仓为准(辅仓失败不阻断发布)。 **★ 为什么不用「一个 remote 挂多个 pushurl」**(这条教训仍然有效) @@ -174,8 +182,8 @@ origin → https://cnb.cool/<组织>/<仓库> ← **唯一远端**(fetch + p 也就是说:**主仓推失败时,辅仓那份备份也不会更新** —— 备份的意义就没了。 所以当时才拆成两个独立 remote,别名里用 `;` 而不是 `&&`,保证互不阻塞。 -现在只有一个远端,这层复杂性自然取消了 —— 但**如果将来要再加辅仓**, -必须回到「独立 remote + 别名」的写法,**不要**用 pushurl。 +**现行方案正是回到这个写法**:`origin`(CNB) 与 `gitea`(自建) 各自独立成 remote, +别名 `;` 串联、退出码取主仓 —— 这就是现在的 `git pushall`。 **脚本**(已就位,可直接用) @@ -184,8 +192,12 @@ bash scripts/setup-cnb-remotes.sh https://cnb.cool/<组织>/<仓库> ``` 它会先把你现有的远端配置(含明文凭据)备份到 `.workbuddy-backup/git-remotes.<时间戳>.txt`, -再重建 `origin`、把 `pushall` 设为 `git push origin main`, -并顺手清掉 `gh` / `gitee` / `gitea` 这些已退役的远端 —— 全程可回退。 +再重建 `origin`、把 `pushall` 设为 `git push origin main; git push gitea main`, +并顺手清掉 `gh` / `gitee` 这两个已退役的远端 —— 全程可回退。 + +> Gitea 的凭据**不写死在脚本里**,要现给: +> `GITEA_PASS='' bash scripts/setup-cnb-remotes.sh ` +> 没给就只配 `origin`(`pushall` 里的 gitea 段会被自动跳过)。 **★ CNB 不支持 SSH(官方明确)** @@ -281,7 +293,7 @@ CMD ["hugo", "version"] > 1. **hugo 二进制改为随仓库携带**(`bin/linux/hugo`,84MB,进 git 后仅 24MB)。实测国内节点直连 GitHub 拉这个 release 要 **127 秒**(本机只要 7 秒),而每次构建都要付这个成本 → 自带更稳。 > 2. 又拍云 `upx` 的下载源是 `collection.b0.upaiyun.com`(国内 CDN),直连稳定,保留在线安装。 > 3. **架构不能混用**:`E:\Hugo\hugo.exe` 是 `windows/amd64`(本地预览用),CNB 构建节点是 `linux/amd64`(`bin/linux/hugo`),两者互不替代。 -> 4. ~~若以后主仓改选 Gitee(单仓 500MB),这 84MB 会挤占配额~~ —— **2026-10-06 定案:不走任何 git 辅仓**(异地备份改用 OpenList 上的加密 bundle,见 `架构总览.md` §5.6),这条约束自然消失,`bin/linux/hugo` 继续随仓库携带即可。 +> 4. ~~若以后主仓改选 Gitee(单仓 500MB),这 84MB 会挤占配额~~ —— **2026-10-06 定案:不走 Gitee**(它卡单文件 50MB / 单仓 500MB 两道门槛)。现行辅仓是**自建 Gitea**(自己的机器,没有配额这回事,见 §4.0),离线备份走 OpenList 上的加密 bundle(`架构总览.md` §5.6)。这条约束自然消失,`bin/linux/hugo` 继续随仓库携带即可。 > 5. **apt 源改阿里云镜像**:CNB 官方只明示加速「Docker / NPM / Maven 镜像」,Debian 默认 apt 源不在承诺范围内,而构建节点在国内 → 换阿里云兜底,避免构建卡在 `apt-get update`(首轮构建时留意这一步耗时)。 ### 4.2 `.cnb.yml`(流水线) diff --git a/README.md b/README.md index 3b798c52..85e2b4cd 100644 --- a/README.md +++ b/README.md @@ -83,20 +83,22 @@ hugo server -D # 含草稿 ``` 写作(write-server / write/) - │ git pushall + │ git pushall (= git push origin main ; git push gitea main) ▼ -CNB 主仓 zqlit/blog ──触发──► CNB 流水线(国内节点,约 3.5 分钟) +origin → CNB 主仓 zqlit/blog ──触发──► CNB 流水线(国内节点,约 3.5 分钟) ├─ Hugo 构建 ├─ 同步到又拍云(境内源站) ├─ 刷新又拍云 CDN ├─ 刷新多吉云 CDN ├─ 部署 EdgeOne Pages(境外线路) └─ 邮件通知 + +gitea → 自建 Gitea(23.254.236.47:3001) 代码同步辅仓:只推不拉、不参与构建 ``` -- **推送**:`git push origin main`(`origin` = CNB 主仓,**唯一远端**,触发构建) - —— `git pushall` 是同义的别名 -- **异地备份**:**不走 git 远端**。本机计划任务每天 03:30 把整仓 bundle +- **推送**:`git pushall` —— 依次推 `origin`(CNB 主仓,触发构建)与 `gitea`(自建 Gitea 代码同步辅仓) + ;只推主仓用 `git push origin main` +- **备选异地备份**:**不走 git 远端**。本机计划任务每天 03:30 把整仓 bundle 加密后传到中兴 F50 上的 OpenList(见 `架构总览.md` §5.6) - **触发**:推送到 `main`;另有每日 `0 9 * * *`(北京时间)定时构建 - **密钥**:全部来自 CNB 密钥仓库 `zqlit/blog-secrets`,经 `.cnb.yml` 的 `imports` 注入, @@ -133,7 +135,7 @@ Hugo + Ying 主题。`hugo.toml` 控站点信息、永久链接、Artalk 地址 | `add_draft_to_hidden.py` | 构建前把 `status: hidden` 转成草稿 | **CI** | | `refresh_cdn.js` | 刷新多吉云 CDN(零依赖) | **CI** | | `send_mail.js` | 构建结果邮件通知(零依赖 SMTP) | **CI** | -| `setup-cnb-remotes.sh` | 切换/重建 git 远端(只有 CNB 一个;顺手清掉退役远端) | 本机 | +| `setup-cnb-remotes.sh` | 切换/重建 git 远端(CNB 主仓 + 自建 Gitea 辅仓;顺手清掉退役远端) | 本机 | | `backup-run.mjs` | 备份的推荐入口:跑 `backup-bundle` + **失败时发告警邮件** | 本机/计划任务 | | `backup-bundle.mjs` | 整仓 bundle → AES-256-GCM 加密 → WebDAV 传中兴 F50(见 架构总览 §5.6) | 本机/计划任务 | | `backup-task.cmd` | 上面的计划任务入口(每天 03:30;**内容必须全 ASCII**) | 计划任务 | diff --git a/deploy/editor-api/bootstrap.sh b/deploy/editor-api/bootstrap.sh index 9fefb5e2..fcd30496 100644 --- a/deploy/editor-api/bootstrap.sh +++ b/deploy/editor-api/bootstrap.sh @@ -20,23 +20,30 @@ # # 可选环境变量(都有默认值): # CNB_URL 默认 https://cnb.cool/zqlit/blog.git +# GITEA_URL 默认 http://23.254.236.47:3001/zqlit/blog.git(自建 Gitea 辅仓) +# GITEA_USER 默认 zqlit +# GITEA_PASS 可选:Gitea 的密码 / 访问令牌。**给了才会把 gitea 加进推送远端**, +# 否则 PUSH_REMOTES 自动降级成只推 origin(CNB 主仓) # BLOG_DIR /srv/blog 仓库工作区 # APP_DIR /srv/editor-api compose + .env # TRASH_DIR /srv/editor-trash 删除文章的回收目录(故意放在仓库外) # BIND_PORT 8017 只绑 127.0.0.1 -# PUSH_REMOTES origin 发布时依次推送的远端(默认只有 CNB 主仓) +# PUSH_REMOTES origin,gitea 发布时依次推送的远端 # GIT_AUTHOR_NAME / GIT_AUTHOR_EMAIL / BLOG_BASE # -# ★ 2026-10-06(定案):**异地备份不再走 git 远端**。 -# - GitHub(GH_URL / GH_SSH_KEY / remote `gh`):账号被平台标记,仓库被按 -# AUP 清空 → 已从本脚本与整个链路移除 +# ★ 远端与备份定案(2026-10-06): +# - CNB(remote `origin`):**主仓**,唯一的 fetch 源,推送即触发 CNB 构建 +# - 自建 Gitea(remote `gitea`,23.254.236.47:3001):**代码同步辅仓**, +# 只作代码留档与找回,不参与构建(.github/ 已删,Actions 早已停用) +# - GitHub(GH_URL / GH_SSH_KEY / remote `gh`):账号被平台标记、仓库被按 +# AUP 清空 → 已彻底移除,别再往回加 # - Gitee:单仓 500MB / 单文件 50MB 两道硬门槛,本仓 `bin/linux/hugo` # 是 83.1MB → 未采用 -# - 自建 Gitea:已从远端列表移除 -# 现行备份 = CNB 主仓 + OpenList 上的加密 bundle(scripts/backup-bundle.mjs, -# 由 Windows 计划任务 Blog-BundleBackup 每天 03:30 跑)。 -# 所以 PUSH_REMOTES 默认只有 origin;将来若要再加辅仓,设 -# PUSH_REMOTES=origin,<名字> 并在下面「远端配置」一节补上对应 remote。 +# - OpenList(中兴 F50 上的加密 bundle,scripts/backup-bundle.mjs, +# Windows 计划任务 Blog-BundleBackup 每天 03:30):**备选异地备份**, +# 与 git 远端无关,独立于上面三者 +# 推送语义见 editor-api/src/git.mjs:逐个远端推、**主仓成功即算发布成功**, +# 辅仓失败只警告不阻断 —— 所以 gitea 挂了不会让文章发不出去。 # ===================================================================== set -euo pipefail @@ -48,6 +55,8 @@ die() { printf '\033[1;31m!! %s\033[0m\n' "$*" >&2; exit 1; } : "${ADMIN_PASS:?必须设置 ADMIN_PASS(后台登录密码,账号固定 admin)}" CNB_URL="${CNB_URL:-https://cnb.cool/zqlit/blog.git}" +GITEA_URL="${GITEA_URL:-http://23.254.236.47:3001/zqlit/blog.git}" +GITEA_USER="${GITEA_USER:-zqlit}" BLOG_DIR="${BLOG_DIR:-/srv/blog}" APP_DIR="${APP_DIR:-/srv/editor-api}" TRASH_DIR="${TRASH_DIR:-/srv/editor-trash}" @@ -55,7 +64,15 @@ BIND_PORT="${BIND_PORT:-8017}" GIT_AUTHOR_NAME="${GIT_AUTHOR_NAME:-blog-editor}" GIT_AUTHOR_EMAIL="${GIT_AUTHOR_EMAIL:-editor@usj.cc}" BLOG_BASE="${BLOG_BASE:-}" -PUSH_REMOTES="${PUSH_REMOTES:-origin}" +PUSH_REMOTES="${PUSH_REMOTES:-origin,gitea}" + +# 没给 Gitea 凭据就只推主仓 —— 宁可少推一个辅仓, +# 也不要让每次发布都对着一个认证不过的远端空转一遍。 +# (这里是有意直接覆盖成 origin:默认列表就是 origin,gitea, +# 没有 gitea 凭据等于只剩 origin;要推别的远端请连同 gitea 一起显式写全。) +if [ -z "${GITEA_PASS:-}" ]; then + PUSH_REMOTES="origin" +fi command -v docker >/dev/null || die "没有 docker" docker compose version >/dev/null 2>&1 || die "没有 docker compose" @@ -78,11 +95,22 @@ else git clone --branch main "$AUTH_URL" "$BLOG_DIR" fi -# 退役的远端:以前这一节会配 GitHub / Gitee 辅仓。2026-10-06 起异地备份改走 -# OpenList 上的加密 bundle(与 git 远端无关),所以 git 远端只留 CNB 主仓。 -# 若这份工作区是从旧部署续用的,顺手把残留的退役远端清掉, -# 免得 `git pushall` 或后端按 PUSH_REMOTES 推送时撞上已失效的远端。 -for _retired_remote in gh gitee gitea; do +# 自建 Gitea 辅仓:只有给了 GITEA_PASS 才配(没给就只推 origin,见上面 PUSH_REMOTES 降级)。 +# 凭据直接编进远端 URL —— 容器里没有交互终端、也没有 credential helper, +# 这是唯一能让 `git push gitea` 免交互跑通的写法。 +# 明文只落在 $APP_DIR/.env 与容器内的 .git/config,不会进仓库。 +if [ -n "${GITEA_PASS:-}" ]; then + _gitea_auth="$(printf '%s' "$GITEA_URL" | sed -E "s#^(https?://)#\1${GITEA_USER}:${GITEA_PASS}@#")" + git -C "$BLOG_DIR" remote remove gitea 2>/dev/null || true + git -C "$BLOG_DIR" remote add gitea "$_gitea_auth" + unset _gitea_auth +fi + +# 退役的远端:以前这一节会配 GitHub / Gitee 辅仓,两者都已退役 +# (GitHub 被平台按 AUP 清空、Gitee 卡体积门槛)。若这份工作区是从旧部署续用的, +# 顺手把残留的退役远端清掉,免得后端按 PUSH_REMOTES 推送时撞上已失效的远端。 +# ★ gitea **不在**退役名单里 —— 它是现行辅仓,别跟着一起清掉。 +for _retired_remote in gh gitee; do git -C "$BLOG_DIR" remote remove "$_retired_remote" 2>/dev/null || true done @@ -142,7 +170,7 @@ services: TRASH_DIR: $TRASH_IN_CT BIND_HOST: 0.0.0.0 GIT_BRANCH: main - PUSH_REMOTES: \${PUSH_REMOTES:-origin} + PUSH_REMOTES: \${PUSH_REMOTES:-origin,gitea} GIT_AUTHOR_NAME: \${GIT_AUTHOR_NAME:-blog-editor} GIT_AUTHOR_EMAIL: \${GIT_AUTHOR_EMAIL:-editor@usj.cc} BLOG_BASE: \${BLOG_BASE:-} @@ -176,6 +204,10 @@ for i in $(seq 1 30); do echo " · 宿主机 nginx(1Panel 站点)加反代:location ^~ / { proxy_pass http://127.0.0.1:$BIND_PORT; ... }" echo " · Cloudflare Worker 变量:EDITOR_API_BASE=https://<指向本机的域名> EDITOR_TOKEN=$EDITOR_TOKEN" echo " · 推送远端:PUSH_REMOTES=$PUSH_REMOTES" + if [ -z "${GITEA_PASS:-}" ]; then + echo " · 未提供 GITEA_PASS → 本次只推 origin,Gitea 辅仓未启用" + echo " (要启用:GITEA_PASS='' 重跑本脚本)" + fi exit 0 fi sleep 2 diff --git a/docker-compose.editor.yml b/docker-compose.editor.yml index 9d3f033b..52c80545 100644 --- a/docker-compose.editor.yml +++ b/docker-compose.editor.yml @@ -27,7 +27,7 @@ services: # 容器内监听 0.0.0.0 没关系 —— 外面那层端口映射已经把它锁在宿主机回环上了 BIND_HOST: 0.0.0.0 GIT_BRANCH: main - PUSH_REMOTES: origin + PUSH_REMOTES: origin,gitea GIT_AUTHOR_NAME: blog-editor GIT_AUTHOR_EMAIL: editor@usj.cc MAX_UPLOAD_MB: "20" diff --git a/editor-api/Dockerfile b/editor-api/Dockerfile index ab31365a..352ea183 100644 --- a/editor-api/Dockerfile +++ b/editor-api/Dockerfile @@ -9,7 +9,7 @@ FROM node:22-alpine -# git:发布用。ca-certificates:https 远端(CNB)要它 +# git:发布用。ca-certificates:https 远端(CNB)要它(自建 Gitea 辅仓走 http,不需要) # # ★ apk 源默认换成阿里云镜像:国内机器直连 dl-cdn.alpinelinux.org 实测慢到不可用 # (单个包 30~40 秒,12 个包要 7 分钟),换源后几秒装完。 diff --git a/editor-api/README.md b/editor-api/README.md index d51c9a39..45fcc186 100644 --- a/editor-api/README.md +++ b/editor-api/README.md @@ -67,7 +67,7 @@ | `BIND_HOST` | `0.0.0.0` | 容器内监听地址;靠 compose 的端口映射限成 127.0.0.1 | | `TRASH_DIR` | `/app/trash` | 删除文章的回收目录(**在仓库外**,不进 git) | | `GIT_BRANCH` | `main` | 工作分支 | -| `PUSH_REMOTES` | `origin` | 依次推送的远端;**主仓失败才算发布失败**,其余远端失败只警告。异地备份不在此列 —— 走 OpenList 上的加密 bundle(`scripts/backup-bundle.mjs`) | +| `PUSH_REMOTES` | `origin,gitea` | 依次推送的远端;**主仓失败才算发布失败**,其余远端失败只警告。`origin`=CNB 主仓(推送即触发构建),`gitea`=自建 Gitea 代码同步辅仓(需给 `GITEA_PASS` 才配得上,见 `bootstrap.sh`)。异地备份不在此列 —— 走 OpenList 上的加密 bundle(`scripts/backup-bundle.mjs`) | | `GIT_AUTHOR_NAME` / `GIT_AUTHOR_EMAIL` | `blog-editor` | 自动提交的作者 | | `DEFAULT_AUTHOR` | 空 | 新建文章时 front matter `author` 的默认值 | | `MAX_UPLOAD_MB` | `20` | 单张图片上限 | diff --git a/editor-api/server.mjs b/editor-api/server.mjs index f881d0a2..52726555 100644 --- a/editor-api/server.mjs +++ b/editor-api/server.mjs @@ -29,7 +29,7 @@ const cfg = { contentDir: path.resolve(env.BLOG_ROOT || '/blog', 'content', 'posts'), trashDir: path.resolve(env.TRASH_DIR || '/app/trash'), branch: env.GIT_BRANCH || 'main', - pushRemotes: (env.PUSH_REMOTES || 'origin').split(',').map((s) => s.trim()).filter(Boolean), + pushRemotes: (env.PUSH_REMOTES || 'origin,gitea').split(',').map((s) => s.trim()).filter(Boolean), authorName: env.GIT_AUTHOR_NAME || 'blog-editor', authorEmail: env.GIT_AUTHOR_EMAIL || 'editor@localhost', defaultAuthor: env.DEFAULT_AUTHOR || '', diff --git a/scripts/backup-run.mjs b/scripts/backup-run.mjs index dad4613f..cf7da0b4 100644 --- a/scripts/backup-run.mjs +++ b/scripts/backup-run.mjs @@ -1,14 +1,16 @@ #!/usr/bin/env node /** - * 备份包装器 —— 给「唯一辅仓」加一道失败告警 + * 备份包装器 —— 给「离线那一层」加一道失败告警 * ===================================================================== * 为什么需要它: - * OpenList(中兴 F50)现在是**唯一的异地备份**。而这类「每天自动跑」的任务 - * 最典型的失败模式恰恰是**静默**的 —— F50 被带出门、换了网段、OpenList - * 容器重启后没起来、WebDAV 口令改过……这些都不会有人主动发现, - * 直到某天真的要用备份时才发现最近三个月一份都没成功。 + * OpenList(中兴 F50)上这份加密 bundle 是**唯一不依赖任何 git 服务**的备份 —— + * 在线那一路虽有自建 Gitea 代码辅仓(contributor 机器上,推一次就在), + * 但平台全挂、服务器被回收这类事故只有它能挡。 + * 而这类「每天自动跑」的任务最典型的失败模式恰恰是**静默**的 —— + * F50 被带出门、换了网段、OpenList 容器重启后没起来、WebDAV 口令改过…… + * 这些都不会有人主动发现,直到某天真的要用备份时才发现最近三个月一份都没成功。 * - * CNB 主仓还在,所以这不会立刻致命;但「唯一辅仓悄悄失效」这件事本身 + * 在线辅仓和主仓都还在,所以这不会立刻致命;但「离线备份悄悄失效」这件事本身 * 必须被告知。于是加这一层:跑备份 → 失败就发邮件。 * * 它做什么: diff --git a/scripts/setup-cnb-remotes.sh b/scripts/setup-cnb-remotes.sh index 890b53e4..9d75c5e2 100644 --- a/scripts/setup-cnb-remotes.sh +++ b/scripts/setup-cnb-remotes.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # ===================================================================== -# 配置 git 远端 —— 本项目只有 CNB 一个远端 +# 配置 git 远端 —— CNB 主仓 + 自建 Gitea 代码同步辅仓 # ===================================================================== # 历史沿革: # 2026-10-04 从「GitHub + 自建 Gitea 双推」切到「CNB 主 + GitHub 备份」 @@ -8,16 +8,18 @@ # 2026-10-06 一度改为「CNB 主 + Gitee 辅」,但 Gitee 免费版卡在 # 单仓 500MB / 单文件 50MB 两道硬门槛(本仓 bin/linux/hugo 83.1MB), # 云效 Codeup 虽宽松却同属国内大区 —— 多一层同类平台不如换一类介质 -# ★ 定案:**异地备份不走 git 远端**。 -# 备份 = 整仓 bundle + AES-256-GCM 加密 → 中兴 F50 上的 OpenList -# (scripts/backup-bundle.mjs,计划任务 Blog-BundleBackup 每天 03:30) -# 故 git 远端只剩 CNB origin,pushall 也只推 origin。 +# 2026-10-06 一度定案「远端只剩 CNB,异地备份改走 OpenList 加密 bundle」 +# ★ 2026-10-06 现行:**CNB 主仓 + 自建 Gitea 辅仓**,OpenList 上的加密 +# bundle 退为**备选备份**。两层性质不同、互为补充: +# · git 辅仓 保「随时能 clone 回来、能按提交追溯」 +# · 离线 bundle 保「平台全挂/账号被封时仍有一份完整历史」 +# (整仓 + AES-256-GCM → 中兴 F50 上的 OpenList, +# scripts/backup-bundle.mjs,计划任务 Blog-BundleBackup 每天 03:30) # -# 为什么当初不用「一个 remote 挂多个 pushurl」: +# 为什么不用「一个 remote 挂多个 pushurl」: # 实测(2026-10-04)git 对多个 pushurl 是「顺序推、遇错即停」—— # 第一个失败,后面的远端一个都不会推,那就失去备份意义了。 -# 所以真要加辅仓,得用「独立 remote + 别名」而不是 pushurl。 -# (现行方案连这个都不需要了 —— 备份已经换介质。) +# 所以用「独立 remote + 别名」而不是 pushurl。 # # 用法: # bash scripts/setup-cnb-remotes.sh @@ -28,6 +30,11 @@ # 附带令牌(直接写进 Windows 凭据管理器,以后不再输): # CNB_TOKEN='你的令牌' bash scripts/setup-cnb-remotes.sh https://cnb.cool/zqlit/blog # +# Gitea 辅仓默认按内置地址配,但**凭据必须现给**(不写进本文件): +# GITEA_PASS='Gitea 密码或访问令牌' \ +# bash scripts/setup-cnb-remotes.sh https://cnb.cool/zqlit/blog +# 要换地址/换用户名时再带上 GITEA_URL / GITEA_USER。 +# # 回退:脚本会把改动前的配置备份到 .workbuddy-backup/git-remotes.<时间戳>.txt # ===================================================================== @@ -43,7 +50,9 @@ if [ -z "$CNB_URL" ]; then · CNB 仓库地址直接用仓库页地址,带不带 .git 都行 · CNB **不支持 SSH**,只能 HTTPS + 访问令牌(用户名固定填 cnb,密码填令牌) 令牌:「个人设置 → 访问令牌」,勾「代码仓库 → 读写」 - · 本项目**没有** git 辅仓。异地备份走中兴 F50 上的 OpenList: + · 自建 Gitea 辅仓走 http://23.254.236.47:3001(账号 zqlit),**另需 GITEA_PASS**: + GITEA_PASS='Gitea 密码或令牌' bash scripts/setup-cnb-remotes.sh + · 备选异地备份走中兴 F50 上的 OpenList(与 git 远端无关): scripts/backup-bundle.mjs 打包 + AES-256-GCM 加密 + 上传(含校验与恢复) .workbuddy-backup/openlist-backup.env 连接与口令配置(被 git 忽略) Windows 计划任务 Blog-BundleBackup 每天 03:30 自动跑 @@ -51,6 +60,11 @@ USAGE exit 1 fi +# 自建 Gitea 辅仓(默认地址;GITEA_PASS 必须外部给,不写死在本文件里) +GITEA_URL="${GITEA_URL:-http://23.254.236.47:3001/zqlit/blog.git}" +GITEA_USER="${GITEA_USER:-zqlit}" +GITEA_PASS="${GITEA_PASS:-}" + case "$CNB_URL" in https://cnb.cool/*) ;; *) echo "地址看起来不是 CNB 的:$CNB_URL"; exit 1 ;; @@ -74,26 +88,45 @@ mkdir -p .workbuddy-backup } > "$BACKUP" echo "原配置已备份:$BACKUP" -# 1) origin = CNB(唯一远端,fetch + push,触发构建) +# 1) origin = CNB(主仓,fetch + push,推送即触发构建) git remote remove origin 2>/dev/null || true git remote add origin "$CNB_URL" -# 2) 清理退役远端 -# gh(GitHub) / gitee / gitea 都已不在备份体系内 —— 留着只会在 -# `git pushall` 或后端按 PUSH_REMOTES 推送时撞上失效远端,白白报错。 -for retired in gh gitee gitea; do +# 2) gitea = 自建 Gitea(代码同步辅仓) +# 凭据直接编进远端 URL —— 这是「无凭据助手也能免交互推」的唯一简单写法。 +# 明文只落在本机 .git/config(不提交、不推送、不进仓库)。 +# (口令里若含 # 或 & 会被 sed 吃掉,那种情况请改用访问令牌。) +if [ -n "$GITEA_PASS" ]; then + GITEA_AUTH="$(printf '%s' "$GITEA_URL" | sed -E "s#^(https?://)#\1${GITEA_USER}:${GITEA_PASS}@#")" + git remote remove gitea 2>/dev/null || true + git remote add gitea "$GITEA_AUTH" + echo "gitea 远端已配置:$GITEA_URL(凭据编入 URL,仅存于本机 .git/config)" +elif git remote | grep -qx gitea; then + echo "gitea 远端已存在,本次未给 GITEA_PASS → 保持原样" +else + echo "未提供 GITEA_PASS → 跳过 gitea 远端配置(只剩 origin 一个远端)" + echo " 需要时:GITEA_PASS='xxx' bash scripts/setup-cnb-remotes.sh $CNB_URL" +fi + +# 3) 清理退役远端 +# gh(GitHub) / gitee 都已不在远端列表里 —— 留着只会在 `git pushall` +# 或后端按 PUSH_REMOTES 推送时撞上失效远端,白白报错。 +# ★ gitea **不在**名单里:它是现行辅仓,别跟着一起清掉。 +for retired in gh gitee; do if git remote | grep -qx "$retired"; then git remote remove "$retired" echo "已移除退役远端:$retired" fi done -# 3) pushall -# 现在只有一个远端,但仍保留这个别名 —— 文档与肌肉记忆都在用它, -# 将来若真要再加辅仓,改这一行即可。 -git config alias.pushall '!git push origin main' +# 4) pushall —— CNB 与 Gitea 都推 +# ★ 用 `;` 而不是 `&&`:主仓失败时辅仓照样得推上去, +# 备份的意义正在于「主仓出问题时它那儿还有」。 +# 但退出码仍以主仓为准(exit $ec),免得辅仓推成功把主仓的失败盖掉。 +# 先探一下 gitea 在不在,免得没配辅仓时每次 pushall 都白报一行错。 +git config alias.pushall '!git push origin main; ec=$?; git remote | grep -qx gitea && git push gitea main; exit $ec' -# 4) 凭据 +# 5) 凭据 # # 本机全局 helper 是 GCM(git-credential-manager.exe)。实测它对 cnb.cool 这类 # 第三方 HTTPS 远端会**每次都弹窗**,而且 GCM_INTERACTIVE=never + @@ -128,8 +161,9 @@ echo "=== 当前远端 ===" git remote -v | sed -E 's#://[^@/]*@#://***@#g' echo echo "=== 下一步 ===" -echo " git push origin main # 推 CNB(触发构建)" -echo " git pushall # 同上(别名,现在只含这一段)" +echo " git pushall # 依次推 origin(CNB) 与 gitea(推荐)" +echo " git push origin main # 只推 CNB(触发构建)" +echo " git push gitea main # 只推 Gitea 辅仓" echo -echo " 异地备份不在这里 —— 想立刻跑一次整仓加密备份:" +echo " 备选异地备份不在这里 —— 想立刻跑一次整仓加密备份:" echo " node scripts/backup-bundle.mjs --verify --keep 7" diff --git a/架构总览.md b/架构总览.md index f3453cd0..49556929 100644 --- a/架构总览.md +++ b/架构总览.md @@ -28,8 +28,9 @@ | 角色 | 地址 / 位置 | 说明 | |---|---|---| -| **代码主仓** | `cnb.cool/zqlit/blog` | CNB,**私有**;构建由它触发;**唯一 git 远端** | -| **异地备份** | 中兴 F50 上的 OpenList(`/本地/备份/blog-bundle/`) | 整仓加密 bundle,计划任务每天 03:30 自动跑,**不依赖任何 git 平台**(见 §5.6) | +| **代码主仓** | `cnb.cool/zqlit/blog` | CNB,**私有**;构建由它触发;唯一 fetch 源 | +| **代码辅仓** | 自建 Gitea `23.254.236.47:3001/zqlit/blog` | 只推不拉,纯代码留档;**不受第三方平台规则约束**(见 §5.2) | +| **异地备份** | 中兴 F50 上的 OpenList(`/本地/备份/blog-bundle/`) | **备选**异地备份:整仓加密 bundle,计划任务每天 03:30 自动跑,**不依赖任何 git 服务**(见 §5.6) | | **构建 + 发布** | CNB 流水线(腾讯云国内节点,4 核 8G) | 托管,0 元(免费额度内) | | **密钥仓库** | `cnb.cool/zqlit/blog-secrets` | CNB「密钥仓库」类型,10 项变量经 `imports` 注入 | | **境内源站** | 又拍云对象存储 | 由 CNB 国内节点**直传**(原为 `if: false` 的死代码) | @@ -48,9 +49,10 @@ ``` ① 写作 write-server(网页) 或 write/(本地 Windows) │ -② git push git push origin main (别名 git pushall,同一件事) - │ └─ origin → cnb.cool/zqlit/blog (**唯一远端**,触发构建) - │ ★ 异地备份不走 git 远端 —— 由本机计划任务每天 03:30 +② git push git pushall = git push origin main ; git push gitea main + │ ├─ origin → cnb.cool/zqlit/blog (**主仓**,触发构建) + │ └─ gitea → 23.254.236.47:3001/zqlit/blog(自建 Gitea,代码同步辅仓) + │ ★ 备选异地备份不走 git 远端 —— 由本机计划任务每天 03:30 │ 跑整仓加密 bundle 传到 F50 上的 OpenList(§5.6) ▼ ③ CNB 流水线(国内节点,约 3.5 分钟,7 个 stage 顺序执行) @@ -113,40 +115,53 @@ ### 5.1 推送 ```bash -git push origin main # 推 CNB(触发构建) -git pushall # 完全等价 —— 它只是个别名,本仓只有这一个远端 +git pushall # CNB 与 Gitea 都推 —— 发布用这个 +git push origin main # 只推 CNB(触发构建) +git push gitea main # 只推 Gitea 辅仓 ``` -**本仓只有 `origin` 一个 git 远端。** 曾经的双推(`git push origin main; git push gh main`) -随 GitHub 退役而取消;后来一度改写成 `origin + gitee`,也随「异地备份改用 OpenList」 -(§5.6)一并取消 —— 备份换了介质,就不再需要第二个 git 远端。 +`git pushall` 的实际定义: -留着的两条教训: +``` +!git push origin main; ec=$?; git remote | grep -qx gitea && git push gitea main; exit $ec +``` -- **不要用「一个 remote 挂多个 pushurl」** —— 实测 git 是「顺序推、遇错即停」, - 第一个失败后面的都不推,备份意义就没了。真要加辅仓,得用「独立 remote + 别名」。 -- 别名当初写成 `;` 串联而非 `&&`,正是为了让一段失败不影响另一段。现在只有一个远端, - 这层考虑自然消失。 +**本仓有两个 git 远端:`origin`(CNB 主仓)+ `gitea`(自建 Gitea 代码同步辅仓)。** +演变路径:GitHub 双推 →(GitHub 被 AUP 清空)→ 一度只剩 `origin` → 现在回到双远端。 +注意 Gitee 从头到尾**没真正建起来过**(卡体积门槛,见 §5.3),别把它记成一代。 + +三条设计上的取舍: + +- **不用「一个 remote 挂多个 pushurl」** —— 实测 git 是「顺序推、遇错即停」, + 第一个失败后面的都不推,备份意义就没了。所以用「独立 remote + 别名」。 +- 别名用 `;` 串联而非 `&&`,正是为了让一段失败不影响另一段(主仓挂了辅仓照样推); + 再 `exit $ec` 把退出码还给主仓,免得辅仓成功掩盖主仓失败。 +- gitea 那一段带 `git remote | grep -qx gitea` 前置判断 —— + 没配辅仓时静默跳过,而不是白报一行「'gitea' does not appear to be a git repository」。 ### 5.2 远端 -| remote | 地址 | 角色 | -|---|---|---| -| `origin` | `https://cnb.cool/zqlit/blog.git` | **唯一远端**(fetch + push,触发 CNB 构建) | +| remote | 地址 | 角色 | fetch | push | +|---|---|---|---|---| +| `origin` | `https://cnb.cool/zqlit/blog.git` | **主仓**(CNB) | ✅ 唯一 fetch 源 | ✅ 触发 CNB 构建 | +| `gitea` | `http://23.254.236.47:3001/zqlit/blog.git` | **代码同步辅仓**(自建,Gitea 28.0.0) | ⛔ 只推不拉 | ✅ 代码留档,失败不阻断 | + +- **Gitea 凭据编在远端 URL 里**(`http://zqlit:<密码>@…`):容器/脚本环境没有交互终端、 + 也没有可用的凭据助手,这是让 `git push gitea` 免交互跑通的唯一简单写法。 + 明文只落在本机 `.git/config`(以及服务器上容器内的同名文件),**不进仓库、不随之推送**。 + ⚠️ 口令里若含 `#` 或 `&` 会被拼 URL 的 `sed` 吃掉 —— 那种情况请改用访问令牌。 +- 自建 Gitea **不参与构建**:`.github/` 已删、Actions 早已停用,它纯粹是代码副本。 + 当前版本 `28.0.0`,走 HTTP + 3001 端口,本机直连即可(**不需要**广州中转机, + 与当初「本机跑不通 git smart HTTP」的旧结论不同 —— 那次是沙箱代理的问题)。 +- 接入时的同步基线:Gitea 停在 `ed38f938`(2026-10-04),落后 57 个提交, + 且是本地 HEAD 的**祖先** → 一次 fast-forward 就追平,**无需强推、不丢历史**。 已移除的远端(改动前的完整配置快照留在 `.workbuddy-backup/git-remotes.*.txt`): | remote | 地址 | 移除于 | 原因 | |---|---|---|---| -| `gh` | `github.com/zqlit/blog` | 2026-10-06 | 账号被标记、仓库被按 AUP 清空(见下) | -| `gitee` | (从未真正建立过) | 2026-10-06 | 两条硬门槛过不去,改走 OpenList 离线备份(§5.3) | -| `gitea` | 自建 `23.254.236.47:3001` | 2026-10-06 | 发布链路早已不依赖;留着只会在推送时撞上落后 56 个提交的旧副本 | - -> **远端仓库本身都没删,只是本地不再挂。** 想临时恢复自建 Gitea 副本: -> ```bash -> git remote add gitea http://<用户名>:<密码>@23.254.236.47:3001/zqlit/blog.git -> git push gitea main -> ``` +| `gh` | `github.com/zqlit/blog` | 2026-10-06 | 账号被标记、仓库被按 AUP 清空(见下),**别再往回加** | +| `gitee` | (从未真正建立过) | — | 两条硬门槛过不去(§5.3) | > ⚠️ **GitHub 退役记录(2026-10-06)** > @@ -167,14 +182,26 @@ git pushall # 完全等价 —— 它只是个别名,本仓只有 > (`bootstrap.sh` / `docker-compose.editor.yml` / `server.mjs` / `README.md`)、 > 以及 CI 定义本身(整个 `.github/` 目录,1268 行)。 -### 5.3 异地备份的定案(2026-10-06) +### 5.3 远端与备份的分工(2026-10-06) -**结论:异地备份不走 git 远端 —— 改用整仓加密 bundle → OpenList(见 §5.6)。** +**结论:git 辅仓用「自建 Gitea」,备份用「整仓加密 bundle → OpenList」。两者都要,各管一段。** -一句话理由:备份要防的是「平台整体出问题」,而把副本放到**另一家同样性质的平台**上, -只是把鸡蛋从左边口袋挪到右边口袋。真正的第三层得**换介质**。 +| 层 | 载体 | 保住什么 | 依赖 | +|---|---|---|---| +| 主仓 `origin` | CNB | 源码 + 触发构建 | CNB 平台 | +| 代码辅仓 `gitea` | 自建 `23.254.236.47:3001` | 随时 `clone` 回来、按提交追溯 | **自己的机器** | +| 离线备份 | F50/OpenList 上的加密 bundle(§5.6) | 完整历史 + 所有对象,平台全挂也能恢复 | 家里的局域网 | -下面这些实测数据保留下来 —— 下次再冒出「要不要加个 git 辅仓」的念头时, +两层的区别不是「多一份」,而是**失效模式不同**:辅仓是**在线、可增量、可浏览**的副本, +但仍在别人的磁盘上;bundle 是**离线、离线介质、单一文件**的全量快照, +连 git 服务都没有也能恢复。所以辅仓不能替 bundle,bundle 也不如辅仓顺手。 + +**为什么不选「另一家托管平台」当辅仓**(Gitee / 云效 Codeup / GitLab): +把副本放到**另一家同样性质的托管平台**上,只是把鸡蛋从左边口袋挪到右边口袋 —— +GitHub 那次 AUP 清空就是活证。自建 Gitea 是唯一「不受第三方规则约束」的在线选项, +它早在建站初期就在跑,本就该留着。 + +下面这些实测数据保留下来 —— 下次再冒出「换一家托管平台当辅仓」的念头时, 这几张表就是答案。 体积分布(HEAD,实测 457.4 MB / 2509 文件): @@ -219,7 +246,8 @@ git pushall # 完全等价 —— 它只是个别名,本仓只有 > ★ **这些都只是「换一个篮子」,不是容灾。** > CNB 与 Gitee/Codeup 同属国内大区;GitHub/GitLab 又执行同一套合规逻辑 > (本轮 GitHub 被按 AUP 清空即为例证)。 -> 所以最终**一个 git 辅仓都没有采用** —— 改用不依赖任何平台账号的离线备份:见 §5.6。 +> 所以**托管平台这条路一个都没采用** —— 在线辅仓回到**自建 Gitea**(§5.2, +> 不受第三方规则约束),另加一层不依赖任何 git 服务的离线备份(§5.6)。 **顺带一个副作用**:`bin/linux/hugo`「出库」这件事**不用做了**。 那个 83.1 MB 二进制之所以成为问题,只因为它卡在 Gitee 的单文件 50 MB 上限上。 @@ -239,10 +267,12 @@ git pushall # 完全等价 —— 它只是个别名,本仓只有 - ⚠️ CNB 的 `imports` 是**一层映射**(key 就是变量名本身);又拍云服务名在 CNB 里必须叫 **`UPYUN_SERVICE`**(旧 Gitea 里叫 `UPYUN_BUCKET`,照抄会「变量未定义」) -### 5.6 整仓离线备份(中兴 F50 / OpenList)★ 当前**唯一**的异地备份 +### 5.6 整仓离线备份(中兴 F50 / OpenList)★ 备选异地备份 -> **定位**:2026-10-06 起这一层就是本项目的异地备份本体 —— 不再有任何 git 辅仓(§5.3)。 -> 所以它的**可靠性**与**失败可见性**都比以前更重要:它一旦悄悄失效,代码就只剩 CNB 一处。 +> **定位**:2026-10-06 起这一层是**备选**异地备份 —— 在线那一路已有自建 Gitea 代码辅仓(§5.2)。 +> 但它的地位并没有因此变轻:**它是唯一一层「不依赖任何 git 服务」的备份**, +> 平台全挂、账号被封、服务器被回收都影响不到它。所以它的**可靠性**与**失败可见性** +> 依然是最要紧的:一旦悄悄失效,能挡住「平台级事故」的就只剩它。 > 失败告警因此是必备件,不是加分项。 **目标介质**:中兴 F50 5G CPE 内置 256 GB 存储,上面跑 OpenList @@ -297,9 +327,9 @@ node scripts/backup-bundle.mjs --decrypt <文件> [--out x.bundle] # 恢复用 | `SMTP_HOST` / `SMTP_PORT` / `SMTP_USER` / `SMTP_PASS` / `SMTP_TO` | 失败告警发信(复用 CNB 流水线那套 QQ 邮箱授权码) | > ⚠️ `BACKUP_PASSPHRASE` 是恢复 bundle 的唯一钥匙,**必须另存进密码管理器**。 -> 它不构成单点:CNB 主仓仍是明文副本,口令丢了只是少一份备份。 +> 它不构成单点:CNB 主仓与自建 Gitea 辅仓都还是明文副本,口令丢了只是少一份备份。 -**失败告警**(`scripts/backup-run.mjs`)—— 这是「唯一辅仓」的必备件。 +**失败告警**(`scripts/backup-run.mjs`)—— 这是「离线那一层」的必备件。 跑完备份后,退出码非 0 就通过 `scripts/send_mail.js` 发一封告警邮件 (主题带 `★`,正文附日志尾部与常见原因清单)。成功时默认**不发**(避免每天一封的邮件疲劳), 加 `--notify-success` 才发。 @@ -316,7 +346,7 @@ node scripts/backup-bundle.mjs --decrypt <文件> [--out x.bundle] # 恢复用 日志追加到 `.workbuddy-backup/logs/backup.log`,超 5 MB 自动轮转。 > 保留 **7 份 = 一周窗口**(每份 605.5 MB ≈ 4.2 GB,F50 有 256 GB)。 -> 原本是 3 份;升格为「唯一辅仓」后放宽 —— 空间不值钱,回溯窗口值钱。 +> 原本是 3 份;定位成「离线那一层备份」后放宽 —— 空间不值钱,回溯窗口值钱。 ★ `backup-task.cmd` **内容必须全 ASCII**:cmd.exe 按**当前代码页**(zh-CN 是 GBK) 解析批处理文件,而 node 输出 UTF-8;UTF-8 中文注释会吞掉 CR/LF、 @@ -350,15 +380,15 @@ git clone blog.bundle blog # 或 git fetch blog.bundle 'refs/*:refs/*' | # | 项 | 说明 | |---|---|---| | 1 | `gh` remote(GitHub) | ✅ **已移除**(2026-10-06)。GitHub 账号被标记、仓库被 AUP 清空 —— **GitHub 已彻底退出本项目**:远端、推送别名、发布链路 4 处硬编码、CI 定义全部清干净 | -| 2 | **异地备份方案** | ✅ **已定案**(2026-10-06):**不走 git 远端**,改用整仓加密 bundle → F50/OpenList(§5.6)。`pushall` 已收回为「只推 origin」,`gitee` / `gitea` 远端均已移除。选型依据(Gitee 两道硬门槛、Codeup 对照)保留在 §5.3 备查 | +| 2 | **远端与备份定案** | ✅ **已定案**(2026-10-06):**CNB 主仓 + 自建 Gitea 代码辅仓 + OpenList 离线 bundle**(§5.2/§5.3/§5.6)。自建 Gitea 不受第三方规则约束,是唯一「平台出事也带不走」的在线副本;离线 bundle 保完整历史。选型依据(Gitee 两道硬门槛、Codeup 对照)保留在 §5.3 备查 | | 3 | `bin/linux/hugo` 出库 | ✅ **不必做**。那个 83.1MB 二进制只卡 Gitee 的单文件上限;既然不走 Gitee,`deploy/Dockerfile` 的取 hugo 方式与 `.cnb.yml` 的 `by:` 字段一行都不用改 | -| 4 | `gitea` remote | ✅ **已移除**(2026-10-06)。远端仓库本身没删,需要时可 `git remote add` 恢复(命令见 §5.2) | +| 4 | `gitea` remote | ✅ **已恢复**(2026-10-06):作为代码同步辅仓重新挂上,`pushall` 恢复双推(`origin` + `gitea`)。接入时 Gitea 停在 `ed38f938`,一次 fast-forward 追平(+57 提交),**未强推、未丢历史**。发布链路 5 处默认值同步改回 `origin,gitea`(`bootstrap.sh` / `docker-compose.editor.yml` / `server.mjs` / `README.md` / `setup-cnb-remotes.sh`) | | 5 | GitHub 侧旧 workflow | ✅ **已删除**(2026-10-06)。整个 `.github/` 目录移除(`deploy.yml` 1169 行 + `aliyun-backup.yml` + `cleanup.yml`),共 −1284 行 —— **GitHub 已不再是任何环节的依赖**。旧实现可在 git 历史中查 | | 6 | 令牌 scope | 缺 `repo-cnb-history:r`(读构建日志);补上后 agent 可自行排错 | -| 7 | Gitea 主机 / 广州中转机 | 发布链路已不依赖;是否退役取决于其它用途 | +| 7 | Gitea 主机 / 广州中转机 | **Gitea 主机已回归**(2026-10-06):作为代码辅仓,本机直连 `23.254.236.47:3001` 即可推。广州中转机仍不参与 git 链路 | | 8 | `README.md` | ✅ 已更新(去 GitHub 化 + 补上备份脚本说明) | | 9 | **敏感文件仍在跟踪中** | `.env`、`GITEA_SECRETS.md`、`write-server/nginx/ssl/privkey.pem`、`content/posts/2024/.../setup-secrets.png` —— 若还要推任何新平台,先 `git rm --cached` 并轮换凭据。⚠️ 当初「不改写历史」的唯一顾虑是备份会分叉;**GitHub 那份已消失,这个顾虑没有了** → `git filter-repo` 现在是可行窗口(代价:全部提交 SHA 改变)。离线 bundle 已加密(§5.6),不受此影响 | -| 10 | **整仓离线备份** | ✅ **已上线**(2026-10-06),**且是当前唯一的异地备份**,见 §5.6。计划任务 `Blog-BundleBackup` 每天 03:30 跑,端到端已验证;失败会发告警邮件(已实测)。待补三件安全项:把 `BACKUP_PASSPHRASE` 抄进密码管理器;给 OpenList **改掉 admin 密码 + 开两步验证**(现 `otp: false`,且旧密码已出现在对话里);确认 5244 端口**未暴露到公网** | +| 10 | **整仓离线备份** | ✅ **已上线**(2026-10-06),现为**备选**异地备份(在线那一路是自建 Gitea 辅仓),见 §5.6。计划任务 `Blog-BundleBackup` 每天 03:30 跑,端到端已验证;失败会发告警邮件(已实测)。待补三件安全项:把 `BACKUP_PASSPHRASE` 抄进密码管理器;给 OpenList **改掉 admin 密码 + 开两步验证**(现 `otp: false`,且旧密码已出现在对话里);确认 5244 端口**未暴露到公网** | | 11 | **F50 存储目录的使用约定** | `/本地/` 下的 `刷机` / `系统` / `资料` / `软件` / `驱动` / `备份` 都是用户自己在管的类别。**本项目的 bundle 只写 `备份/blog-bundle/` 子目录**,不与用户手工备份(`github-zqlit-*`、`local-repos-*`)混放 | ---