Files
blog/scripts/setup-cnb-remotes.sh
T
zqlit 3eeafa07b2 fix(证书管家): writeapi.usj.cc 证书纳管(相对软链接);pushall 改为先 rebase 再推
用户:「动手 推送吧」—— 动手处理两个有死线的遗留。

一、writeapi.usj.cc 的证书(12 月会断)→ 已解决

现状复核推翻了上一轮的判断:
  · 上一轮记的「它的 ssl_certificate 引用不在 /www/sites/ 树里」是**错的** ——
    /www/conf.d/writeapi.usj.cc.conf 里明确写着
    ssl_certificate /www/sites/writeapi.usj.cc/ssl/fullchain.pem;
  · 真正原因是**它在 1Panel 面板上根本不是一个网站**(面板共 11 个站点,没有它),
    所以 `findWebsite('writeapi.usj.cc')` 必然抛「找不到网站」→
    加进 one_panel_sites 只会让**每次续期都报错**(而且只警告不阻断,长期没人发现)
  · 实测确认:redeploy 时其余 5 个站点正常,只有它失败

解法:**不写一行代码** —— 它与 artalk.usj.cc 本来就同用一张 *.usj.cc 证书,
让它用**相对软链接跟随 artalk** 即可,随续期自动更新:
  ln -sfn ../../artalk.usj.cc/ssl/fullchain.pem /1panel/1panel/www/sites/writeapi.usj.cc/ssl/fullchain.pem

★★ 踩到的坑:软链接**必须相对路径**
  宿主 /1panel/1panel/www/sites/ ↔ 容器内 /www/sites/(1Panel 的 openresty 跑在容器
  `1Panel-openresty-Kmh2`)。第一版写成宿主绝对路径 → **容器侧断链** →
  nginx -s reload **静默失败**(保留旧配置、服务不中断、握手照旧拿旧证书)→
  表面全绿,真实后果是「等某天 nginx 重启,writeapi 的 HTTPS 直接挂」。
  唯一线索是 worker 进程的启动时间没变 —— **所以验收必须看 worker 是否真的重启**。

验收(全部实测):
  替换前 CN=usj.cc    / LiteSSL RSA CA / 2026-12-07(mtime 10-04 21:50)
  替换后 CN=*.usj.cc  / LiteSSL ECC CA / 2027-01-04
  容器内 head -1 → -----BEGIN CERTIFICATE----- / -----BEGIN PRIVATE KEY-----
  预检 syntax is ok / test is successful
  reload → signal process started;**worker 22:51:39 重启**(证明软链接真被读了)
  握手 SNI=writeapi.usj.cc → *.usj.cc / Jan 4 2027;实访 /health → HTTP 200

新增 deploy/cn-certkeeper/src/dump-cert.mjs:把 KV 里(AES-GCM 加密)的证书导成明文 PEM,
补上「KV ↔ 手工 vhost 磁盘文件」之间原先不存在的桥。只读,用完即删(含明文私钥)。

操作层的两个坑(写进文档):
  · 1Panel 文件 API **拒绝写 .mjs**(返回 500「目标路径不存在」,实为可执行扩展名过滤);
    往新建的 root:root 700 目录写也会失败 → 临时脚本用
    `docker exec -i … sh -c 'cat > 路径' < 本地文件` 送
  · cnrun.py(1Panel 计划任务通道)偶发「退出码 0 但零输出」;
    加 `timeout 30 docker exec …` 包一层即稳定,判断状态优先看容器内直接证据

二、配置回退 + 文档更正

  · certkeeper-config.mjs:把 writeapi 从 one_panel_sites 移除,换成一段注释说明
    「它不是 1Panel 站点,别加进来」+ 软链接做法 + 必须相对的警告;
    线上 config.kv 同步回退(sha256 与改动前完全一致 ccac2530…)
  · 更正「3 个手工 vhost」的说法:vaultwarden **是**正常纳管的站点(面板 #13,
    主域名 vw.usj.cc,别名才是 vaultwarden)→ 手工 vhost 实际只剩 writeapi + dnsapi
  · dnsapi.usj.cc 重新定性为**不做**:它是 cn-dns-helper 的反代入口
    (proxy_pass 127.0.0.1:8018,仅 HTTP),去留应与 cn-dns-helper 一起决定,
    不该单独给一个待退役的服务加 HTTPS

