#!/bin/sh set -eu PGDATA=/tmp/sub2api-postgresql LEGACY_PGDATA=/data/postgresql BACKUP_DIR=/data/postgresql-backup BACKUP_FILE="$BACKUP_DIR/latest.dump" REDIS_DATA_DIR=/tmp/sub2api-redis APP_DATA_DIR=/data/sub2api APP_PID="" REDIS_PID="" BACKUP_PID="" MIGRATED=0 NEW_CLUSTER=0 PG_DIRS=" pg_commit_ts pg_dynshmem pg_logical pg_logical/mappings pg_logical/snapshots pg_multixact pg_multixact/members pg_multixact/offsets pg_notify pg_replslot pg_serial pg_snapshots pg_stat pg_stat_tmp pg_subtrans pg_tblspc pg_twophase pg_wal pg_wal/archive_status pg_wal/summaries " ensure_pg_dirs() { for rel in $PG_DIRS; do dir="$PGDATA/$rel" if [ ! -d "$dir" ]; then mkdir -p "$dir" chown postgres:postgres "$dir" chmod 700 "$dir" fi rm -f "$dir/.hf-keep" done } start_postgres() { su-exec postgres pg_ctl -D "$PGDATA" -o "-c listen_addresses=127.0.0.1 -c port=5432 -c unix_socket_directories=/tmp" -w start } start_redis() { su-exec redis redis-server \ --bind 127.0.0.1 \ --port 6379 \ --dir "$REDIS_DATA_DIR" \ --save 60 1 \ --appendonly yes \ --appendfsync everysec & REDIS_PID=$! } backup_postgres() { if ! su-exec postgres pg_ctl -D "$PGDATA" status >/dev/null 2>&1; then return 1 fi mkdir -p "$BACKUP_DIR" local_dump="/tmp/sub2api-backup.$.dump" bucket_dump="$BACKUP_DIR/.latest.$$.dump" rm -f "$local_dump" "$bucket_dump" if ! su-exec postgres pg_dump -h /tmp -U "$DATABASE_USER" -Fc -d "$DATABASE_DBNAME" -f "$local_dump"; then rm -f "$local_dump" return 1 fi chmod 600 "$local_dump" if ! cp "$local_dump" "$bucket_dump"; then rm -f "$local_dump" "$bucket_dump" return 1 fi if ! mv -f "$bucket_dump" "$BACKUP_FILE"; then rm -f "$local_dump" "$bucket_dump" return 1 fi rm -f "$local_dump" echo "PostgreSQL backup saved to persistent storage" } cleanup() { trap - EXIT INT TERM if [ -n "$APP_PID" ] && kill -0 "$APP_PID" 2>/dev/null; then kill -TERM "$APP_PID" 2>/dev/null || true wait "$APP_PID" 2>/dev/null || true fi if [ -n "$BACKUP_PID" ] && kill -0 "$BACKUP_PID" 2>/dev/null; then kill -TERM "$BACKUP_PID" 2>/dev/null || true wait "$BACKUP_PID" 2>/dev/null || true fi backup_postgres >/dev/null 2>&1 || true if [ -n "$REDIS_PID" ] && kill -0 "$REDIS_PID" 2>/dev/null; then kill -TERM "$REDIS_PID" 2>/dev/null || true wait "$REDIS_PID" 2>/dev/null || true fi su-exec postgres pg_ctl -D "$PGDATA" -m fast -w stop >/dev/null 2>&1 || true } trap cleanup EXIT INT TERM if [ -z "$DATABASE_PASSWORD" ]; then echo "DATABASE_PASSWORD is required" >&2 exit 1 fi mkdir -p "$BACKUP_DIR" "$REDIS_DATA_DIR" "$APP_DATA_DIR" chown -R redis:redis "$REDIS_DATA_DIR" chown -R sub2api:sub2api "$APP_DATA_DIR" # Prefer the logical backup after the first successful migration. The original # bucket-backed cluster remains untouched as a one-time recovery source. if [ ! -s "$PGDATA/PG_VERSION" ] && [ ! -s "$BACKUP_FILE" ] && [ -s "$LEGACY_PGDATA/PG_VERSION" ]; then echo "Migrating legacy PostgreSQL data to local runtime storage" copy_attempt=0 while :; do rm -rf "$PGDATA" mkdir -p "$PGDATA" if cp -a "$LEGACY_PGDATA/." "$PGDATA/"; then break fi copy_attempt=$((copy_attempt + 1)) if [ "$copy_attempt" -ge 3 ]; then echo "Legacy PostgreSQL data copy failed" >&2 exit 1 fi sleep 5 done rm -f "$PGDATA/postmaster.pid" "$PGDATA/backup_label" "$PGDATA/tablespace_map" MIGRATED=1 fi if [ ! -s "$PGDATA/PG_VERSION" ]; then NEW_CLUSTER=1 mkdir -p "$PGDATA" chown postgres:postgres "$PGDATA" chmod 700 "$PGDATA" PASSWORD_FILE=$(mktemp) chmod 600 "$PASSWORD_FILE" printf '%s\n' "$DATABASE_PASSWORD" > "$PASSWORD_FILE" chown postgres:postgres "$PASSWORD_FILE" su-exec postgres initdb -D "$PGDATA" -U "$DATABASE_USER" --pwfile="$PASSWORD_FILE" --auth-local=trust --auth-host=scram-sha-256 rm -f "$PASSWORD_FILE" fi ensure_pg_dirs chown -R postgres:postgres "$PGDATA" chmod 700 "$PGDATA" rm -f "$PGDATA/postmaster.pid" start_postgres if ! su-exec postgres psql -h /tmp -U "$DATABASE_USER" -d postgres -tAc "SELECT 1 FROM pg_database WHERE datname = 'sub2api'" | grep -q 1; then su-exec postgres createdb -h /tmp -U "$DATABASE_USER" -O "$DATABASE_USER" "$DATABASE_DBNAME" fi if [ "$NEW_CLUSTER" -eq 1 ] && [ "$MIGRATED" -eq 0 ] && [ -s "$BACKUP_FILE" ]; then echo "Restoring PostgreSQL from persistent logical backup" restore_dump=/tmp/sub2api-restore.dump rm -f "$restore_dump" if ! cp "$BACKUP_FILE" "$restore_dump"; then echo "Persistent PostgreSQL backup could not be read" >&2 exit 1 fi if ! su-exec postgres pg_restore -l "$restore_dump" >/dev/null 2>&1; then echo "Persistent PostgreSQL backup is invalid" >&2 exit 1 fi su-exec postgres pg_restore -h /tmp -U "$DATABASE_USER" \ --clean --if-exists --no-owner --exit-on-error \ -d "$DATABASE_DBNAME" "$restore_dump" rm -f "$restore_dump" echo "PostgreSQL restore completed" fi if ! backup_postgres; then echo "Initial PostgreSQL backup failed" >&2 exit 1 fi start_redis attempt=0 until redis-cli -h 127.0.0.1 -p 6379 ping 2>/dev/null | grep -q PONG; do if ! kill -0 "$REDIS_PID" 2>/dev/null; then echo "Redis failed to start" >&2 exit 1 fi attempt=$((attempt + 1)) if [ "$attempt" -ge 30 ]; then echo "Redis readiness check timed out" >&2 exit 1 fi sleep 1 done su-exec sub2api /app/sub2api & APP_PID=$! ( sleep 15 while kill -0 "$APP_PID" 2>/dev/null; do backup_postgres || echo "PostgreSQL periodic backup failed" >&2 sleep 30 done ) & BACKUP_PID=$! while kill -0 "$APP_PID" 2>/dev/null; do if ! su-exec postgres pg_ctl -D "$PGDATA" status >/dev/null 2>&1; then echo "PostgreSQL stopped unexpectedly; attempting local recovery" >&2 rm -f "$PGDATA/postmaster.pid" if ! start_postgres; then echo "PostgreSQL recovery failed; stopping Sub2API" >&2 kill -TERM "$APP_PID" 2>/dev/null || true wait "$APP_PID" 2>/dev/null || true exit 1 fi fi sleep 2 done wait "$APP_PID"