构建、部署与验收文档:Makefile、冒烟脚本、systemd/Caddy、决策与验收报告

- Makefile:web / sync-assets(go:embed 约束拷贝)/ build / linux /
  test / smoke / dev / vulncheck;版本信息 ldflags 注入
- scripts/smoke.sh:构建冒烟(§8.3-6)——起服务断言 SPA 资源 200 +
  正确 MIME、meta 注入、安全头、私有不可见(19 项断言)
- deploy:pure-note.service(沙箱加固)、每日 gc+backup 的
  maint service/timer(03:00 Persistent)、Caddyfile 反代示例
- docs/decisions.md:35 条实施决策留档(D1–D35)
- docs/acceptance.md:§9.3 检查单逐项证据、§13 测试组映射、
  §12 里程碑门、浏览器端到端走查记录
- README.md:快速开始、测试、部署与升级/恢复 SOP
This commit is contained in:
2026-09-08 08:15:02 +08:00
parent e9316c9169
commit 183676428e
9 changed files with 442 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
#!/usr/bin/env bash
# 构建冒烟测试(设计 §8.3-6):
# 启动二进制 → 创建公开/私有笔记 → 断言首页/深链 HTML 注入 meta、
# script/link 以 200 + 正确 MIME 加载(防「深链白屏」类回归)→ 可见性抽查。
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
BIN="$ROOT/pure-note"
TMP="$(mktemp -d)"
PORT="${SMOKE_PORT:-18099}"
BASE="http://127.0.0.1:$PORT"
FAIL=0
cleanup() { kill "$SRV_PID" 2>/dev/null || true; rm -rf "$TMP"; }
trap cleanup EXIT
ok() { echo " ✓ $1"; }
bad() { echo " ✗ $1"; FAIL=1; }
check() { if [ "$2" -eq 0 ]; then ok "$1"; else bad "$1"; fi }
echo "[smoke] 初始化数据目录"
PN_ADMIN_PASSWORD=smoke-password-123 PN_SITE_TITLE="Smoke 站" "$BIN" init --data-dir "$TMP/data"
echo "[smoke] 启动服务 :$PORT"
"$BIN" serve --data-dir "$TMP/data" --addr "127.0.0.1:$PORT" --dev >"$TMP/serve.log" 2>&1 &
SRV_PID=$!
for _ in $(seq 1 50); do
curl -sf "$BASE/api/health" >/dev/null 2>&1 && break
sleep 0.2
done
echo "[smoke] 健康检查"
curl -sf "$BASE/api/health" | grep -q '"ok"'; check "GET /api/health 200 含 ok" $?
echo "[smoke] 登录并创建笔记"
CSRF=$(curl -sf -X POST "$BASE/api/auth/login" -H "Origin: $BASE" \
-d '{"password":"smoke-password-123"}' -c "$TMP/cookies" \
| sed 's/.*"csrf_token":"\([^"]*\)".*/\1/')
[ -n "$CSRF" ]; check "登录获得 CSRF token" $?
CREATE=$(curl -sf -X POST "$BASE/api/admin/notes" -H "Origin: $BASE" -H "X-CSRF-Token: $CSRF" \
-H 'Content-Type: application/json' -b "$TMP/cookies" \
-d '{"title":"冒烟笔记 Smoke","content":"# Hello\n\n正文","status":"public"}')
echo "$CREATE" | grep -q '"slug"'; check "创建公开笔记" $?
SLUG=$(echo "$CREATE" | sed 's/.*"slug":"\([^"]*\)".*/\1/')
curl -sf -X POST "$BASE/api/admin/notes" -H "Origin: $BASE" -H "X-CSRF-Token: $CSRF" \
-H 'Content-Type: application/json' -b "$TMP/cookies" \
-d '{"title":"私有机密","content":"秘密","status":"private"}' >/dev/null
check "创建私有笔记" $?
echo "[smoke] SPA 与 meta 注入"
HOME_HTML=$(curl -sf "$BASE/")
echo "$HOME_HTML" | grep -q '<title>Smoke 站</title>'; check "首页注入站点标题" $?
DEEP_HTML=$(curl -sf "$BASE/notes/$SLUG")
echo "$DEEP_HTML" | grep -q '<title>冒烟笔记 Smoke - Smoke 站</title>'
check "深链注入笔记标题(防白屏回归)" $?
JS=$(echo "$DEEP_HTML" | grep -o '/assets/index-[^"]*\.js' | head -1)
[ -n "$JS" ]; check "HTML 含打包 script 引用" $?
CODE=$(curl -s -o /dev/null -w '%{http_code}' "$BASE$JS" || true)
[ "$CODE" = "200" ]; check "script 资源 200(实际 $CODE)" $?
MIME=$(curl -s -o /dev/null -w '%{content_type}' "$BASE$JS" || true)
echo "$MIME" | grep -q 'javascript'; check "script MIME 为 javascript(实际 $MIME)" $?
CSS=$(echo "$DEEP_HTML" | grep -o '/assets/index-[^"]*\.css' | head -1)
CODE=$(curl -s -o /dev/null -w '%{http_code}' "$BASE$CSS" || true)
[ "$CODE" = "200" ]; check "link 样式资源 200(实际 $CODE)" $?
MIME=$(curl -s -o /dev/null -w '%{content_type}' "$BASE$CSS" || true)
echo "$MIME" | grep -q 'text/css'; check "link MIME 为 css(实际 $MIME)" $?
echo "[smoke] 安全头抽查"
H=$(curl -sI "$BASE/")
echo "$H" | grep -qi "Strict-Transport-Security"; check "HSTS" $?
echo "$H" | grep -qi "X-Frame-Options: DENY"; check "X-Frame-Options DENY" $?
echo "$H" | grep -qi "X-Content-Type-Options: nosniff"; check "nosniff" $?
echo "$H" | grep -qi "Content-Security-Policy.*script-src 'self'"; check "CSP script-src 'self'" $?
echo "[smoke] 可见性抽查(私有不可见)"
if curl -sf "$BASE/api/notes" | grep -q '私有机密'; then bad "匿名列表不含私有笔记"; else ok "匿名列表不含私有笔记"; fi
if curl -sf "$BASE/feed.xml" | grep -q '私有机密'; then bad "RSS 不含私有笔记"; else ok "RSS 不含私有笔记"; fi
CODE=$(curl -s -o /dev/null -w '%{http_code}' "$BASE/api/notes/$SLUG")
[ "$CODE" = "200" ]; check "公开笔记详情匿名 200(实际 $CODE)" $?
if [ "$FAIL" -eq 0 ]; then
echo "[smoke] 全部通过 ✓"
else
echo "[smoke] 存在失败项 ✗"
exit 1
fi