三、pushall 加固:先 fetch + rebase 再推(本次真实撞到的问题)

推 CNB 时被 rejected —— 因为线上写作后台(editor-api)发文章会**直接推 main**,
本机两笔提交与之分叉。这不是异常,是**日常**。原别名直接 push,必然反复撞。

  · 新增 scripts/pushall.sh:fetch → 已在远端之后则 rebase → 推所有远端
    - 冲突时**停在 rebase 中途**交人工(不强推、不丢东西)
    - 工作区不干净时 git 自己会拒绝 rebase(不会吞改动)
    - 任一远端失败**不改判另一个**(主仓失败辅仓照样推),退出码以第一次失败为准
    - ⚠️ 修了自己写的一处疏漏:`if ! cmd; then ec=$?` 的 $? 是**取反后**的结果(0),
      不是 git 的退出码 —— 必须显式 ec=1(靠 set -o pipefail 保证管道退出码不被 sed 掩盖)
  · setup-cnb-remotes.sh 的别名改指向脚本;README 脚本表补这一行

四、文档

  · docs/证书管家.md:速览表「已知遗留」→ 已解决;§9.14 遗留段重写(含纠正自己写错的判据
    「路径像不像不构成判据,要直接查面板站点清单」);**新增 §9.15**
    「手工 vhost 的证书:用相对软链接跟随已纳管站点」(机制 + 路径坑 + 三条验收 + dump-cert 用法)
  · docs/架构精简候选.md:候选 5 标记完成,补上「为什么 A 走不通、B 未采用」;
    执行顺序里划掉它
  · 架构总览.md:§5.7「已知遗留」重写(2 个手工 vhost + vaultwarden 更正);
    §6 待办 #13 改为已解决、#14 标注完成
2026-10-06 22:54:58 +08:00

