修复生产发布备份状态处理
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 3m53s
Project CI / AI game creator shell Rust shard 3/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (push) Has been cancelled

备份锁冲突时写入失败状态 JSON

发布脚本安全解析异步备份状态并保留异常文件

生产发布临时目录统一使用 ~/data/tmp
This commit is contained in:
2026-09-15 22:50:02 +08:00
parent 7015f41a8e
commit 2ef9238aa9
2 changed files with 41 additions and 4 deletions
+15
View File
@@ -4517,6 +4517,21 @@ if (
for (const line of describeError(error)) {
console.error(`[database-backup] ${line}`);
}
// 即使初始化失败(例如已有进程持有锁),也保持 --result-file 可被机器读取。
// 调用方可以检查错误载荷并跳过延后上传,避免对空的临时文件执行 JSON.parse。
try {
const parsed = parseArgs(process.argv.slice(2));
if (parsed.resultFile) {
atomicWriteJson(resolvePath(parsed.resultFile), {
uploadStatus: 'failed',
error: error instanceof Error ? error.message : String(error),
archivePath: '',
manifestPath: '',
});
}
} catch {
// 参数解析或状态文件写入失败时,不能掩盖原始备份错误。
}
process.exit(1);
});
}
+26 -4
View File
@@ -519,7 +519,9 @@ prepare_async_backup() {
restart_service_args+=(--restart-service-after genarrative-api.service)
fi
ASYNC_BACKUP_STATUS_FILE="$(mktemp /tmp/genarrative-stdb-backup-status.XXXXXX.json)"
task_tmp_dir="${HOME}/data/tmp"
mkdir -p "${task_tmp_dir}"
ASYNC_BACKUP_STATUS_FILE="$(mktemp "${task_tmp_dir}/genarrative-stdb-backup-status.XXXXXX.json")"
echo "[production-stdb-publish] publish 前生成本地冷备份,随后会异步上传 OSS"
node -- "${ASYNC_BACKUP_SCRIPT}" \
--env-file /etc/genarrative/api-server.env \
@@ -536,14 +538,32 @@ start_async_backup_upload() {
local node_binary=""
local unit_name=""
local unit_suffix=""
local backup_paths=""
if [[ -z "${ASYNC_BACKUP_STATUS_FILE}" || ! -f "${ASYNC_BACKUP_STATUS_FILE}" ]]; then
echo "[production-stdb-publish] 警告:未找到可上传的本地备份状态文件,跳过异步上传" >&2
return 0
fi
ASYNC_BACKUP_ARCHIVE="$(node -e 'const fs=require("node:fs"); const p=process.argv[1]; const o=JSON.parse(fs.readFileSync(p,"utf8")); process.stdout.write(o.archivePath || "");' "${ASYNC_BACKUP_STATUS_FILE}")"
ASYNC_BACKUP_MANIFEST="$(node -e 'const fs=require("node:fs"); const p=process.argv[1]; const o=JSON.parse(fs.readFileSync(p,"utf8")); process.stdout.write(o.manifestPath || "");' "${ASYNC_BACKUP_STATUS_FILE}")"
# 备份进程可能因数据库锁冲突在写入状态文件前退出,或只留下空/截断文件。
# 解析失败时保留状态文件并让调用方继续处理发布失败,不能再让 node JSON.parse
# 的堆栈噪声覆盖原始备份错误。
if ! backup_paths="$(node -e '
const fs = require("node:fs");
const p = process.argv[1];
let o;
try {
o = JSON.parse(fs.readFileSync(p, "utf8"));
} catch {
process.exit(2);
}
process.stdout.write(`${o.archivePath || ""}\n${o.manifestPath || ""}`);
' "${ASYNC_BACKUP_STATUS_FILE}" 2>/dev/null)"; then
echo "[production-stdb-publish] 警告:备份状态文件为空或不是有效 JSON,跳过异步上传并保留状态文件: ${ASYNC_BACKUP_STATUS_FILE}" >&2
return 1
fi
ASYNC_BACKUP_ARCHIVE="${backup_paths%%$'\n'*}"
ASYNC_BACKUP_MANIFEST="${backup_paths#*$'\n'}"
if [[ -z "${ASYNC_BACKUP_ARCHIVE}" || -z "${ASYNC_BACKUP_MANIFEST}" ]]; then
echo "[production-stdb-publish] 警告:备份状态文件缺少 archivePath 或 manifestPath,跳过异步上传" >&2
return 0
@@ -727,7 +747,9 @@ if [[ -n "${RUN_AS_USER}" && "$(id -u)" -eq 0 ]]; then
echo "[production-stdb-publish] 发布用户不存在: ${RUN_AS_USER}" >&2
exit 1
fi
PUBLISH_TMP_DIR="$(mktemp -d /tmp/genarrative-stdb-publish.XXXXXX)"
task_tmp_dir="${HOME}/data/tmp"
mkdir -p "${task_tmp_dir}"
PUBLISH_TMP_DIR="$(mktemp -d "${task_tmp_dir}/genarrative-stdb-publish.XXXXXX")"
install -m 0644 "${SOURCE_DIR}/spacetime_module.wasm" "${PUBLISH_TMP_DIR}/spacetime_module.wasm"
chown -R "${RUN_AS_USER}:${RUN_AS_USER}" "${PUBLISH_TMP_DIR}"
PUBLISH_ARGS=(