From f16b9487d3fc31d8610f9987e831ab10b48b3fd1 Mon Sep 17 00:00:00 2001 From: wait4xx Date: Sat, 24 Oct 2026 22:10:59 +0800 Subject: [PATCH] =?UTF-8?q?feat(deploy):=20=E5=8F=91=E5=B8=83=E9=93=BE?= =?UTF-8?q?=E5=9C=B0=E5=9F=BA=E4=B8=89=E4=BB=B6=E5=A5=97=E2=80=94=E2=80=94?= =?UTF-8?q?webhook=20=E9=AA=A8=E6=9E=B6/=E6=9E=84=E5=BB=BA=E5=8D=A0?= =?UTF-8?q?=E4=BD=8D/=E5=9B=9E=E6=BB=9A=E5=B9=B2=E8=B7=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../specs/2026-10-24_add-blog-infra/tasks.md | 4 +- infra/deploy/build.sh | 15 ++++++++ infra/deploy/rollback-dryrun.sh | 33 +++++++++++++++++ infra/deploy/webhook-receive.sh | 37 +++++++++++++++++++ 4 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 infra/deploy/build.sh create mode 100644 infra/deploy/rollback-dryrun.sh create mode 100644 infra/deploy/webhook-receive.sh diff --git a/.spec/specs/2026-10-24_add-blog-infra/tasks.md b/.spec/specs/2026-10-24_add-blog-infra/tasks.md index 845e927..7b8862c 100644 --- a/.spec/specs/2026-10-24_add-blog-infra/tasks.md +++ b/.spec/specs/2026-10-24_add-blog-infra/tasks.md @@ -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` diff --git a/infra/deploy/build.sh b/infra/deploy/build.sh new file mode 100644 index 0000000..368beba --- /dev/null +++ b/infra/deploy/build.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" diff --git a/infra/deploy/rollback-dryrun.sh b/infra/deploy/rollback-dryrun.sh new file mode 100644 index 0000000..0f21e7e --- /dev/null +++ b/infra/deploy/rollback-dryrun.sh @@ -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 改回 + 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; } diff --git a/infra/deploy/webhook-receive.sh b/infra/deploy/webhook-receive.sh new file mode 100644 index 0000000..16cc4d6 --- /dev/null +++ b/infra/deploy/webhook-receive.sh @@ -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 +# 真实接入(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] }" + +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