flask-api-server / start-space.sh
shadowfh96's picture
Upload start-space.sh with huggingface_hub
5fb57b5 verified
Raw
History Blame Contribute Delete
11.6 kB
#!/usr/bin/env sh
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