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