Spaces:
Runtime error
Runtime error
| set -eu | |
| if [ -n "${ADMIN_PASSWORD_V2:-}" ]; then | |
| ADMIN_PASSWORD="$ADMIN_PASSWORD_V2" | |
| echo "Admin password secret source: ADMIN_PASSWORD_V2" | |
| else | |
| echo "Admin password secret source: ADMIN_PASSWORD" | |
| fi | |
| required_vars="ADMIN_PASSWORD JWT_SECRET TOTP_ENCRYPTION_KEY" | |
| for var_name in $required_vars; do | |
| eval "var_value=\${$var_name:-}" | |
| if [ -z "$var_value" ]; then | |
| echo "Missing required secret: $var_name" >&2 | |
| exit 1 | |
| fi | |
| done | |
| ADMIN_EMAIL="${ADMIN_EMAIL:-2691539771@qq.com}" | |
| SERVER_PORT="${SERVER_PORT:-7860}" | |
| DATA_DIR="${DATA_DIR:-/tmp/gateway-data}" | |
| mkdir -p "$DATA_DIR" /tmp/gateway-redis /tmp/gateway-postgres | |
| chown -R sub2api:sub2api "$DATA_DIR" /tmp/gateway-redis | |
| chown -R postgres:postgres /tmp/gateway-postgres | |
| redis-server --daemonize yes --bind 127.0.0.1 --port 6379 --dir /tmp/gateway-redis --save "" --appendonly no | |
| if [ -z "${DATABASE_HOST:-}" ]; then | |
| if [ ! -s /tmp/gateway-postgres/PG_VERSION ]; then | |
| su-exec postgres initdb -D /tmp/gateway-postgres --auth=trust >/tmp/gateway-postgres-init.log | |
| fi | |
| su-exec postgres pg_ctl -D /tmp/gateway-postgres -l /tmp/gateway-postgres.log -o "-c listen_addresses=127.0.0.1 -c port=5432 -c unix_socket_directories=/tmp" start | |
| createdb -h 127.0.0.1 -U postgres gatewaydb 2>/dev/null || true | |
| DATABASE_HOST="127.0.0.1" | |
| DATABASE_PORT="5432" | |
| DATABASE_USER="postgres" | |
| DATABASE_PASSWORD="" | |
| DATABASE_DBNAME="gatewaydb" | |
| DATABASE_SSLMODE="disable" | |
| echo "Using ephemeral local PostgreSQL. Configure Supabase secrets for persistent data." | |
| else | |
| DATABASE_PORT="${DATABASE_PORT:-5432}" | |
| DATABASE_USER="${DATABASE_USER:-postgres}" | |
| DATABASE_PASSWORD="${DATABASE_PASSWORD:-}" | |
| DATABASE_DBNAME="${DATABASE_DBNAME:-postgres}" | |
| DATABASE_SSLMODE="${DATABASE_SSLMODE:-require}" | |
| fi | |
| export AUTO_SETUP="${AUTO_SETUP:-true}" | |
| export ADMIN_EMAIL | |
| export ADMIN_PASSWORD | |
| export JWT_SECRET | |
| export TOTP_ENCRYPTION_KEY | |
| export DATA_DIR | |
| export DATABASE_HOST | |
| export DATABASE_PORT | |
| export DATABASE_USER | |
| export DATABASE_PASSWORD | |
| export DATABASE_DBNAME | |
| export DATABASE_SSLMODE | |
| export REDIS_HOST="${REDIS_HOST:-127.0.0.1}" | |
| export REDIS_PORT="${REDIS_PORT:-6379}" | |
| export SERVER_HOST="${SERVER_HOST:-0.0.0.0}" | |
| export SERVER_PORT | |
| export SERVER_MODE="${SERVER_MODE:-release}" | |
| export PORT="${PORT:-$SERVER_PORT}" | |
| export GIN_MODE="${GIN_MODE:-release}" | |
| migration_count() { | |
| PGPASSWORD="${DATABASE_PASSWORD:-}" psql \ | |
| -h "$DATABASE_HOST" \ | |
| -p "$DATABASE_PORT" \ | |
| -U "$DATABASE_USER" \ | |
| -d "$DATABASE_DBNAME" \ | |
| -tAc "select count(1) from schema_migrations;" 2>/dev/null || printf '0\n' | |
| } | |
| sync_balance_cache_once() { | |
| if [ "${BALANCE_CACHE_SYNC_ENABLED:-true}" != "true" ]; then | |
| return 0 | |
| fi | |
| rows="$(PGPASSWORD="${DATABASE_PASSWORD:-}" psql \ | |
| -h "$DATABASE_HOST" \ | |
| -p "$DATABASE_PORT" \ | |
| -U "$DATABASE_USER" \ | |
| -d "$DATABASE_DBNAME" \ | |
| -qAt \ | |
| -F '|' \ | |
| -c "select id, balance::double precision from users where deleted_at is null and status = 'active';" 2>/tmp/balance-cache-sync.err || true)" | |
| if [ -z "$rows" ]; then | |
| return 0 | |
| fi | |
| ttl="${BALANCE_CACHE_SYNC_TTL_SECONDS:-300}" | |
| printf '%s\n' "$rows" | while IFS='|' read -r user_id balance; do | |
| case "$user_id" in | |
| ''|*[!0-9]*) continue ;; | |
| esac | |
| if [ -n "${REDIS_PASSWORD:-}" ]; then | |
| REDISCLI_AUTH="$REDIS_PASSWORD" redis-cli \ | |
| -h "${REDIS_HOST:-127.0.0.1}" \ | |
| -p "${REDIS_PORT:-6379}" \ | |
| -n "${REDIS_DB:-0}" \ | |
| SETEX "billing:balance:${user_id}" "$ttl" "$balance" >/dev/null 2>&1 || true | |
| else | |
| redis-cli \ | |
| -h "${REDIS_HOST:-127.0.0.1}" \ | |
| -p "${REDIS_PORT:-6379}" \ | |
| -n "${REDIS_DB:-0}" \ | |
| SETEX "billing:balance:${user_id}" "$ttl" "$balance" >/dev/null 2>&1 || true | |
| fi | |
| done | |
| } | |
| start_balance_cache_sync() { | |
| if [ "${BALANCE_CACHE_SYNC_ENABLED:-true}" != "true" ]; then | |
| return 0 | |
| fi | |
| ( | |
| interval="${BALANCE_CACHE_SYNC_INTERVAL_SECONDS:-60}" | |
| while :; do | |
| sync_balance_cache_once || true | |
| sleep "$interval" | |
| done | |
| ) & | |
| echo "Balance cache sync loop started." | |
| } | |
| write_runtime_config() { | |
| cat >"$DATA_DIR/config.yaml" <<EOF | |
| server: | |
| host: "${SERVER_HOST}" | |
| port: ${SERVER_PORT} | |
| mode: "${SERVER_MODE}" | |
| database: | |
| host: "${DATABASE_HOST}" | |
| port: ${DATABASE_PORT} | |
| user: "${DATABASE_USER}" | |
| password: "${DATABASE_PASSWORD}" | |
| dbname: "${DATABASE_DBNAME}" | |
| sslmode: "${DATABASE_SSLMODE}" | |
| max_open_conns: ${DATABASE_MAX_OPEN_CONNS:-10} | |
| max_idle_conns: ${DATABASE_MAX_IDLE_CONNS:-4} | |
| conn_max_lifetime_minutes: ${DATABASE_CONN_MAX_LIFETIME_MINUTES:-10} | |
| conn_max_idle_time_minutes: ${DATABASE_CONN_MAX_IDLE_TIME_MINUTES:-5} | |
| redis: | |
| host: "${REDIS_HOST}" | |
| port: ${REDIS_PORT} | |
| password: "${REDIS_PASSWORD:-}" | |
| db: ${REDIS_DB:-0} | |
| enable_tls: ${REDIS_ENABLE_TLS:-false} | |
| jwt: | |
| secret: "${JWT_SECRET}" | |
| expire_hour: ${JWT_EXPIRE_HOUR:-24} | |
| totp: | |
| encryption_key: "${TOTP_ENCRYPTION_KEY}" | |
| timezone: "${TZ:-Asia/Shanghai}" | |
| default: | |
| user_concurrency: 5 | |
| user_balance: 0 | |
| api_key_prefix: "sk-" | |
| rate_multiplier: 1.0 | |
| rate_limit: | |
| requests_per_minute: 60 | |
| burst_size: 10 | |
| gateway: | |
| max_account_switches: ${GATEWAY_MAX_ACCOUNT_SWITCHES:-3} | |
| max_account_switches_gemini: ${GATEWAY_MAX_ACCOUNT_SWITCHES_GEMINI:-2} | |
| max_idle_conns: ${GATEWAY_MAX_IDLE_CONNS:-256} | |
| max_idle_conns_per_host: ${GATEWAY_MAX_IDLE_CONNS_PER_HOST:-32} | |
| max_conns_per_host: ${GATEWAY_MAX_CONNS_PER_HOST:-128} | |
| max_upstream_clients: ${GATEWAY_MAX_UPSTREAM_CLIENTS:-256} | |
| client_idle_ttl_seconds: ${GATEWAY_CLIENT_IDLE_TTL_SECONDS:-300} | |
| stream_keepalive_interval: ${GATEWAY_STREAM_KEEPALIVE_INTERVAL:-5} | |
| models_list_cache_ttl_seconds: ${GATEWAY_MODELS_LIST_CACHE_TTL_SECONDS:-30} | |
| openai_http2: | |
| enabled: ${OPENAI_HTTP2_ENABLED:-true} | |
| allow_proxy_fallback_to_http1: ${OPENAI_HTTP2_ALLOW_PROXY_FALLBACK_TO_HTTP1:-true} | |
| openai_ws: | |
| max_conns_per_account: ${OPENAI_WS_MAX_CONNS_PER_ACCOUNT:-24} | |
| min_idle_per_account: ${OPENAI_WS_MIN_IDLE_PER_ACCOUNT:-0} | |
| max_idle_per_account: ${OPENAI_WS_MAX_IDLE_PER_ACCOUNT:-4} | |
| event_flush_batch_size: ${OPENAI_WS_EVENT_FLUSH_BATCH_SIZE:-1} | |
| event_flush_interval_ms: ${OPENAI_WS_EVENT_FLUSH_INTERVAL_MS:-5} | |
| usage_record: | |
| worker_count: ${USAGE_RECORD_WORKER_COUNT:-16} | |
| queue_size: ${USAGE_RECORD_QUEUE_SIZE:-4096} | |
| task_timeout_seconds: ${USAGE_RECORD_TASK_TIMEOUT_SECONDS:-15} | |
| overflow_policy: "${USAGE_RECORD_OVERFLOW_POLICY:-sync}" | |
| auto_scale_enabled: ${USAGE_RECORD_AUTO_SCALE_ENABLED:-false} | |
| EOF | |
| printf 'installed_at=%s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >"$DATA_DIR/.installed" | |
| chown sub2api:sub2api "$DATA_DIR/config.yaml" "$DATA_DIR/.installed" | |
| chmod 600 "$DATA_DIR/config.yaml" | |
| chmod 400 "$DATA_DIR/.installed" | |
| } | |
| maybe_skip_auto_setup() { | |
| if [ "${FORCE_RUNTIME_CONFIG:-false}" = "true" ]; then | |
| write_runtime_config | |
| export AUTO_SETUP=false | |
| echo "Runtime config forced; skipping auto setup." | |
| return 0 | |
| fi | |
| if [ "${SKIP_AUTO_SETUP_WHEN_MIGRATED:-true}" != "true" ]; then | |
| return 0 | |
| fi | |
| if [ "$DATABASE_HOST" = "127.0.0.1" ]; then | |
| return 0 | |
| fi | |
| applied_count="$(migration_count | tr -d '[:space:]')" | |
| case "$applied_count" in | |
| ''|*[!0-9]*) applied_count=0 ;; | |
| esac | |
| required_count="${MIGRATION_BOOTSTRAP_MIN_COUNT:-178}" | |
| echo "Detected applied migrations: ${applied_count}/${required_count}" | |
| if [ "$applied_count" -ge "$required_count" ]; then | |
| write_runtime_config | |
| export AUTO_SETUP=false | |
| echo "External database is already bootstrapped; skipping auto setup." | |
| fi | |
| return 0 | |
| } | |
| sync_admin_password() { | |
| if [ "${SYNC_ADMIN_PASSWORD:-true}" != "true" ]; then | |
| return 0 | |
| fi | |
| echo "Admin password sync loop started." | |
| synced_dbs="" | |
| candidate_dbs="$DATABASE_DBNAME" | |
| if [ "$DATABASE_HOST" = "127.0.0.1" ]; then | |
| candidate_dbs="$DATABASE_DBNAME postgres gatewaydb" | |
| fi | |
| for attempt in $(seq 1 15); do | |
| password_hash="$(python3 - <<'PY' | |
| import bcrypt | |
| import os | |
| print(bcrypt.hashpw(os.environ["ADMIN_PASSWORD"].encode("utf-8"), bcrypt.gensalt()).decode("utf-8")) | |
| PY | |
| )" | |
| synced_count=0 | |
| for sync_dbname in $candidate_dbs; do | |
| case " $synced_dbs " in | |
| *" $sync_dbname "*) continue ;; | |
| esac | |
| table_exists="$(PGPASSWORD="${DATABASE_PASSWORD:-}" psql \ | |
| -h "$DATABASE_HOST" \ | |
| -p "$DATABASE_PORT" \ | |
| -U "$DATABASE_USER" \ | |
| -d "$sync_dbname" \ | |
| -tAc "select to_regclass('public.users') is not null;" 2>/tmp/admin-user-check.err || true)" | |
| table_exists="$(printf '%s' "$table_exists" | tr -d '[:space:]')" | |
| admin_count="$(PGPASSWORD="${DATABASE_PASSWORD:-}" psql \ | |
| -h "$DATABASE_HOST" \ | |
| -p "$DATABASE_PORT" \ | |
| -U "$DATABASE_USER" \ | |
| -d "$sync_dbname" \ | |
| -tAc "select count(1) from users where role = 'admin';" 2>/dev/null || true)" | |
| admin_count="$(printf '%s' "$admin_count" | tr -d '[:space:]')" | |
| if [ "$table_exists" = "t" ]; then | |
| PGPASSWORD="${DATABASE_PASSWORD:-}" psql \ | |
| -h "$DATABASE_HOST" \ | |
| -p "$DATABASE_PORT" \ | |
| -U "$DATABASE_USER" \ | |
| -d "$sync_dbname" \ | |
| -v ON_ERROR_STOP=1 \ | |
| -c "update users set password_hash = '${password_hash}', role = 'admin', status = 'active', updated_at = now() where email = '${ADMIN_EMAIL}'; insert into users (email, password_hash, role, balance, concurrency, status, created_at, updated_at) select '${ADMIN_EMAIL}', '${password_hash}', 'admin', 0, 5, 'active', now(), now() where not exists (select 1 from users where email = '${ADMIN_EMAIL}');" >/tmp/admin-password-sync.out 2>/tmp/admin-password-sync.err | |
| stored_hash="$(PGPASSWORD="${DATABASE_PASSWORD:-}" psql \ | |
| -h "$DATABASE_HOST" \ | |
| -p "$DATABASE_PORT" \ | |
| -U "$DATABASE_USER" \ | |
| -d "$sync_dbname" \ | |
| -tAc "select password_hash from users where role = 'admin' limit 1;" 2>/tmp/admin-password-verify.err || true)" | |
| export STORED_ADMIN_PASSWORD_HASH="$stored_hash" | |
| verify_result="$(python3 - <<'PY' | |
| import bcrypt | |
| import os | |
| stored = os.environ.get("STORED_ADMIN_PASSWORD_HASH", "").strip() | |
| password = os.environ["ADMIN_PASSWORD"].encode("utf-8") | |
| print("ok" if stored and bcrypt.checkpw(password, stored.encode("utf-8")) else "failed") | |
| PY | |
| )" | |
| echo "Admin password synchronized from HF secret in db=${sync_dbname}." | |
| echo "Admin password bcrypt verification: ${verify_result}" | |
| synced_dbs="$synced_dbs $sync_dbname" | |
| synced_count=$((synced_count + 1)) | |
| fi | |
| done | |
| if [ -n "$(printf '%s' "$synced_dbs" | tr -d '[:space:]')" ] && [ "$attempt" -ge 5 ]; then | |
| return 0 | |
| fi | |
| sleep 2 | |
| done | |
| echo "Admin password sync skipped: admin user not found before timeout." >&2 | |
| cat /tmp/admin-user-check.err >&2 || true | |
| } | |
| maybe_skip_auto_setup | |
| start_balance_cache_sync | |
| attempt=1 | |
| max_attempts="${BOOTSTRAP_MAX_ATTEMPTS:-6}" | |
| while :; do | |
| echo "Starting gateway on ${SERVER_HOST}:${SERVER_PORT} (attempt ${attempt}/${max_attempts})" | |
| su-exec sub2api /app/hub-gateway & | |
| app_pid="$!" | |
| trap 'kill -TERM "$app_pid" 2>/dev/null || true' INT TERM | |
| sync_admin_password & | |
| set +e | |
| wait "$app_pid" | |
| app_status="$?" | |
| set -e | |
| if [ "$app_status" -eq 0 ]; then | |
| exit 0 | |
| fi | |
| if [ "$AUTO_SETUP" != "true" ] || [ "$DATABASE_HOST" = "127.0.0.1" ]; then | |
| exit "$app_status" | |
| fi | |
| maybe_skip_auto_setup | |
| if [ "$AUTO_SETUP" != "true" ]; then | |
| attempt=$((attempt + 1)) | |
| continue | |
| fi | |
| if [ "$attempt" -ge "$max_attempts" ]; then | |
| exit "$app_status" | |
| fi | |
| attempt=$((attempt + 1)) | |
| echo "Gateway exited during bootstrap; retrying to continue migrations." | |
| sleep 2 | |
| done | |