Files
blog/scripts/pushall.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

68 lines
2.8 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
# =====================================================================
# 一条命令把本机提交推到所有远端(CNB 主仓 + 自建 Gitea 代码辅仓)
#
# 为什么需要「先 fetch 再 rebase」:
# 线上写作后台(editor-api 容器,post.usj.cc)发布文章时是**直接往 main 推**的。
# 所以本机在「上次 fetch 之后」只要后台发过文章,本机 main 就与远端分叉 ——
# 此时直接 push 会被 rejected(fetch first)。
# 这不是异常,是**日常**:2026-10-06 第一次实测就撞上了。
# 所以推送前先把自己的提交 rebase 到远端之上,保持线性历史。
#
# 安全边界:
# · 只 rebase **本机未推送的**提交,不外加强推
# · 有冲突就**停在 rebase 中途**交给人工处理(`git rebase --abort` 可放弃)
# · 工作区不干净时 git 自己会拒绝 rebase,不会吞掉改动
# · 任一远端推送失败**不改判另一个** —— 主仓失败时辅仓照样推,
# 备份的意义正在于「主仓出问题时它那儿还有」;但退出码以第一次失败为准
#
# 用法:
# git pushall (别名指向本脚本,等价于直接 bash scripts/pushall.sh)
# bash scripts/pushall.sh
# =====================================================================
set -uo pipefail
cd "$(git rev-parse --show-toplevel)" || exit 1
REMOTES=("origin")
git remote | grep -qx gitea && REMOTES+=("gitea")
echo "1/3 同步远端 (origin)…"
git fetch origin --prune 2>&1 | sed 's/^/ /' || { echo "!! fetch 失败,中止"; exit 1; }
if git rev-parse --verify --quiet origin/main >/dev/null; then
behind=$(git rev-list --count HEAD..origin/main)
if [ "$behind" -gt 0 ]; then
echo " 远端领先 $behind 笔(大概是后台发的文章)→ rebase 到其之上"
if ! git rebase origin/main 2>&1 | sed 's/^/ /'; then
echo "!! rebase 冲突 —— 已停在 rebase 中途,请处理:"
echo " git status 看冲突文件"
echo " git rebase --continue 解决后继续"
echo " git rebase --abort 放弃本次 rebase(回到原状)"
exit 1
fi
else
echo " 本机已是最新"
fi
fi
echo "2/3 推送 → ${REMOTES[*]}"
ec=0
for r in "${REMOTES[@]}"; do
# set -o pipefail 在,所以管道退出码 = git push 的(sed 不会掩盖它)
if ! git push "$r" main 2>&1 | sed "s/^/ [$r] /"; then
# ★ 这里不能写 ec=$? —— `!` 之后的 $? 是取反后的结果(0),不是 git 的退出码
[ "$ec" -eq 0 ] && ec=1
fi
done
if [ "$ec" -eq 0 ]; then
echo "3/3 ✓ 全部远端同步完成"
for r in "${REMOTES[@]}"; do
echo " $r = $(git ls-remote "$r" refs/heads/main 2>/dev/null | cut -c1-8)"
done
else
echo "3/3 ✗ 有远端推送失败(退出码 $ec)"
fi
exit "$ec"