su2a / deploy /docker-entrypoint.sh
cjovs's picture
fix: chown /app to su2a for auto-update write access
4257495 verified
Raw
History Blame Contribute Delete
7.05 kB
#!/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"
# Restore from bucket if dump exists
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."
# === Auto-update: download latest binary from GitHub ===
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
# Backup database to bucket
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"
# Override config via env vars to bypass FUSE read corruption
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=$!
# On fresh DB: wait for tables then create admin user
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