#!/bin/sh set -eu PATH="/usr/lib/postgresql16/bin:$PATH" log() { printf '%s\n' "$*" } DATA_ROOT="${DATA_ROOT:-/data}" APP_DATA_DIR="${APP_DATA_DIR:-$DATA_ROOT/data}" POSTGRES_DATA_DIR="${POSTGRES_DATA_DIR:-$DATA_ROOT/postgres}" REDIS_DATA_DIR="${REDIS_DATA_DIR:-$DATA_ROOT/redis}" RUNTIME_DIR="${RUNTIME_DIR:-$DATA_ROOT/run}" LOG_DIR="${LOG_DIR:-$DATA_ROOT/logs}" POSTGRES_USER="${POSTGRES_USER:-postgres}" POSTGRES_DB="${POSTGRES_DB:-sub2api}" POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-}" REDIS_PASSWORD="${REDIS_PASSWORD:-}" SERVER_PORT="${SERVER_PORT:-8080}" BACKUP_INTERVAL_SECONDS="${BACKUP_INTERVAL_SECONDS:-1800}" if [ -z "$POSTGRES_PASSWORD" ]; then log "[startup] POSTGRES_PASSWORD is required" exit 1 fi if [ -z "${JWT_SECRET:-}" ]; then log "[startup] JWT_SECRET is required" exit 1 fi if [ -z "${TOTP_ENCRYPTION_KEY:-}" ]; then log "[startup] TOTP_ENCRYPTION_KEY is required" exit 1 fi mkdir -p "$APP_DATA_DIR" "$POSTGRES_DATA_DIR" "$REDIS_DATA_DIR" "$RUNTIME_DIR/postgresql" "$LOG_DIR" chown -R sub2api:sub2api "$DATA_ROOT" /app chmod 700 "$POSTGRES_DATA_DIR" restore_if_needed() { if [ -z "${HF_BACKUP_REPO:-}" ] || [ -z "${HF_TOKEN:-}" ]; then log "[restore] skipped: backup repo is not configured" return 0 fi if su-exec sub2api python3 /app/deploy/huggingface/backup_manager.py restore --data-root "$DATA_ROOT"; then return 0 fi log "[restore] restore step failed; continuing with local state" return 0 } init_postgres() { if [ -s "$POSTGRES_DATA_DIR/PG_VERSION" ]; then return 0 fi password_file="$RUNTIME_DIR/postgres-password.txt" printf '%s' "$POSTGRES_PASSWORD" > "$password_file" chown sub2api:sub2api "$password_file" chmod 600 "$password_file" su-exec sub2api initdb \ -D "$POSTGRES_DATA_DIR" \ --username="$POSTGRES_USER" \ --pwfile="$password_file" \ --auth-host=scram-sha-256 \ --auth-local=scram-sha-256 rm -f "$password_file" } start_postgres() { su-exec sub2api pg_ctl \ -D "$POSTGRES_DATA_DIR" \ -l "$LOG_DIR/postgres.log" \ -o "-c listen_addresses=127.0.0.1 -c port=5432 -c unix_socket_directories=$RUNTIME_DIR/postgresql" \ -w start } ensure_database() { if PGPASSWORD="$POSTGRES_PASSWORD" psql -h 127.0.0.1 -U "$POSTGRES_USER" -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname = '$POSTGRES_DB'" | grep -q 1; then return 0 fi PGPASSWORD="$POSTGRES_PASSWORD" createdb -h 127.0.0.1 -U "$POSTGRES_USER" "$POSTGRES_DB" } start_redis() { if [ -n "$REDIS_PASSWORD" ]; then su-exec sub2api redis-server \ --bind 127.0.0.1 \ --port 6379 \ --dir "$REDIS_DATA_DIR" \ --save 60 1 \ --appendonly yes \ --appendfsync everysec \ --requirepass "$REDIS_PASSWORD" \ --daemonize yes else su-exec sub2api redis-server \ --bind 127.0.0.1 \ --port 6379 \ --dir "$REDIS_DATA_DIR" \ --save 60 1 \ --appendonly yes \ --appendfsync everysec \ --daemonize yes fi } wait_for_redis() { attempts=0 while [ "$attempts" -lt 20 ]; do if [ -n "$REDIS_PASSWORD" ]; then if redis-cli -a "$REDIS_PASSWORD" -h 127.0.0.1 ping >/dev/null 2>&1; then return 0 fi else if redis-cli -h 127.0.0.1 ping >/dev/null 2>&1; then return 0 fi fi attempts=$((attempts + 1)) sleep 1 done log "[startup] Redis did not become ready in time" return 1 } start_backup_loop() { if [ -z "${HF_BACKUP_REPO:-}" ] || [ -z "${HF_TOKEN:-}" ]; then log "[backup] periodic backups disabled" return 0 fi ( while true; do sleep "$BACKUP_INTERVAL_SECONDS" su-exec sub2api python3 /app/deploy/huggingface/backup_manager.py backup --data-root "$DATA_ROOT" || true done ) & BACKUP_LOOP_PID="$!" export BACKUP_LOOP_PID log "[backup] loop started (${BACKUP_INTERVAL_SECONDS}s)" } cleanup() { if [ "${CLEANUP_DONE:-0}" = "1" ]; then return 0 fi CLEANUP_DONE=1 export CLEANUP_DONE if [ -n "${BACKUP_LOOP_PID:-}" ]; then kill "$BACKUP_LOOP_PID" >/dev/null 2>&1 || true fi if [ -n "${HF_BACKUP_REPO:-}" ] && [ -n "${HF_TOKEN:-}" ]; then su-exec sub2api python3 /app/deploy/huggingface/backup_manager.py backup --data-root "$DATA_ROOT" || true fi if [ -n "$REDIS_PASSWORD" ]; then redis-cli -a "$REDIS_PASSWORD" -h 127.0.0.1 shutdown >/dev/null 2>&1 || true else redis-cli -h 127.0.0.1 shutdown >/dev/null 2>&1 || true fi su-exec sub2api pg_ctl -D "$POSTGRES_DATA_DIR" -m fast stop >/dev/null 2>&1 || true } trap cleanup INT TERM EXIT restore_if_needed init_postgres start_postgres ensure_database start_redis wait_for_redis start_backup_loop export AUTO_SETUP="${AUTO_SETUP:-true}" export DATA_DIR="$APP_DATA_DIR" export SERVER_HOST="${SERVER_HOST:-0.0.0.0}" export SERVER_PORT export SERVER_MODE="${SERVER_MODE:-release}" export RUN_MODE="${RUN_MODE:-standard}" export DATABASE_HOST="${DATABASE_HOST:-127.0.0.1}" export DATABASE_PORT="${DATABASE_PORT:-5432}" export DATABASE_USER="${DATABASE_USER:-$POSTGRES_USER}" export DATABASE_PASSWORD="${DATABASE_PASSWORD:-$POSTGRES_PASSWORD}" export DATABASE_DBNAME="${DATABASE_DBNAME:-$POSTGRES_DB}" export DATABASE_SSLMODE="${DATABASE_SSLMODE:-disable}" export DATABASE_MAX_OPEN_CONNS="${DATABASE_MAX_OPEN_CONNS:-10}" export DATABASE_MAX_IDLE_CONNS="${DATABASE_MAX_IDLE_CONNS:-5}" export DATABASE_CONN_MAX_LIFETIME_MINUTES="${DATABASE_CONN_MAX_LIFETIME_MINUTES:-30}" export DATABASE_CONN_MAX_IDLE_TIME_MINUTES="${DATABASE_CONN_MAX_IDLE_TIME_MINUTES:-5}" export REDIS_HOST="${REDIS_HOST:-127.0.0.1}" export REDIS_PORT="${REDIS_PORT:-6379}" export REDIS_DB="${REDIS_DB:-0}" export REDIS_POOL_SIZE="${REDIS_POOL_SIZE:-64}" export REDIS_MIN_IDLE_CONNS="${REDIS_MIN_IDLE_CONNS:-2}" export REDIS_ENABLE_TLS="${REDIS_ENABLE_TLS:-false}" export ADMIN_EMAIL="${ADMIN_EMAIL:-admin@sub2api.local}" export LOG_OUTPUT_TO_STDOUT="${LOG_OUTPUT_TO_STDOUT:-true}" export LOG_OUTPUT_TO_FILE="${LOG_OUTPUT_TO_FILE:-false}" export OPS_ENABLED="${OPS_ENABLED:-false}" export DASHBOARD_AGGREGATION_ENABLED="${DASHBOARD_AGGREGATION_ENABLED:-false}" export TZ="${TZ:-Asia/Shanghai}" log "[startup] launching sub2api on port ${SERVER_PORT}" su-exec sub2api /app/sub2api & APP_PID="$!" wait "$APP_PID"