修复维护模式失败路径临时文件清理
在首次创建临时文件前注册 set -u 安全的统一 EXIT 清理。 补充 marker 与公告页符号链接失败后的临时文件残留回归断言。 更新生产运维文档中的维护页临时文件安全边界。
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# 本地开发验证与生产运维
|
||||
|
||||
更新时间:`2026-08-03`
|
||||
更新时间:`2026-08-05`
|
||||
|
||||
## 标准开发流程
|
||||
|
||||
@@ -899,6 +899,6 @@ node scripts/rebind-orphan-work-owners.mjs --in <exported-migration.json> --out
|
||||
- `--dry-run`:只统计回填行数,不写文件。
|
||||
- `--placeholder-user-id`:需要时可覆盖默认占位账号 ID。
|
||||
|
||||
## 维护页目标文件安全边界(2026-08-04)
|
||||
## 维护页目标文件安全边界(2026-08-05)
|
||||
|
||||
`scripts/deploy/maintenance-on.sh` 只允许把同目录临时普通文件原子替换到普通文件或尚不存在的 `page.html` / `enabled` 目标。目标只要是符号链接(包括指向目录的链接)或目录,脚本必须在替换前失败,不能跟随链接把临时文件移入链接目标,也不能打印“已进入维护模式”。跨平台实现继续使用 POSIX `mv -f`,安全语义由替换函数的目标类型门禁保证;修改后运行 `bash -n scripts/deploy/maintenance-on.sh` 与 `npm run check:maintenance-page`。
|
||||
`scripts/deploy/maintenance-on.sh` 只允许把同目录临时普通文件原子替换到普通文件或尚不存在的 `page.html` / `enabled` 目标。目标只要是符号链接(包括指向目录的链接)或目录,脚本必须在替换前失败,不能跟随链接把临时文件移入链接目标,也不能打印“已进入维护模式”。`page_temp` 与 `marker_temp` 必须在 `set -u` 下安全初始化,清理 trap 必须在首次 `mktemp` 前生效;任一失败退出都不得在目标同级遗留 `page.html.tmp.*` 或 `enabled.tmp.*`,成功替换后则清空临时路径并解除 trap,不能误删已安装目标。跨平台实现继续使用 POSIX `mv -f`,安全语义由替换函数的目标类型门禁保证;修改后运行 `bash -n scripts/deploy/maintenance-on.sh` 与 `npm run check:maintenance-page`。
|
||||
|
||||
@@ -167,6 +167,13 @@ function validateRuntimePageLifecycle() {
|
||||
if (readdirSync(linkedPageTarget).length > 0) {
|
||||
fail('拒绝公告页符号链接后不得把临时文件移入链接目标目录。');
|
||||
}
|
||||
if (
|
||||
readdirSync(path.dirname(runtimePageFile)).some((entry) =>
|
||||
entry.startsWith(`${path.basename(runtimePageFile)}.tmp.`),
|
||||
)
|
||||
) {
|
||||
fail('公告页符号链接校验失败后不得残留 page.html.tmp.* 临时文件。');
|
||||
}
|
||||
if (existsSync(markerFile)) {
|
||||
fail('公告页符号链接校验失败时不得创建维护 marker。');
|
||||
}
|
||||
@@ -193,6 +200,13 @@ function validateRuntimePageLifecycle() {
|
||||
if (readdirSync(linkedMarkerTarget).length > 0) {
|
||||
fail('拒绝 marker 符号链接后不得把临时文件移入链接目标目录。');
|
||||
}
|
||||
if (
|
||||
readdirSync(path.dirname(markerFile)).some((entry) =>
|
||||
entry.startsWith(`${path.basename(markerFile)}.tmp.`),
|
||||
)
|
||||
) {
|
||||
fail('marker 符号链接校验失败后不得残留 enabled.tmp.* 临时文件。');
|
||||
}
|
||||
if (
|
||||
linkedMarkerEnable.stdout.includes('已进入维护模式') ||
|
||||
linkedMarkerEnable.stderr.includes('已进入维护模式')
|
||||
|
||||
@@ -6,6 +6,19 @@ MAINTENANCE_FILE="${GENARRATIVE_MAINTENANCE_FILE:-/var/lib/genarrative/maintenan
|
||||
MAINTENANCE_PAGE_FILE="${GENARRATIVE_MAINTENANCE_PAGE_FILE:-/var/lib/genarrative/maintenance/page.html}"
|
||||
PAGE_SOURCE=""
|
||||
REASON_PARTS=()
|
||||
page_temp=""
|
||||
marker_temp=""
|
||||
|
||||
cleanup_temps() {
|
||||
if [[ -n "${page_temp}" ]]; then
|
||||
rm -f -- "${page_temp}"
|
||||
fi
|
||||
if [[ -n "${marker_temp}" ]]; then
|
||||
rm -f -- "${marker_temp}"
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup_temps EXIT
|
||||
|
||||
replace_file_atomically() {
|
||||
local source_file="$1"
|
||||
@@ -56,7 +69,6 @@ mkdir -p "$(dirname "${MAINTENANCE_FILE}")" "$(dirname "${MAINTENANCE_PAGE_FILE}
|
||||
|
||||
if [[ -n "${PAGE_SOURCE}" ]]; then
|
||||
page_temp="$(mktemp "${MAINTENANCE_PAGE_FILE}.tmp.XXXXXX")"
|
||||
trap 'rm -f "${page_temp:-}" "${marker_temp:-}"' EXIT
|
||||
install -m 0644 -- "${PAGE_SOURCE}" "${page_temp}"
|
||||
replace_file_atomically "${page_temp}" "${MAINTENANCE_PAGE_FILE}"
|
||||
page_temp=""
|
||||
|
||||
Reference in New Issue
Block a user