172 lines
8.3 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# =====================================================================
# 配置 git 远端 —— CNB 主仓 + 自建 Gitea 代码同步辅仓
# =====================================================================
# 历史沿革:
# 2026-10-04 从「GitHub + 自建 Gitea 双推」切到「CNB 主 + GitHub 备份」
# 2026-10-06 GitHub 账号被平台标记、仓库被按 AUP 清空 → 移除 gh
# 2026-10-06 一度改为「CNB 主 + Gitee 辅」,但 Gitee 免费版卡在
# 单仓 500MB / 单文件 50MB 两道硬门槛(本仓 bin/linux/hugo 83.1MB),
# 云效 Codeup 虽宽松却同属国内大区 —— 多一层同类平台不如换一类介质
# 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」:
# 实测(2026-10-04)git 对多个 pushurl 是「顺序推、遇错即停」——
# 第一个失败,后面的远端一个都不会推,那就失去备份意义了。
# 所以用「独立 remote + 别名」而不是 pushurl。
#
# 用法:
# bash scripts/setup-cnb-remotes.sh <CNB_URL>
#
# 例:
# bash scripts/setup-cnb-remotes.sh https://cnb.cool/zqlit/blog
#
# 附带令牌(直接写进 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
# =====================================================================
set -euo pipefail
CNB_URL="${1:-}"
if [ -z "$CNB_URL" ]; then
cat <<'USAGE'
用法:bash scripts/setup-cnb-remotes.sh <CNB_URL>
提示:
· CNB 仓库地址直接用仓库页地址,带不带 .git 都行
· CNB **不支持 SSH**,只能 HTTPS + 访问令牌(用户名固定填 cnb,密码填令牌)
令牌:「个人设置 → 访问令牌」,勾「代码仓库 → 读写」
· 自建 Gitea 辅仓走 http://23.254.236.47:3001(账号 zqlit),**另需 GITEA_PASS**:
GITEA_PASS='Gitea 密码或令牌' bash scripts/setup-cnb-remotes.sh <CNB_URL>
· 备选异地备份走中兴 F50 上的 OpenList(与 git 远端无关):
scripts/backup-bundle.mjs 打包 + AES-256-GCM 加密 + 上传(含校验与恢复)
.workbuddy-backup/openlist-backup.env 连接与口令配置(被 git 忽略)
Windows 计划任务 Blog-BundleBackup 每天 03:30 自动跑
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 ;;
esac
ROOT="$(git rev-parse --show-toplevel)"
cd "$ROOT"
STAMP="$(date +%Y%m%d-%H%M%S)"
BACKUP=".workbuddy-backup/git-remotes.$STAMP.txt"
mkdir -p .workbuddy-backup
{
echo "# 改动前的 git 远端配置($STAMP)"
echo "# 用途:回退参考。本文件可能含明文凭据,勿提交、勿外传。"
echo
echo "## git remote -v"
git remote -v
echo
echo "## 原始 remote.* 与 credential.* 配置"
git config --local --get-regexp '^(remote|credential)\.' || true
} > "$BACKUP"
echo "原配置已备份:$BACKUP"
# 1) origin = CNB(主仓,fetch + push,推送即触发构建)
git remote remove origin 2>/dev/null || true
git remote add origin "$CNB_URL"
# 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
# 4) pushall —— CNB 与 Gitea 都推
# ★ 2026-10-06 起逻辑搬到 scripts/pushall.sh —— 因为线上写作后台(editor-api)
# 发文章时**会直接往 main 推**,本机推送前必须先 fetch+rebase,否则必被 rejected。
# (最初的别名就是被这个场景打回来的:本地两笔 vs 远端一篇新文章 → 分叉。)
# 脚本里还带:rebase 冲突时停在半路交人工、gitea 不存在时静默跳过、
# 「主仓失败辅仓照样推」且退出码以主仓为准。
# 先探一下 gitea 在不在,免得没配辅仓时每次 pushall 都白报一行错。
git config alias.pushall '!bash "$(git rev-parse --show-toplevel)/scripts/pushall.sh"'
# 5) 凭据
#
# 本机全局 helper 是 GCM(git-credential-manager.exe)。实测它对 cnb.cool 这类
# 第三方 HTTPS 远端会**每次都弹窗**,而且 GCM_INTERACTIVE=never +
# GIT_TERMINAL_PROMPT=0 都压不住 —— git ls-remote 直接挂死(timeout 25s 未返回)。
# → 本仓按需补一个**空的** credential.helper 条目遮蔽全局 GCM,
# 再显式改用 wincred:同样写 Windows 凭据管理器(加密),但没有 UI 弹窗。
# (另:PortableGit 未打包 git-credential-store,exec-path / mingw64/bin 都没有)
#
# ★ 用「已存在就不再加」而不是 --replace-all:本仓可能另有一个从文件读令牌的
# helper($HOME/.workbuddy/secrets/cnb-token),那是有效的,不能被脚本抹掉。
# 也避免重复运行把空条目越堆越多。
store_cred() { # host username password
printf 'protocol=https\nhost=%s\nusername=%s\npassword=%s\n\n' "$1" "$2" "$3" \
| git credential-wincred store
}
if [ -n "${CNB_TOKEN:-}" ]; then
if ! git config --local --get-all credential.helper 2>/dev/null | grep -qx ''; then
git config --local --add credential.helper ""
fi
git config --local credential.https://cnb.cool.helper wincred
store_cred cnb.cool cnb "$CNB_TOKEN"
echo "CNB 凭据已写入 Windows 凭据管理器(wincred,无弹窗)"
else
echo "未提供 CNB_TOKEN,已跳过凭据写入。需要时重跑:"
echo " CNB_TOKEN='你的令牌' bash scripts/setup-cnb-remotes.sh $CNB_URL"
fi
echo
echo "=== 当前远端 ==="
git remote -v | sed -E 's#://[^@/]*@#://***@#g'
echo
echo "=== 下一步 ==="
echo " git pushall # 依次推 origin(CNB) 与 gitea(推荐)"
echo " git push origin main # 只推 CNB(触发构建)"
echo " git push gitea main # 只推 Gitea 辅仓"
echo
echo " 备选异地备份不在这里 —— 想立刻跑一次整仓加密备份:"
echo " node scripts/backup-bundle.mjs --verify --keep 7"