| #!/bin/sh |
| set -e |
|
|
| PGDATA="/tmp/pgdata" |
| PGUSER="postgres" |
| PGPASS="postgres" |
| PGDB="su2adb" |
| REDIS_DIR="/tmp/redis" |
| BUCKET_DUMP="hf://buckets/cjovs/su2a-data/su2adb_dump.sql" |
|
|
| if [ "$(id -u)" = "0" ]; then |
| mkdir -p /tmp/redis 2>/dev/null || true |
| chown -R su2a:su2a /app/data 2>/dev/null & |
| chown su2a:su2a /app 2>/dev/null || true |
| sleep 2 |
| kill %1 2>/dev/null || true |
| exec su-exec su2a "$0" "$@" |
| fi |
|
|
| echo "=== Starting PostgreSQL ===" |
| rm -f "$PGDATA/postmaster.pid" 2>/dev/null || true |
|
|
| if [ ! -f "$PGDATA/PG_VERSION" ]; then |
| echo "[pg] Initializing $PGDATA ..." |
| initdb -D "$PGDATA" --username="$PGUSER" --auth=trust |
| echo "host all all 127.0.0.1/32 trust" >> "$PGDATA/pg_hba.conf" |
| echo "host all all ::1/128 trust" >> "$PGDATA/pg_hba.conf" |
| fi |
|
|
| pg_ctl -D "$PGDATA" -l "$PGDATA/logfile" -o "-k /tmp -p 5432" start |
| until pg_isready -h localhost -p 5432 -U "$PGUSER" -q 2>/dev/null; do sleep 1; done |
| echo "[pg] PostgreSQL ready." |
|
|
| psql -h localhost -p 5432 -U "$PGUSER" -d postgres -c "ALTER USER $PGUSER WITH PASSWORD '$PGPASS';" 2>/dev/null || true |
|
|
| if ! psql -h localhost -p 5432 -U "$PGUSER" -lqt 2>/dev/null | cut -d \| -f 1 | grep -qw "$PGDB"; then |
| echo "[pg] Creating database $PGDB ..." |
| createdb -h localhost -p 5432 -U "$PGUSER" "$PGDB" |
| |
| |
| if [ -n "$HF_TOKEN" ]; then |
| echo "[pg] Checking bucket for backup..." |
| if hf buckets list cjovs/su2a-data --quiet 2>/dev/null | grep -q "su2adb_dump.sql"; then |
| echo "[pg] Restoring from bucket backup..." |
| hf buckets cp "$BUCKET_DUMP" /tmp/restore.sql --quiet 2>/dev/null || true |
| if [ -f /tmp/restore.sql ] && [ -s /tmp/restore.sql ]; then |
| psql -h localhost -p 5432 -U "$PGUSER" -d "$PGDB" -f /tmp/restore.sql 2>/dev/null || true |
| rm -f /tmp/restore.sql |
| echo "[pg] Restore complete." |
| fi |
| fi |
| fi |
| fi |
| echo "[pg] Database $PGDB ready." |
|
|
| echo "=== Starting Redis ===" |
| mkdir -p "$REDIS_DIR" 2>/dev/null || true |
| redis-server --daemonize yes --dir "$REDIS_DIR" --save "" --port 6379 --bind 127.0.0.1 |
| echo "[redis] Redis started." |
|
|
| |
| echo "[update] Checking for latest version..." |
| set +e |
| python3 <<'PYEOF' |
| import os, json, tarfile, io |
| from urllib.request import Request, urlopen |
|
|
| try: |
| req = Request('https://api.github.com/repos/Wei-Shaw/sub2api/releases/latest') |
| req.add_header('Accept', 'application/vnd.github.v3+json') |
| req.add_header('User-Agent', 'Sub2API-AutoUpdater') |
| with urlopen(req, timeout=30) as resp: |
| release = json.loads(resp.read()) |
|
|
| tag = release['tag_name'].lstrip('v') |
|
|
| for asset in release.get('assets', []): |
| name = asset.get('name', '') |
| if 'linux_amd64' in name and name.endswith('.tar.gz'): |
| url = asset['browser_download_url'] |
| print(f'[update] v{tag} found, downloading {name} ...') |
|
|
| req2 = Request(url) |
| req2.add_header('User-Agent', 'Sub2API-AutoUpdater') |
| with urlopen(req2, timeout=600) as resp: |
| data = resp.read() |
|
|
| with tarfile.open(fileobj=io.BytesIO(data), mode='r:gz') as tar: |
| for member in tar: |
| mname = member.name.rstrip('/') |
| if mname == 'sub2api' or mname == './sub2api': |
| f = tar.extractfile(member) |
| if f: |
| tmp = '/tmp/.su2a.new' |
| with open(tmp, 'wb') as out: |
| out.write(f.read()) |
| os.chmod(tmp, 0o755) |
| import shutil |
| shutil.move(tmp, '/app/su2a') |
| print(f'[update] Updated to v{tag}') |
| exit(0) |
| break |
|
|
| print('[update] No matching linux_amd64 asset found') |
| exit(1) |
| except Exception as e: |
| print(f'[update] Skipped: {e}') |
| exit(1) |
| PYEOF |
| UPDATE_EXIT=$? |
| set -e |
| if [ $UPDATE_EXIT -eq 0 ]; then |
| echo "[update] Running latest version" |
| else |
| echo "[update] Running built-in version (will retry next restart)" |
| fi |
|
|
| cleanup() { |
| echo "=== Shutting down ===" |
| redis-cli -h 127.0.0.1 -p 6379 shutdown 2>/dev/null || true |
| |
| |
| if [ -n "$HF_TOKEN" ]; then |
| echo "[pg] Backing up database to bucket..." |
| pg_dump -h localhost -p 5432 -U "$PGUSER" -d "$PGDB" --no-owner --no-acl -f /tmp/su2adb_dump.sql 2>/dev/null || true |
| if [ -f /tmp/su2adb_dump.sql ] && [ -s /tmp/su2adb_dump.sql ]; then |
| hf buckets cp /tmp/su2adb_dump.sql "$BUCKET_DUMP" --quiet 2>/dev/null || true |
| echo "[pg] Backup uploaded." |
| fi |
| fi |
| |
| pg_ctl -D "$PGDATA" stop -m fast 2>/dev/null || true |
| } |
| trap cleanup EXIT INT TERM |
|
|
| export DATABASE_DBNAME="$PGDB" |
|
|
| |
| export DATABASE_HOST=localhost |
| export DATABASE_PORT=5432 |
| export DATABASE_USER="$PGUSER" |
| export DATABASE_PASSWORD="$PGPASS" |
| export DATABASE_SSLMODE=disable |
| export REDIS_HOST=localhost |
| export REDIS_PORT=6379 |
| export REDIS_DB=0 |
| export REDIS_ENABLE_TLS=false |
| export SERVER_HOST=0.0.0.0 |
| export SERVER_PORT=8080 |
| export SERVER_MODE=release |
| export JWT_SECRET=e32467771e531062d2b2d68919e7430e60c32f0f0381320ed068cd6d61435e9c |
| export JWT_EXPIRE_HOUR=24 |
|
|
| echo "=== Starting su2a ===" |
| while true; do |
| echo "[su2a] Starting..." |
| /app/su2a "$@" & |
| PID=$! |
|
|
| |
| if [ ! -f /tmp/.init_done ] && [ -n "$ADMIN_EMAIL" ] && [ -n "$ADMIN_PASSWORD" ]; then |
| for i in $(seq 1 30); do |
| if psql -h localhost -p 5432 -U "$PGUSER" -d "$PGDB" -c "SELECT 1 FROM users LIMIT 1" >/dev/null 2>&1; then |
| echo "[init] Tables ready, creating admin user..." |
| pip3 install --no-cache-dir --break-system-packages -q bcrypt 2>/dev/null |
| python3 -c " |
| import os, bcrypt, subprocess |
| email = os.environ['ADMIN_EMAIL'] |
| password = os.environ['ADMIN_PASSWORD'] |
| hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode() |
| sql = f\"\"\"INSERT INTO users (email, password_hash, role, status, balance, concurrency, signup_source) |
| VALUES ('{email}', '{hashed}', 'admin', 'active', 0, 5, 'email') |
| ON CONFLICT (email) WHERE deleted_at IS NULL |
| DO UPDATE SET password_hash = EXCLUDED.password_hash, role = 'admin'\"\"\" |
| r = subprocess.run(['psql', '-h', 'localhost', '-p', '5432', '-U', 'postgres', '-d', 'su2adb', '-c', sql], capture_output=True, text=True) |
| if r.returncode == 0: |
| print('[init] Admin user created: ' + email) |
| else: |
| print('[init] ERROR creating admin: ' + r.stderr.strip()) |
| " |
| touch /tmp/.init_done |
| break |
| fi |
| sleep 2 |
| done |
| fi |
|
|
| wait $PID |
| echo "[su2a] Exited, restarting in 2s..." |
| sleep 2 |
| done |