feat(deploy): 发布链地基三件套——webhook 骨架/构建占位/回滚干跑

- webhook-receive.sh:HMAC 骨架 + 仅 main 分支触发(干跑验证两条 ref 路径)
- build.sh:锁发布链形态与路径约定,astro 命令留 Phase 1 TODO
- rollback-dryrun.sh:回滚四前置条件验证 + 人为操作手册(秒级回 18090)
- staging 落用户区 ~/docker_apps/blog/www-staging(宿主 sudo 不可用,nginx 以 root 读用户目录无碍)

验证: webhook 干跑 main accepted / non-main ignored;rollback-dryrun
      四要素 ok(halo vhost/容器/响应/staging)

Co-authored-by: GLM-5.3
This commit is contained in:
wait4xandGLM-5.3 committed 2026-10-24 22:10:59 +08:00
1 parent ddcda54f90
commit f16b9487d3
4 files changed
+87 -2

No files matched your search

@@ -35,8 +35,8 @@
- boundary: 用户在 ESA 控制台添加 git.wait4x.online(代理开)后:安装两份 conf 到 sites-available + symlink + nginx -t + reload;仅新增文件,不改既有 vhost - boundary: 用户在 ESA 控制台添加 git.wait4x.online(代理开)后:安装两份 conf 到 sites-available + symlink + nginx -t + reload;仅新增文件,不改既有 vhost
- verify: `curl -skf https://git.wait4x.online -o /dev/null -w '%{http_code}' | grep -q 200` - verify: `curl -skf https://git.wait4x.online -o /dev/null -w '%{http_code}' | grep -q 200`
- [ ] T6 发布链地基(webhook 脚本骨架 + 回滚预案) - [x] T6 发布链地基(webhook 脚本骨架 + 回滚预案)
- depends-on: T5 - depends-on: T3(2026-10-24 修正:骨架仅依赖 gitea 形态,与 T5 域名解耦,提前完成)
- claim: orchestrator 2026-10-24 - claim: orchestrator 2026-10-24
- boundary: 仓库内新增 `infra/deploy/`(webhook 接收脚本骨架 + 回滚预案文档);宿主机建 `/var/www/blog-staging/` 占位目录;不切换任何 nginx upstream - boundary: 仓库内新增 `infra/deploy/`(webhook 接收脚本骨架 + 回滚预案文档);宿主机建 `/var/www/blog-staging/` 占位目录;不切换任何 nginx upstream
- verify: `bash infra/deploy/rollback-dryrun.sh` - verify: `bash infra/deploy/rollback-dryrun.sh`
+15
View File
@@ -0,0 +1,15 @@
#!/bin/sh
# 构建管线骨架(Phase 0 占位,Phase 1 填充真实命令)
#
# 发布链:gitea push(main) → webhook → 本脚本 → 构建产物 rsync 到 www-staging
# → nginx 切换/生效。Phase 0 只锁形态与路径约定,不引入 Astro 依赖。
set -e
REPO_DIR="$HOME/Code/Github/my-blog" # Phase 4 改为构建机上的裸 clone
STAGING="$HOME/docker_apps/blog/www-staging"
echo "[build] placeholder: Phase 1 将在此执行 astro build"
# TODO(phase1): cd "$REPO_DIR" && pnpm install --frozen-lockfile && pnpm build
# TODO(phase1): rsync -a --delete "$REPO_DIR/dist/" "$STAGING/"
mkdir -p "$STAGING"
printf 'staging placeholder built at %s\n' "$(date -Is)" > "$STAGING/index.html"
echo "[build] staging updated: $STAGING"
+33
View File
@@ -0,0 +1,33 @@
#!/bin/sh
# 回滚预案干跑:验证回滚前置条件齐备,并打印真实切换命令(不执行)
#
# 背景:Phase 4 上线时 wait4x.online 的 nginx 从 proxy_pass 18090(Halo)
# 切到静态目录;任何时刻回滚 = 把 location 改回 18090。本脚本在 Phase 0
# 确认三要素存在,给人一条可复制的回滚路径。
set -e
NGINX_DEFAULT=/etc/nginx/sites-available/default
STAGING="$HOME/docker_apps/blog/www-staging"
fail=0
check() { if eval "$2"; then echo " ok $1"; else echo " FAIL $1"; fail=1; fi }
check "halo vhost 存在(回滚目标 upstream 可达配置)" \
"grep -q 'proxy_pass http://127.0.0.1:18090' $NGINX_DEFAULT"
check "halo 容器存活(回滚有意义)" \
"docker inspect -f '{{.State.Status}}' halo-halo-1 | grep -q running"
check "Halo 本机响应" \
"curl -sf http://127.0.0.1:18090 -o /dev/null"
check "staging 占位目录就绪" \
"test -f $STAGING/index.html"
cat <<'EOF'
回滚操作手册(人为执行,勿自动化):
1. 编辑 /etc/nginx/sites-available/default 中 wait4x.online 的 location /:
proxy_pass http://127.0.0.1:18090; # 从 root <staging> 改回
2. nginx -t && systemctl reload nginx
3. ESA 控制台刷新缓存(或等待 TTL)
预期效果:秒级回到 Halo;静态文件目录保留不动,可再次前切。
EOF
[ "$fail" = "0" ] && echo "rollback dry-run: all preconditions ok" || { echo "rollback dry-run: FAILURES above"; exit 1; }
+37
View File
@@ -0,0 +1,37 @@
#!/bin/sh
# gitea webhook 接收骨架(Phase 0:仅鉴权与触发框架,构建动作为占位)
#
# 为什么 shell 而非服务:单用户发布链,gitea webhook 直接 POST 到本机端口
# 需要常驻服务;Phase 0 先以 gitea 侧 webhook → 本脚本(经 systemd path 或
# 手动)的形态落骨架,Phase 4 再决定是否升级为常驻接收器。
#
# 用法(Phase 0 干跑): sh infra/deploy/webhook-receive.sh --dry-run <payload.json>
# 真实接入(Phase 4): gitea 仓 webhooks → URL 指向接收器,X-Gitea-Signature 头做 HMAC-SHA256
set -e
DRY_RUN=0
[ "${1:-}" = "--dry-run" ] && DRY_RUN=1 && shift
# SECRET 从宿主私有文件读取,绝不入库(与 gitea init-credentials.txt 同策略)
SECRET_FILE="$HOME/docker_apps/gitea/webhook-secret.txt"
PAYLOAD="${1:?usage: webhook-receive.sh [--dry-run] <payload.json>}"
verify_signature() {
# Phase 4 实装:openssl sha256 与 X-Gitea-Signature 比对;干跑跳过
# 密钥文件路径固定如上,存在性检查失败时只报固定文案,不回显路径变量
test "$DRY_RUN" = "1" && { echo "[dry-run] signature check skipped"; return 0; }
test -s "$SECRET_FILE" || { echo "missing webhook secret file" >&2; exit 1; }
echo "TODO(phase4): HMAC-SHA256 verify" >&2; exit 9
}
trigger_build() {
# 仅接受 push 到默认分支的事件(payload 的 ref 字段),防误触发
if python3 -c 'import json,sys; d=json.load(open(sys.argv[1])); sys.exit(0 if d.get("ref","").endswith("/main") else 1)' "$PAYLOAD"; then
echo "[webhook] push to main accepted -> build.sh"
if [ "$DRY_RUN" = "1" ]; then echo "[dry-run] build skipped"; else sh "$(dirname "$0")/build.sh"; fi
else
echo "[webhook] non-main ref ignored"
fi
}
verify_signature
trigger_build