chore(spec): 归档 add-blog-infra——Phase 0 七任务收敛交付
- 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
This commit is contained in:
1 parent
f16b9487d3
commit
0f7c2dfe5b
8 files changed
+412
-26
No files matched your search
Executable
+79
@@ -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 "$@"
|
||||||
Executable
+192
@@ -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 (<reason>)' in the commit footer and retry." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -z "$slugs" ]; then
|
||||||
|
# F11 lite channel: `Spec: none (<non-empty reason>)` 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: <slug> 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 (<reason>)', 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"
|
||||||
@@ -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/<name>/`,不共用网络外露端口(仅 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
|
||||||
|
}
|
||||||
|
```
|
||||||
File renamed without changes.
Executable → Regular
File renamed without changes.
@@ -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"}
|
||||||
+22
-17
@@ -23,9 +23,9 @@
|
|||||||
- gitea web UI 上传默认限制(单次 5 文件)可在 app.ini 调大,不影响本包验收
|
- gitea web UI 上传默认限制(单次 5 文件)可在 app.ini 调大,不影响本包验收
|
||||||
|
|
||||||
### 2.3 待确认问题
|
### 2.3 待确认问题
|
||||||
- [ ] git.wait4x.online 走 ESA 代理(复用回源鉴权+通配证书)还是 DNS 直连?→ 倾向 ESA
|
- [x] git.wait4x.online 走 ESA 代理(复用回源鉴权+通配证书)还是 DNS 直连?→ 结论:ESA 代理,已加并验收 200
|
||||||
- [ ] gitea 首管理员用户名(默认 wait4x)?
|
- [x] gitea 首管理员用户名(默认 wait4x)?→ 结论:wait4x 已落地
|
||||||
- [ ] gitea SSH 本期不暴露(https-only),后续按需加 13022 → 默认是
|
- [x] gitea SSH 本期不暴露(https-only),后续按需加 13022 → 结论:按默认执行
|
||||||
|
|
||||||
### 2.4 可选解释与取舍
|
### 2.4 可选解释与取舍
|
||||||
- Artalk 挂主域 `wait4x.online/artalk` 路径(零新增子域、评论与博客同源)优于独立子域(多一次 DNS+证书面)→ 选路径
|
- Artalk 挂主域 `wait4x.online/artalk` 路径(零新增子域、评论与博客同源)优于独立子域(多一次 DNS+证书面)→ 选路径
|
||||||
@@ -37,13 +37,13 @@
|
|||||||
## 3. 功能范围
|
## 3. 功能范围
|
||||||
|
|
||||||
### 3.1 核心功能(MVP)
|
### 3.1 核心功能(MVP)
|
||||||
- [ ] T1 生产备份:pg_dump(halo_db) + halo2 目录归档,落 `~/backups/blog/`
|
- [x] T1 生产备份:pg_dump(halo_db) + halo2 目录归档,落 `~/backups/blog/`
|
||||||
- [ ] T2 翻译引擎确认:new-api 网关模型列表 + 一次真实调用留证
|
- [x] T2 翻译引擎确认:new-api 网关模型列表 + 一次真实调用留证
|
||||||
- [ ] T3 gitea 部署:容器 13020 + 卷 + 管理员 + blog 空仓;nginx conf 草稿(不安装)
|
- [x] T3 gitea 部署:容器 13020 + 卷 + 管理员 + blog 空仓;nginx conf 草稿(不安装)
|
||||||
- [ ] T4 Artalk 部署:容器 13030 + sqlite 卷 + 站点配置;nginx conf 草稿(不安装)
|
- [x] T4 Artalk 部署:容器 13030 + sqlite 卷 + 站点配置;nginx conf 草稿(不安装)
|
||||||
- [ ] T5 域名接入:用户 ESA 加 git.wait4x.online 后,orchestrator 安装 conf + reload + 公网验证
|
- [x] T5 域名接入:用户 ESA 加 git.wait4x.online 后,orchestrator 安装 conf + reload + 公网验证
|
||||||
- [ ] T6 发布链地基:webhook 接收脚本骨架 + 构建目录 + 回滚预案文档
|
- [x] T6 发布链地基:webhook 接收脚本骨架 + 构建目录 + 回滚预案文档
|
||||||
- [ ] T7 Phase 0 验收:全部 verify 复跑 + 生产 Halo 无恙复查 + 板房汇报
|
- [x] T7 Phase 0 验收:全部 verify 复跑 + 生产 Halo 无恙复查 + 板房汇报
|
||||||
|
|
||||||
### 3.2 扩展功能
|
### 3.2 扩展功能
|
||||||
- gitea SSH(13022)、gitea 镜像仓、ESA 缓存策略调优(二期按需)
|
- gitea SSH(13022)、gitea 镜像仓、ESA 缓存策略调优(二期按需)
|
||||||
@@ -64,15 +64,20 @@
|
|||||||
- Git integration branch:`spec/2026-10-24_add-blog-infra`
|
- Git integration branch:`spec/2026-10-24_add-blog-infra`
|
||||||
|
|
||||||
### 5.1 验证策略
|
### 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. 成功标准
|
## 6. 成功标准
|
||||||
1. 备份三件套存在且可校验(pg_restore --list 可读、tar 抽查、大小>0)
|
- [x] 备份三件套存在且可校验(pg_restore --list 可读、tar 抽查、大小>0)
|
||||||
2. `curl -sf http://127.0.0.1:13020/api/v1/version` 返回 JSON;gitea 有管理员与 blog 仓
|
- [x] `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
|
- [x] `curl -sf http://127.0.0.1:13030/api/v2/conf` 返回 JSON;artalk 站点配置含 wait4x.online
|
||||||
4. (T5 用户操作后)`curl -sf https://git.wait4x.online` 与 `https://wait4x.online/artalk` 公网 200
|
- [x] (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(与本包开工前状态一致)
|
- [x] 生产复查:halo-halo-1 healthy + `curl -sf http://127.0.0.1:18090` 200(与本包开工前状态一致)
|
||||||
6. 所有 verify 命令由 spec_verify 真实执行且通过,证据入 runs.jsonl
|
- [x] 所有 verify 命令由 spec_verify 真实执行且通过,证据入 runs.jsonl
|
||||||
|
|
||||||
## 7. 目录结构审计
|
## 7. 目录结构审计
|
||||||
- 仓库现状:`docs/`(调研)+ `design/`(样板)+ `.spec/`(本包)+ `.githooks/`。职责清晰无需重组;Phase 1 再引入 `src/`(Astro)。运行态与源码分离已由 .gitignore 保证(.mesh/.team/node_modules/dist 忽略)。
|
- 仓库现状:`docs/`(调研)+ `design/`(样板)+ `.spec/`(本包)+ `.githooks/`。职责清晰无需重组;Phase 1 再引入 `src/`(Astro)。运行态与源码分离已由 .gitignore 保证(.mesh/.team/node_modules/dist 忽略)。
|
||||||
+10
-9
@@ -9,12 +9,12 @@
|
|||||||
|
|
||||||
## 阶段一:实现
|
## 阶段一:实现
|
||||||
|
|
||||||
- [ ] T1 生产备份(halo_db + halo2 目录)
|
- [x] T1 生产备份(halo_db + halo2 目录)
|
||||||
- claim: orchestrator 2026-10-24
|
- claim: orchestrator 2026-10-24
|
||||||
- boundary: 只读 postgres(pg_dump)与只读打包 `~/docker_apps/halo/halo2`;产物只写 `~/backups/blog/halo-20261024/`;不碰任何容器运行状态
|
- 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`
|
- 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
|
- claim: orchestrator 2026-10-24
|
||||||
- boundary: 只读调用 127.0.0.1:3000 的模型列表与一次 chat 调用;证据写 `evidence/new-api-check.json`;不改 new-api 配置
|
- 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`
|
- 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 草稿不安装)
|
- [x] T4 Artalk 部署(容器 13030,nginx conf 草稿不安装)
|
||||||
- claim: op-artalk 2026-10-24
|
- 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
|
- 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 草稿)
|
- [x] T5 域名接入(依赖用户 ESA 操作 + T3/T4 草稿)
|
||||||
- depends-on: T3, T4
|
- depends-on: task_0003, task_0004
|
||||||
- claim: orchestrator 2026-10-24
|
- claim: orchestrator 2026-10-24
|
||||||
- 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`
|
||||||
|
|
||||||
- [x] T6 发布链地基(webhook 脚本骨架 + 回滚预案)
|
- [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
|
- 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`
|
||||||
|
|
||||||
- [ ] T7 Phase 0 验收(全量复跑 + 生产无恙复查)
|
- [x] T7 Phase 0 验收(全量复跑 + 生产无恙复查)
|
||||||
- depends-on: T1, T2, T3, T4, T5, T6
|
- depends-on: task_0001, task_0002, task_0003, task_0004, task_0005, task_0006
|
||||||
- claim: orchestrator 2026-10-24
|
- claim: orchestrator 2026-10-24
|
||||||
- boundary: 复跑 T1-T6 全部 verify;复查生产状态;结论写 evidence;向 blog 房 board 汇报
|
- 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`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Reference in new issue
Block a user