XAPI / deploy /huggingface /start.sh
cjovs's picture
Deploy Sub2API HF Space with embedded Postgres/Redis backup runtime
8059bf0 verified
#!/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"