#!/usr/bin/env bash # 容器入口。 # root 启动:建好 DATA_DIR 子目录、按 PUID/PGID 修正属主,再用 gosu 降权重跑自己(二进制与 /app 保持 root 只读)。 # serve(缺省):迁移 → 启动自检 → api、worker(io 组)、worker:agent(Agent 会话)三个进程互相看护。 # 任一进程退出就停掉另一个并以非 0 退出,把"半死容器"变成整体退出,交给 restart 策略自愈。 # backup / restore <备份目录> / migrate / preflight / export-mappings / rotate-master-key ...:一次性子命令(降权后执行)。 # 其它参数原样 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 | rotate-master-key) shift; exec "$BIN" --role "$cmd" "$@" ;; *) exec "$@" ;; esac "$BIN" --role migrate "$BIN" --role preflight "$BIN" --role api & api_pid=$! "$BIN" --role worker & worker_pid=$! "$BIN" --role worker:agent & agent_pid=$! pids=("$api_pid" "$worker_pid" "$agent_pid") stop_all() { kill -TERM "${pids[@]}" 2>/dev/null || true wait "${pids[@]}" 2>/dev/null || true } on_signal() { echo "entrypoint: 收到 $1,停止 api / worker / worker:agent" >&2 stop_all exit 0 } trap 'on_signal SIGTERM' TERM trap 'on_signal SIGINT' INT set +e wait -n "${pids[@]}" code=$? set -e echo "entrypoint: 有进程退出(code=$code),停止其余进程" >&2 stop_all # 常驻进程不应自行正常退出;按失败处理以触发重启 if [ "$code" -eq 0 ]; then code=1; fi exit "$code"