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:
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
|
||||
- verify: `curl -skf https://git.wait4x.online -o /dev/null -w '%{http_code}' | grep -q 200`
|
||||
|
||||
- [ ] T6 发布链地基(webhook 脚本骨架 + 回滚预案)
|
||||
- depends-on: T5
|
||||
- [x] T6 发布链地基(webhook 脚本骨架 + 回滚预案)
|
||||
- depends-on: T3(2026-10-24 修正:骨架仅依赖 gitea 形态,与 T5 域名解耦,提前完成)
|
||||
- claim: orchestrator 2026-10-24
|
||||
- boundary: 仓库内新增 `infra/deploy/`(webhook 接收脚本骨架 + 回滚预案文档);宿主机建 `/var/www/blog-staging/` 占位目录;不切换任何 nginx upstream
|
||||
- verify: `bash infra/deploy/rollback-dryrun.sh`
|
||||
|
||||
@@ -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"
|
||||
@@ -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; }
|
||||
@@ -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
|
||||
Reference in new issue
Block a user