- 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]>
71 lines
2.3 KiB
Bash
Executable File
71 lines
2.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# 容器入口。
|
|
# 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
|
|
|
|
"$BIN" --role api &
|
|
api_pid=$!
|
|
"$BIN" --role worker &
|
|
worker_pid=$!
|
|
|
|
stop_all() {
|
|
kill -TERM "$api_pid" "$worker_pid" 2>/dev/null || true
|
|
wait "$api_pid" "$worker_pid" 2>/dev/null || true
|
|
}
|
|
|
|
on_signal() {
|
|
echo "entrypoint: 收到 $1,停止 api 与 worker" >&2
|
|
stop_all
|
|
exit 0
|
|
}
|
|
trap 'on_signal SIGTERM' TERM
|
|
trap 'on_signal SIGINT' INT
|
|
|
|
set +e
|
|
wait -n "$api_pid" "$worker_pid"
|
|
code=$?
|
|
set -e
|
|
echo "entrypoint: 有进程退出(code=$code),停止其余进程" >&2
|
|
stop_all
|
|
# 常驻进程不应自行正常退出;按失败处理以触发重启
|
|
if [ "$code" -eq 0 ]; then code=1; fi
|
|
exit "$code"
|