| #!/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" |
|
|
| |
| |
| 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" |
|
|