diff --git a/scripts/database-backup-to-oss.mjs b/scripts/database-backup-to-oss.mjs index c64e8657f..148267ac7 100644 --- a/scripts/database-backup-to-oss.mjs +++ b/scripts/database-backup-to-oss.mjs @@ -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); }); } diff --git a/scripts/deploy/production-stdb-publish.sh b/scripts/deploy/production-stdb-publish.sh index f34492476..c102e45ec 100644 --- a/scripts/deploy/production-stdb-publish.sh +++ b/scripts/deploy/production-stdb-publish.sh @@ -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=(