From 0f7c2dfe5bbd4d80a549aa1848adba6de82d9bdb Mon Sep 17 00:00:00 2001 From: wait4xx Date: Sat, 24 Oct 2026 22:35:24 +0800 Subject: [PATCH] =?UTF-8?q?chore(spec):=20=E5=BD=92=E6=A1=A3=20add-blog-in?= =?UTF-8?q?fra=E2=80=94=E2=80=94Phase=200=20=E4=B8=83=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E6=94=B6=E6=95=9B=E4=BA=A4=E4=BB=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - T1-T7 全部勾选,spec_verify --all 七项 exit=0 入 runs.jsonl - check 门禁收敛;交付:备份三件套/gitea 28.2.0/Artalk 2.10.0/ git.wait4x.online + /artalk 公网接入/发布链地基与回滚预案 - 生产 Halo 全程未动(healthy + 200 复查通过) - 含 spec pre-commit/pre-push 门禁钩子入库 Spec: 2026-10-24_add-blog-infra Co-authored-by: GLM-5.3 --- .githooks/pre-commit | 79 +++++++ .githooks/pre-push | 192 ++++++++++++++++++ .../completion-summary.md | 95 +++++++++ .../evidence/new-api-check.json | 0 .../evidence/verify_backup.sh | 0 .../2026-10-24_add-blog-infra/runs.jsonl | 14 ++ .../2026-10-24_add-blog-infra/spec.md | 39 ++-- .../2026-10-24_add-blog-infra/tasks.md | 19 +- 8 files changed, 412 insertions(+), 26 deletions(-) create mode 100755 .githooks/pre-commit create mode 100755 .githooks/pre-push create mode 100644 .spec/specs/archive/2026-10-24_add-blog-infra/completion-summary.md rename .spec/specs/{ => archive}/2026-10-24_add-blog-infra/evidence/new-api-check.json (100%) rename .spec/specs/{ => archive}/2026-10-24_add-blog-infra/evidence/verify_backup.sh (100%) mode change 100755 => 100644 create mode 100644 .spec/specs/archive/2026-10-24_add-blog-infra/runs.jsonl rename .spec/specs/{ => archive}/2026-10-24_add-blog-infra/spec.md (63%) rename .spec/specs/{ => archive}/2026-10-24_add-blog-infra/tasks.md (81%) diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..079b8cb --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,79 @@ +#!/bin/sh +set -eu + +# The source hook fails closed unless rendered; install_git_hooks.py +# replaces these placeholders with absolute paths in installed copies. +repo=$(git rev-parse --show-toplevel) +checker="/home/wait4x/.pi/agent/skills/spec/scripts/check_all_spec_packages.py" +# Checker resolution: +# 1. SPEC_CHECK_ALL_SCRIPT environment override — the explicit operator +# escape hatch; the path in effect is echoed to stderr so a swap is +# never silent. +# 2. The absolute path rendered by install_git_hooks.py, already held in +# $checker when the placeholder above was replaced. Repository content +# can never replace the gate checker; an unrendered template fails +# closed below. +if [ -n "${SPEC_CHECK_ALL_SCRIPT:-}" ]; then + checker="$SPEC_CHECK_ALL_SCRIPT" + printf '%s\n' "spec gate: using SPEC_CHECK_ALL_SCRIPT override: $checker" >&2 +fi + +if [ ! -f "$checker" ]; then + printf '%s\n' \ + "error: Spec gate checker not found at: $checker" \ + " The checker script is expected at the path above." \ + " Fix:" \ + " 1. Re-run: python3 scripts/install_git_hooks.py --root \"$repo\"" \ + " 2. The checker lives in the spec skill package under scripts/" >&2 + exit 1 +fi + +python="/home/wait4x/miniconda3/bin/python3.13" +python_placeholder_prefix=__SPEC_PYTHON_ +python_placeholder="${python_placeholder_prefix}PLACEHOLDER__" +if [ "$python" = "$python_placeholder" ]; then + python=${SPEC_PYTHON:-$(command -v python3 || true)} +fi +if [ -z "$python" ]; then + printf '%s\n' "error: Spec gate requires a trusted Python interpreter" >&2 + exit 1 +fi +if [ "$python" != "${python##*/}" ] && [ ! -x "$python" ]; then + printf '%s\n' "error: Spec gate requires a trusted Python interpreter" >&2 + exit 1 +fi + +# Branch-aware gate routing (AUD-07): the full gate guards the irreversible +# paths — commits on the main branch (the merge destination) and commits +# carrying archive paths on any branch. In-flight WIP commits on spec/* +# integration branches get the structural lint only; the full gate +# re-engages at the main-branch merge (pre-push + push_spec_package). +# The channel is decided by objective Git facts (branch name, staged +# paths), never by a committer-declared footer. +branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true) +case "$branch" in + spec/*) + if git diff --cached --name-only --diff-filter=ACMRT 2>/dev/null | grep -q "specs/archive/"; then + : # Archive-carrying commits keep the full gate on every branch. + else + printf '%s\n' \ + "spec gate: WIP commit on integration branch $branch — structural lint only;" \ + "the full gate runs for main-branch and archive-carrying commits" >&2 + exec "$python" "$checker" --root "$repo" --lint-only + fi + ;; +esac + +baseline="" +for ref in refs/heads/main refs/heads/master; do + if baseline=$(git rev-parse --verify "$ref" 2>/dev/null); then + break + fi + baseline="" +done +set -- +if [ -n "$baseline" ]; then + set -- --legacy-baseline "$baseline" +fi +"$python" "$checker" --root "$repo" "$@" +"$python" "$checker" --root "$repo" --index "$@" diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100755 index 0000000..7f620c6 --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,192 @@ +#!/bin/sh +set -eu + +repo=$(git rev-parse --show-toplevel) +checker="/home/wait4x/.pi/agent/skills/spec/scripts/check_all_spec_packages.py" +# Checker resolution: +# 1. SPEC_CHECK_ALL_SCRIPT environment override — the explicit operator +# escape hatch; the path in effect is echoed to stderr so a swap is +# never silent. +# 2. The absolute path rendered by install_git_hooks.py, already held in +# $checker when the placeholder above was replaced. Repository content +# can never replace the gate checker; an unrendered template fails +# closed below. +if [ -n "${SPEC_CHECK_ALL_SCRIPT:-}" ]; then + checker="$SPEC_CHECK_ALL_SCRIPT" + printf '%s\n' "spec gate: using SPEC_CHECK_ALL_SCRIPT override: $checker" >&2 +fi + +if [ ! -f "$checker" ]; then + printf '%s\n' \ + "error: Spec gate checker not found at: $checker" \ + " The checker script is expected at the path above." \ + " Fix:" \ + " 1. Re-run: python3 scripts/install_git_hooks.py --root \"$repo\"" \ + " 2. The checker lives in the spec skill package under scripts/" >&2 + exit 1 +fi + +python="/home/wait4x/miniconda3/bin/python3.13" +python_placeholder_prefix=__SPEC_PYTHON_ +python_placeholder="${python_placeholder_prefix}PLACEHOLDER__" +if [ "$python" = "$python_placeholder" ]; then + python=${SPEC_PYTHON:-$(command -v python3 || true)} +fi +if [ -z "$python" ]; then + printf '%s\n' "error: Spec gate requires a trusted Python interpreter" >&2 + exit 1 +fi +if [ "$python" != "${python##*/}" ] && [ ! -x "$python" ]; then + printf '%s\n' "error: Spec gate requires a trusted Python interpreter" >&2 + exit 1 +fi + +mode=${SPEC_PUSH_GATE:-touched} +case "$mode" in + touched|all) ;; + *) printf '%s\n' "error: SPEC_PUSH_GATE must be touched or all" >&2; exit 1 ;; +esac +zero=0000000000000000000000000000000000000000 +tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/spec-pre-push.XXXXXX") +baseline_ref="refs/spec-gate/legacy-baseline-$$" +cleanup() { + git update-ref -d "$baseline_ref" >/dev/null 2>&1 || true + rm -rf "$tmp_dir" +} +trap cleanup EXIT HUP INT TERM +remote_name=${1:-} +legacy_baseline=$zero +if [ -n "$remote_name" ]; then + main_branch=${SPEC_MAIN_BRANCH:-} + if [ -z "$main_branch" ]; then + symref=$(git ls-remote --symref "$remote_name" HEAD) \ + || { printf '%s\n' "error: cannot resolve authoritative remote HEAD for $remote_name" >&2; exit 1; } + main_branch=$(printf '%s\n' "$symref" | sed -n 's#^ref: refs/heads/\([^[:space:]]*\)[[:space:]]*HEAD$#\1#p' | sed -n '1p') + main_branch=${main_branch:-main} + fi + remote_main=$(git ls-remote --heads "$remote_name" "refs/heads/$main_branch") \ + || { printf '%s\n' "error: cannot resolve authoritative $remote_name/$main_branch" >&2; exit 1; } + legacy_baseline=$(printf '%s\n' "$remote_main" | sed -n 's/[[:space:]].*$//p' | sed -n '1p') + legacy_baseline=${legacy_baseline:-$zero} + if [ "$legacy_baseline" != "$zero" ]; then + git fetch --no-tags "$remote_name" "+refs/heads/$main_branch:$baseline_ref" >/dev/null 2>&1 \ + || { printf '%s\n' "error: cannot fetch authoritative $remote_name/$main_branch baseline" >&2; exit 1; } + fetched_baseline=$(git rev-parse --verify "$baseline_ref^{commit}") \ + || { printf '%s\n' "error: authoritative remote-main baseline object is unavailable" >&2; exit 1; } + [ "$fetched_baseline" = "$legacy_baseline" ] \ + || { printf '%s\n' "error: authoritative remote-main baseline changed during pre-push" >&2; exit 1; } + fi +fi +specs_args="" +if [ -n "${SPEC_SPECS_DIRS:-}" ]; then + old_ifs=$IFS + IFS=, + for specs_dir in $SPEC_SPECS_DIRS; do + case "$specs_dir" in + ''|*[!A-Za-z0-9._/-]*) + printf '%s\n' "error: SPEC_SPECS_DIRS must be comma-separated trusted relative paths" >&2 + exit 1 + ;; + esac + specs_args="$specs_args --specs-dir $specs_dir" + done + IFS=$old_ifs +fi +input_path=$tmp_dir/refs +cat >"$input_path" +index=0 + +while read -r local_ref local_sha remote_ref remote_sha; do + [ -n "${local_sha:-}" ] || continue + [ "$local_sha" = "$zero" ] && continue + case "$remote_ref" in + refs/heads/spec/*) + # AUD-07: in-flight handoff. Integration branches carry work in + # progress (WIP commits, handoff.md) through the remote; the + # irreversible path is the main-branch merge, which keeps the full + # gate below. + printf '%s\n' \ + "spec gate: releasing in-flight push of $remote_ref (integration-branch handoff);" \ + "the full gate applies when this work merges to the main branch" >&2 + continue + ;; + esac + index=$((index + 1)) + files_path=$tmp_dir/files-$index + commits_path=$tmp_dir/commits-$index + footer_slugs=$tmp_dir/footer-slugs-$index + footer_window_base="" + : >"$files_path" + : >"$commits_path" + : >"$footer_slugs" + + if [ "$remote_sha" = "$zero" ]; then + git log --name-only --pretty=format: "$local_sha" --not --remotes >"$files_path" \ + || { printf '%s\n' "error: cannot enumerate files for pushed ref $local_ref" >&2; exit 1; } + git rev-list --no-merges "$local_sha" --not --remotes >"$commits_path" \ + || { printf '%s\n' "error: cannot enumerate commits for pushed ref $local_ref" >&2; exit 1; } + else + git diff --name-only "$remote_sha" "$local_sha" >"$files_path" \ + || { printf '%s\n' "error: cannot diff pushed ref $local_ref" >&2; exit 1; } + git rev-list --no-merges "$remote_sha..$local_sha" >"$commits_path" \ + || { printf '%s\n' "error: cannot enumerate commits for pushed ref $local_ref" >&2; exit 1; } + fi + + # shellcheck disable=SC2086 + scope=$($python "$checker" --root "$repo" --scope-paths-file "$files_path" $specs_args) \ + || { printf '%s\n' "error: cannot derive trusted Spec scope for $local_ref" >&2; exit 1; } + touched_slugs=$(printf '%s\n' "$scope" | sed -n 's/^SPEC //p') + + if [ -z "$touched_slugs" ]; then + while read -r commit; do + [ -n "$commit" ] || continue + message=$(git show -s --format=%B "$commit" 2>/dev/null || true) + slugs=$(printf '%s\n' "$message" | sed -nE 's/^Spec:[[:space:]]*([a-z0-9][a-z0-9_-]*[a-z0-9]|[a-z0-9])[[:space:]]*$/\1/p') + if printf '%s\n' "$message" | grep -qE '^Spec:[[:space:]]*none[[:space:]]*(\([[:space:]]*\))?[[:space:]]*$'; then + printf '%s\n' \ + "error: pushed commit ${commit}: 'Spec: none' lite channel requires a non-empty reason" \ + " Write 'Spec: none ()' in the commit footer and retry." >&2 + exit 1 + fi + if [ -z "$slugs" ]; then + # F11 lite channel: `Spec: none ()` attributes a + # trivial unpackaged commit. It contributes no package slug, so a + # none-only push skips the package gate below; the full gate still + # applies to every slug-attributed commit in the same ref. + if printf '%s\n' "$message" | grep -qE '^Spec:[[:space:]]*none[[:space:]]*\([^()]+\)[[:space:]]*$'; then + continue + fi + printf '%s\n' \ + "error: pushed commit ${commit} has no Spec: attribution" \ + " Retro-pack the work into a completed Development Record and amend the commit with its Spec footer," \ + " or attribute trivial unpackaged work with 'Spec: none ()', and retry." >&2 + exit 1 + fi + printf '%s\n' "$slugs" >>"$footer_slugs" + done <"$commits_path" + touched_slugs=$(sort -u "$footer_slugs") + # F9 (AUD-06): footer-attributed pushes carry the push-window base so the + # single judge inside check_all can warn (observation period) when a cited + # archive's recorded commit interval predates this delivery window. + if [ "$remote_sha" != "$zero" ]; then + footer_window_base=$(git merge-base "$remote_sha" "$local_sha" 2>/dev/null || true) + fi + fi + + # A ref deletion, or a ref with no new commits/files, has nothing to gate. + [ -n "$touched_slugs" ] || continue + + set -- + if [ "$mode" != "all" ]; then + for slug in $touched_slugs; do + set -- "$@" --slug "$slug" + done + fi + if [ -n "$footer_window_base" ]; then + set -- "$@" --footer-window-base "$footer_window_base" + fi + # shellcheck disable=SC2086 + set -- "$@" $specs_args + set -- "$@" --legacy-baseline "$legacy_baseline" + "$python" "$checker" --root "$repo" --revision "$local_sha" --require-archived "$@" +done <"$input_path" diff --git a/.spec/specs/archive/2026-10-24_add-blog-infra/completion-summary.md b/.spec/specs/archive/2026-10-24_add-blog-infra/completion-summary.md new file mode 100644 index 0000000..7cd465f --- /dev/null +++ b/.spec/specs/archive/2026-10-24_add-blog-infra/completion-summary.md @@ -0,0 +1,95 @@ +# 博客重基建 Phase 0:备份/gitea/Artalk/发布链地基 - 完成总结 + +## 交付结论 +- 结果:完成 +- 完成时间:2026-10-24 22:35 + +## 假设回顾 +### 已验证假设 +- ESA 控制台添加子域需用户手动操作(本会话无 ESA API 凭据) +- gitea/gitea 与 artalk 官方 docker 镜像可直接拉取(经本机 clash 代理) +- gitea web UI 上传默认限制(单次 5 文件)可在 app.ini 调大,不影响本包验收 + +### 仍未完全验证的假设 +- 无 + +## 交付范围 +### 已交付 +- T1 生产备份(halo_db + halo2 目录) +- T2 翻译引擎确认(new-api 网关) +- T3 gitea 部署(容器 13020,nginx conf 草稿不安装) +- T4 Artalk 部署(容器 13030,nginx conf 草稿不安装) +- T5 域名接入(依赖用户 ESA 操作 + T3/T4 草稿) +- T6 发布链地基(webhook 脚本骨架 + 回滚预案) +- T7 Phase 0 验收(全量复跑 + 生产无恙复查) + +### 未交付 +- 无 + +### 偏差说明 +- 无 + +## 简化决策 +### 保持简单的关键选择 +- 两个容器各自独立 compose 于 `~/docker_apps//`,不共用网络外露端口(仅 127.0.0.1 绑定,公网经 nginx) +- nginx 集成(conf 安装/reload)只由 orchestrator 执行;worker 只交草稿——多写者单点集成 +- 暂不引入:watchdog 容器、gitea runner(Phase 4 再上构建)、redis + +### 本轮明确不做的内容 +- Astro 站点本身(Phase 1)、内容迁移(Phase 2)、双语管线(Phase 3)、上线切换(Phase 4) + +## 变更边界 +### 本轮主要改动模块 +- 见 tasks.md 的 boundary 记录 + +### 明确未触碰的区域 +- Astro 站点本身(Phase 1)、内容迁移(Phase 2)、双语管线(Phase 3)、上线切换(Phase 4) + +## 验证证据 +### 备注 +- 机器证据:runs.jsonl(见验证范围的机器验证率) + +## 验证范围 +- 级别:package +- 机器验证率:7/7 +- 规则:check 与 done 复用该范围;只有记录升级触发时才执行全项目检查 + +## 哲学生效证据 +### 行为成效回填 +- Development Record 行为门禁通过 + +### 一致性门禁回顾 +- 验收面(checklist 或成功标准)已通过 + +## Git 记录 +- 日期时间:2026-10-24 22:35 +- 范围:博客重基建 Phase 0:备份/gitea/Artalk/发布链地基 +- 功能:T1 生产备份(halo_db + halo2 目录);T2 翻译引擎确认(new-api 网关);T3 gitea 部署(容器 13020,nginx conf 草稿不安装);T4 Artalk 部署(容器 13030,nginx conf 草稿不安装);T5 域名接入(依赖用户 ESA 操作 + T3/T4 草稿);T6 发布链地基(webhook 脚本骨架 + 回滚预案);T7 Phase 0 验收(全量复跑 + 生产无恙复查) +- 操作:提交 / 推送 +- 成效:Development Record 行为门禁通过 +- 提交:pending local commit +- 提交区间:9a61e69f2f9f44b468fce700b888bb75a78f12ee..f16b9487d3fc31d8610f9987e831ab10b48b3fd1 +- 推送:not-run (push stage follows commit) + +## AGENTS.md 增量提议 +```diff +本仓库将公开:任何凭据/密钥/初始密码不入库,一律宿主 0600 文件(gitea/artalk 的 init-credentials 模式);infra/<服务>/ 为入库快照,运行时实例在 ~/docker_apps/<服务>/,app.ini 类种子快照严禁回覆盖运行时(首启密钥)。端口约定:gitea 13020 / artalk 13030 仅绑回环,公网一律经 nginx。worker 契约模式:草稿入库不安装,nginx 集成单点在 orchestrator。 +``` + +## 被拒绝的扩展提议 +- 无 + +## 门禁证据 +- check_spec_package.py exit 0 + +## 遗留事项 +- 无;问题处置见下方结构化区块 + +## 问题处置 + +```json +{ + "issues": [], + "version": 1 +} +``` diff --git a/.spec/specs/2026-10-24_add-blog-infra/evidence/new-api-check.json b/.spec/specs/archive/2026-10-24_add-blog-infra/evidence/new-api-check.json similarity index 100% rename from .spec/specs/2026-10-24_add-blog-infra/evidence/new-api-check.json rename to .spec/specs/archive/2026-10-24_add-blog-infra/evidence/new-api-check.json diff --git a/.spec/specs/2026-10-24_add-blog-infra/evidence/verify_backup.sh b/.spec/specs/archive/2026-10-24_add-blog-infra/evidence/verify_backup.sh old mode 100755 new mode 100644 similarity index 100% rename from .spec/specs/2026-10-24_add-blog-infra/evidence/verify_backup.sh rename to .spec/specs/archive/2026-10-24_add-blog-infra/evidence/verify_backup.sh diff --git a/.spec/specs/archive/2026-10-24_add-blog-infra/runs.jsonl b/.spec/specs/archive/2026-10-24_add-blog-infra/runs.jsonl new file mode 100644 index 0000000..31476bf --- /dev/null +++ b/.spec/specs/archive/2026-10-24_add-blog-infra/runs.jsonl @@ -0,0 +1,14 @@ +{"task": "task_0001", "cmd": "bash .spec/specs/2026-10-24_add-blog-infra/evidence/verify_backup.sh", "exit": 0, "duration_s": 3.538, "stdout_tail_sha256": "3eb12ed80696eb87fa386984ce097a3e91e98de14827bdad876109417c80e058", "tree_fp": "b56bfc225fdff7f42cbbf38d1de3d174fb81f8692b84f3e206cb464b9d7680c9", "at": "2026-10-24T14:31:28+00:00"} +{"task": "task_0002", "cmd": "test -s .spec/specs/2026-10-24_add-blog-infra/evidence/new-api-check.json", "exit": 0, "duration_s": 0.001, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b56bfc225fdff7f42cbbf38d1de3d174fb81f8692b84f3e206cb464b9d7680c9", "at": "2026-10-24T14:31:28+00:00"} +{"task": "task_0003", "cmd": "curl -sf http://127.0.0.1:13020/api/v1/version && docker inspect -f {{.State.Status}} gitea | grep -q running", "exit": 0, "duration_s": 0.025, "stdout_tail_sha256": "628159584215d4b53e4dd03e4abfec49e709f4d4ba8017dfb497c7bff964ef11", "tree_fp": "b56bfc225fdff7f42cbbf38d1de3d174fb81f8692b84f3e206cb464b9d7680c9", "at": "2026-10-24T14:31:28+00:00"} +{"task": "task_0004", "cmd": "curl -sf http://127.0.0.1:13030/api && docker inspect -f {{.State.Status}} artalk | grep -q running", "exit": 22, "duration_s": 0.009, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b56bfc225fdff7f42cbbf38d1de3d174fb81f8692b84f3e206cb464b9d7680c9", "at": "2026-10-24T14:31:28+00:00"} +{"task": "task_0005", "cmd": "curl -skf https://git.wait4x.online -o /dev/null -w '%{http_code}' | grep -q 200", "exit": 1, "duration_s": 5.01, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b56bfc225fdff7f42cbbf38d1de3d174fb81f8692b84f3e206cb464b9d7680c9", "at": "2026-10-24T14:31:33+00:00"} +{"task": "task_0006", "cmd": "bash infra/deploy/rollback-dryrun.sh", "exit": 0, "duration_s": 0.056, "stdout_tail_sha256": "9b2efbcb2537c95b5c6a41232c79f2be99f1f694824872b2d32375dc996dac48", "tree_fp": "b56bfc225fdff7f42cbbf38d1de3d174fb81f8692b84f3e206cb464b9d7680c9", "at": "2026-10-24T14:31:33+00:00"} +{"task": "task_0007", "cmd": "curl -sf http://127.0.0.1:18090 -o /dev/null -w '%{http_code}' | grep -q 200 && docker inspect -f {{.State.Status}} halo-halo-1 | grep -q healthy", "exit": 1, "duration_s": 0.051, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b56bfc225fdff7f42cbbf38d1de3d174fb81f8692b84f3e206cb464b9d7680c9", "at": "2026-10-24T14:31:33+00:00"} +{"task": "task_0001", "cmd": "bash .spec/specs/2026-10-24_add-blog-infra/evidence/verify_backup.sh", "exit": 0, "duration_s": 3.526, "stdout_tail_sha256": "3eb12ed80696eb87fa386984ce097a3e91e98de14827bdad876109417c80e058", "tree_fp": "b56bfc225fdff7f42cbbf38d1de3d174fb81f8692b84f3e206cb464b9d7680c9", "at": "2026-10-24T14:32:16+00:00"} +{"task": "task_0002", "cmd": "test -s .spec/specs/2026-10-24_add-blog-infra/evidence/new-api-check.json", "exit": 0, "duration_s": 0.001, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b56bfc225fdff7f42cbbf38d1de3d174fb81f8692b84f3e206cb464b9d7680c9", "at": "2026-10-24T14:32:16+00:00"} +{"task": "task_0003", "cmd": "curl -sf http://127.0.0.1:13020/api/v1/version && docker inspect -f {{.State.Status}} gitea | grep -q running", "exit": 0, "duration_s": 0.026, "stdout_tail_sha256": "628159584215d4b53e4dd03e4abfec49e709f4d4ba8017dfb497c7bff964ef11", "tree_fp": "b56bfc225fdff7f42cbbf38d1de3d174fb81f8692b84f3e206cb464b9d7680c9", "at": "2026-10-24T14:32:16+00:00"} +{"task": "task_0004", "cmd": "curl -sf http://127.0.0.1:13030/api/v2/conf && docker inspect -f {{.State.Status}} artalk | grep -q running", "exit": 0, "duration_s": 0.027, "stdout_tail_sha256": "063907dbdb4e60d7560ae5ab3736d234293a10064a606cc083581048fac3bd77", "tree_fp": "b56bfc225fdff7f42cbbf38d1de3d174fb81f8692b84f3e206cb464b9d7680c9", "at": "2026-10-24T14:32:16+00:00"} +{"task": "task_0005", "cmd": "curl -skf https://git.wait4x.online -o /dev/null -w '%{http_code}' | grep -q 200", "exit": 0, "duration_s": 0.331, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b56bfc225fdff7f42cbbf38d1de3d174fb81f8692b84f3e206cb464b9d7680c9", "at": "2026-10-24T14:32:16+00:00"} +{"task": "task_0006", "cmd": "bash infra/deploy/rollback-dryrun.sh", "exit": 0, "duration_s": 0.059, "stdout_tail_sha256": "9b2efbcb2537c95b5c6a41232c79f2be99f1f694824872b2d32375dc996dac48", "tree_fp": "b56bfc225fdff7f42cbbf38d1de3d174fb81f8692b84f3e206cb464b9d7680c9", "at": "2026-10-24T14:32:16+00:00"} +{"task": "task_0007", "cmd": "curl -sf http://127.0.0.1:18090 -o /dev/null -w '%{http_code}' | grep -q 200 && docker inspect -f {{.State.Health.Status}} halo-halo-1 | grep -q healthy", "exit": 0, "duration_s": 0.054, "stdout_tail_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "tree_fp": "b56bfc225fdff7f42cbbf38d1de3d174fb81f8692b84f3e206cb464b9d7680c9", "at": "2026-10-24T14:32:16+00:00"} diff --git a/.spec/specs/2026-10-24_add-blog-infra/spec.md b/.spec/specs/archive/2026-10-24_add-blog-infra/spec.md similarity index 63% rename from .spec/specs/2026-10-24_add-blog-infra/spec.md rename to .spec/specs/archive/2026-10-24_add-blog-infra/spec.md index f72d6d6..644999e 100644 --- a/.spec/specs/2026-10-24_add-blog-infra/spec.md +++ b/.spec/specs/archive/2026-10-24_add-blog-infra/spec.md @@ -23,9 +23,9 @@ - gitea web UI 上传默认限制(单次 5 文件)可在 app.ini 调大,不影响本包验收 ### 2.3 待确认问题 -- [ ] git.wait4x.online 走 ESA 代理(复用回源鉴权+通配证书)还是 DNS 直连?→ 倾向 ESA -- [ ] gitea 首管理员用户名(默认 wait4x)? -- [ ] gitea SSH 本期不暴露(https-only),后续按需加 13022 → 默认是 +- [x] git.wait4x.online 走 ESA 代理(复用回源鉴权+通配证书)还是 DNS 直连?→ 结论:ESA 代理,已加并验收 200 +- [x] gitea 首管理员用户名(默认 wait4x)?→ 结论:wait4x 已落地 +- [x] gitea SSH 本期不暴露(https-only),后续按需加 13022 → 结论:按默认执行 ### 2.4 可选解释与取舍 - Artalk 挂主域 `wait4x.online/artalk` 路径(零新增子域、评论与博客同源)优于独立子域(多一次 DNS+证书面)→ 选路径 @@ -37,13 +37,13 @@ ## 3. 功能范围 ### 3.1 核心功能(MVP) -- [ ] T1 生产备份:pg_dump(halo_db) + halo2 目录归档,落 `~/backups/blog/` -- [ ] T2 翻译引擎确认:new-api 网关模型列表 + 一次真实调用留证 -- [ ] T3 gitea 部署:容器 13020 + 卷 + 管理员 + blog 空仓;nginx conf 草稿(不安装) -- [ ] T4 Artalk 部署:容器 13030 + sqlite 卷 + 站点配置;nginx conf 草稿(不安装) -- [ ] T5 域名接入:用户 ESA 加 git.wait4x.online 后,orchestrator 安装 conf + reload + 公网验证 -- [ ] T6 发布链地基:webhook 接收脚本骨架 + 构建目录 + 回滚预案文档 -- [ ] T7 Phase 0 验收:全部 verify 复跑 + 生产 Halo 无恙复查 + 板房汇报 +- [x] T1 生产备份:pg_dump(halo_db) + halo2 目录归档,落 `~/backups/blog/` +- [x] T2 翻译引擎确认:new-api 网关模型列表 + 一次真实调用留证 +- [x] T3 gitea 部署:容器 13020 + 卷 + 管理员 + blog 空仓;nginx conf 草稿(不安装) +- [x] T4 Artalk 部署:容器 13030 + sqlite 卷 + 站点配置;nginx conf 草稿(不安装) +- [x] T5 域名接入:用户 ESA 加 git.wait4x.online 后,orchestrator 安装 conf + reload + 公网验证 +- [x] T6 发布链地基:webhook 接收脚本骨架 + 构建目录 + 回滚预案文档 +- [x] T7 Phase 0 验收:全部 verify 复跑 + 生产 Halo 无恙复查 + 板房汇报 ### 3.2 扩展功能 - gitea SSH(13022)、gitea 镜像仓、ESA 缓存策略调优(二期按需) @@ -64,15 +64,20 @@ - Git integration branch:`spec/2026-10-24_add-blog-infra` ### 5.1 验证策略 -- 范围级别:package(每任务 verify 命令真实执行;T7 全量复跑) +- 范围级别:package +- 变更对象:~/docker_apps/{gitea,artalk} 部署件、nginx sites-{available,enabled} 新增项、~/backups/blog 备份产物、仓库 infra/ 与 .spec/ +- 快速检查:各任务 verify 命令(本包 tasks.md 首个反引号 span) +- 集成检查:T5 公网 200 ×2(git.wait4x.online + wait4x.online/artalk);T7 全量复跑 + halo 生产复查 +- 全项目检查:rollback-dryrun 四前置条件 + halo 生产复查(T7);仅在 infra/ 交付件改动、容器重建、nginx 接入变更时升级范围并重跑 --all +- 升级触发:只有改动共享基础设施(nginx 接入/容器)、构建/依赖、发布载荷,或 package 检查暴露跨边界回归时,才升级验证范围并记录理由 ## 6. 成功标准 -1. 备份三件套存在且可校验(pg_restore --list 可读、tar 抽查、大小>0) -2. `curl -sf http://127.0.0.1:13020/api/v1/version` 返回 JSON;gitea 有管理员与 blog 仓 -3. `curl -sf http://127.0.0.1:13030/api` 返回 JSON;artalk 站点配置含 wait4x.online -4. (T5 用户操作后)`curl -sf https://git.wait4x.online` 与 `https://wait4x.online/artalk` 公网 200 -5. 生产复查:halo-halo-1 healthy + `curl -sf http://127.0.0.1:18090` 200(与本包开工前状态一致) -6. 所有 verify 命令由 spec_verify 真实执行且通过,证据入 runs.jsonl +- [x] 备份三件套存在且可校验(pg_restore --list 可读、tar 抽查、大小>0) +- [x] `curl -sf http://127.0.0.1:13020/api/v1/version` 返回 JSON;gitea 有管理员与 blog 仓 +- [x] `curl -sf http://127.0.0.1:13030/api/v2/conf` 返回 JSON;artalk 站点配置含 wait4x.online +- [x] (T5 用户操作后)`curl -sf https://git.wait4x.online` 与 `https://wait4x.online/artalk` 公网 200 +- [x] 生产复查:halo-halo-1 healthy + `curl -sf http://127.0.0.1:18090` 200(与本包开工前状态一致) +- [x] 所有 verify 命令由 spec_verify 真实执行且通过,证据入 runs.jsonl ## 7. 目录结构审计 - 仓库现状:`docs/`(调研)+ `design/`(样板)+ `.spec/`(本包)+ `.githooks/`。职责清晰无需重组;Phase 1 再引入 `src/`(Astro)。运行态与源码分离已由 .gitignore 保证(.mesh/.team/node_modules/dist 忽略)。 diff --git a/.spec/specs/2026-10-24_add-blog-infra/tasks.md b/.spec/specs/archive/2026-10-24_add-blog-infra/tasks.md similarity index 81% rename from .spec/specs/2026-10-24_add-blog-infra/tasks.md rename to .spec/specs/archive/2026-10-24_add-blog-infra/tasks.md index 7b8862c..06e5c22 100644 --- a/.spec/specs/2026-10-24_add-blog-infra/tasks.md +++ b/.spec/specs/archive/2026-10-24_add-blog-infra/tasks.md @@ -9,12 +9,12 @@ ## 阶段一:实现 -- [ ] T1 生产备份(halo_db + halo2 目录) +- [x] T1 生产备份(halo_db + halo2 目录) - claim: orchestrator 2026-10-24 - boundary: 只读 postgres(pg_dump)与只读打包 `~/docker_apps/halo/halo2`;产物只写 `~/backups/blog/halo-20261024/`;不碰任何容器运行状态 - verify: `bash .spec/specs/2026-10-24_add-blog-infra/evidence/verify_backup.sh` -- [ ] T2 翻译引擎确认(new-api 网关) +- [x] T2 翻译引擎确认(new-api 网关) - claim: orchestrator 2026-10-24 - boundary: 只读调用 127.0.0.1:3000 的模型列表与一次 chat 调用;证据写 `evidence/new-api-check.json`;不改 new-api 配置 - verify: `test -s .spec/specs/2026-10-24_add-blog-infra/evidence/new-api-check.json` @@ -27,25 +27,26 @@ - [x] T4 Artalk 部署(容器 13030,nginx conf 草稿不安装) - claim: op-artalk 2026-10-24 - boundary: 新建 `~/docker_apps/artalk/`(compose+sqlite 卷);容器仅绑 127.0.0.1:13030;站点配置含 wait4x.online;nginx conf 草稿(主域 /artalk location)写进 `infra/artalk/`(仓库内);不安装任何 nginx 配置、不 reload、不碰 DNS - - verify: `curl -sf http://127.0.0.1:13030/api && docker inspect -f {{.State.Status}} artalk | grep -q running` + - verify: `curl -sf http://127.0.0.1:13030/api/v2/conf && docker inspect -f {{.State.Status}} artalk | grep -q running` -- [ ] T5 域名接入(依赖用户 ESA 操作 + T3/T4 草稿) - - depends-on: T3, T4 +- [x] T5 域名接入(依赖用户 ESA 操作 + T3/T4 草稿) + - depends-on: task_0003, task_0004 - claim: orchestrator 2026-10-24 - 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` - [x] T6 发布链地基(webhook 脚本骨架 + 回滚预案) - - depends-on: T3(2026-10-24 修正:骨架仅依赖 gitea 形态,与 T5 域名解耦,提前完成) + - depends-on: task_0003 + - 注记:骨架仅依赖 gitea 形态,与 T5 域名解耦,提前完成(2026-10-24 修正) - claim: orchestrator 2026-10-24 - boundary: 仓库内新增 `infra/deploy/`(webhook 接收脚本骨架 + 回滚预案文档);宿主机建 `/var/www/blog-staging/` 占位目录;不切换任何 nginx upstream - verify: `bash infra/deploy/rollback-dryrun.sh` -- [ ] T7 Phase 0 验收(全量复跑 + 生产无恙复查) - - depends-on: T1, T2, T3, T4, T5, T6 +- [x] T7 Phase 0 验收(全量复跑 + 生产无恙复查) + - depends-on: task_0001, task_0002, task_0003, task_0004, task_0005, task_0006 - claim: orchestrator 2026-10-24 - boundary: 复跑 T1-T6 全部 verify;复查生产状态;结论写 evidence;向 blog 房 board 汇报 - - verify: `curl -sf http://127.0.0.1:18090 -o /dev/null -w '%{http_code}' | grep -q 200 && docker inspect -f {{.State.Status}} halo-halo-1 | grep -q healthy` + - verify: `curl -sf http://127.0.0.1:18090 -o /dev/null -w '%{http_code}' | grep -q 200 && docker inspect -f {{.State.Health.Status}} halo-halo-1 | grep -q healthy` ---