Files
rootandClaude Opus 5.5 b0e5d539ed feat(auth): TOTP setup endpoint, recovery codes, passkeys and master key rotation
- GET /auth/totp/setup returns the pending secret and otpauth URL (created
  on first call, reused on refresh) for the console to render as a QR code.
- Enabling TOTP issues 8 one-time recovery codes (argon2id hashes in
  admin_recovery_code); login accepts recoveryCode in place of totpCode,
  disable accepts either, and recovery_codes regenerates with the password.
- Passkeys via @simplewebauthn/server: register/list/rename/delete under
  the console session, public login options/verify that reuse the login
  rate limit and session cookies. Challenges are one-time rows in
  webauthn_challenge; RP ID and origins come from WEBAUTHN_RP_ID /
  WEBAUTHN_ORIGINS, falling back to the request host. Signature counter
  regressions are rejected. SessionInfo gains passkeyEnabled.
- --role rotate-master-key re-encrypts the secret table from
  SECRET_MASTER_KEY to SECRET_MASTER_KEY_NEW one row per transaction,
  skips rows already on the new key (resumable, swap keys to roll back),
  logs progress and audits secret.master_key_rotate. Secrets now record
  the master key fingerprint in kdf_params.kid.
- Runbook: recovery codes, passkey configuration, rotation steps and
  rollback.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 20:22:07 +08:00

74 lines
2.4 KiB
Bash
Executable File

#!/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"