feat(ops): W4-B single-container packaging, DATA_DIR, backup/restore

- DATA_DIR (/data: agent-sessions, backups, exports, tmp); entrypoint chowns it
  to PUID/PGID then drops privileges via gosu; /app binary stays root read-only
- compile with --allow-read --allow-write so mounted library roots are writable
- preflight: DATA_DIR/session dir must be writable (fatal); enabled library
  roots and downloader path mappings checked read/write (warn + audit)
- --role export-mappings (mapping manifest JSON, --compare row counts) and
  --role read-only-on (automation.readOnlyReconcile=true + audit)
- scripts/backup.sh (pg_dump custom + mappings + agent sessions, SHA256SUMS,
  BACKUP_KEEP rotation) and scripts/restore.sh (checksum, refuses with live
  connections, psql single-transaction replay compatible with PG16, read-only
  reconcile on, row-count verification)
- compose: app + postgres only, data volume, env example with key generation
- docs: runbook deploy.md, backup-restore.md with verification record

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-27 21:27:55 +08:00
1 parent 8c6a922cfb
commit 34e8751b45
18 files changed
+918 -43

No files matched your search

+22
View File
@@ -0,0 +1,22 @@
# docker compose 用的环境变量(复制为部署目录下的 .env)。
#
# SECRET_MASTER_KEY:凭证加密主密钥,生成一次后永不更换(换了之前存的站点/下载器/模型密钥全部无法解密)。
# 生成:openssl rand -hex 32
# 必须与数据库备份分开保管(密码管理器 / 离线介质);备份里不含它,丢了它备份里的密钥也无法恢复。
SECRET_MASTER_KEY=
# 数据库密码:openssl rand -hex 16
POSTGRES_PASSWORD=
# 对外端口与绑定地址(放在反代后面时可改成 127.0.0.1)
UMT_PORT=4380
UMT_BIND=0.0.0.0
# 容器运行用户:与宿主媒体目录属主一致,例如 id -u / id -g 的输出
PUID=10001
PGID=10001
# 备份保留份数(0 = 不轮转)
BACKUP_KEEP=14
LOG_LEVEL=info
# APP_VERSION=v0.1.0
# 国内网络构建换源(可选):APT_MIRROR=mirrors.ustc.edu.cn
+35 -2
View File
@@ -1,9 +1,42 @@
#!/usr/bin/env bash
# 容器入口:迁移 → 启动自检 → api 与 worker 两个进程互相看护。
# 任一进程退出就停掉另一个并以非 0 退出,把"半死容器"变成整体退出,交给 restart 策略自愈。
# 容器入口。
# root 启动:建好 DATA_DIR 子目录、按 PUID/PGID 修正属主,再用 gosu 降权重跑自己(二进制与 /app 保持 root 只读)。
# serve(缺省):迁移 → 启动自检 → api 与 worker 两个进程互相看护。
# 任一进程退出就停掉另一个并以非 0 退出,把"半死容器"变成整体退出,交给 restart 策略自愈。
# backup / restore <备份目录> / migrate / preflight / export-mappings ...:一次性子命令(降权后执行)。
# 其它参数原样 exec(例如 bash)。
set -euo pipefail
BIN=/app/umt-server
DATA_DIR="${DATA_DIR:-/data}"
PUID="${PUID:-10001}"
PGID="${PGID:-10001}"
export DATA_DIR TMPDIR="${TMPDIR:-$DATA_DIR/tmp}"
if [ "$(id -u)" = "0" ]; then
if [ "$PUID" = "0" ]; then
echo "entrypoint: 不允许 PUID=0(生产环境禁止以 root 运行)" >&2
exit 2
fi
mkdir -p "$DATA_DIR"/{agent-sessions,backups,exports,tmp}
if [ -n "${AGENT_SESSION_DIR:-}" ]; then mkdir -p "$AGENT_SESSION_DIR"; fi
# 只在属主不对时递归 chown,避免每次启动扫全卷;媒体库/下载目录是用户挂载,不碰
for d in "$DATA_DIR" ${AGENT_SESSION_DIR:+"$AGENT_SESSION_DIR"}; do
if [ "$(stat -c %u:%g "$d")" != "$PUID:$PGID" ] || [ -n "$(find "$d" \( ! -user "$PUID" -o ! -group "$PGID" \) -print -quit)" ]; then
chown -R "$PUID:$PGID" "$d"
fi
done
exec gosu "$PUID:$PGID" "$0" "$@"
fi
cmd="${1:-serve}"
case "$cmd" in
serve) ;;
backup) shift; exec /app/scripts/backup.sh "$@" ;;
restore) shift; exec /app/scripts/restore.sh "$@" ;;
migrate | preflight | export-mappings | read-only-on) shift; exec "$BIN" --role "$cmd" "$@" ;;
*) exec "$@" ;;
esac
"$BIN" --role migrate
"$BIN" --role preflight