File size: 6,106 Bytes
0f33135
 
 
14fbdb2
 
 
 
 
0f33135
c1cdb5b
 
14fbdb2
 
 
c1cdb5b
14fbdb2
e18b997
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14fbdb2
e18b997
 
 
 
 
 
14fbdb2
7ecc783
e18b997
 
 
 
 
 
14fbdb2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19a98be
14fbdb2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c1cdb5b
 
 
 
 
14fbdb2
c1cdb5b
 
 
14fbdb2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c1cdb5b
14fbdb2
 
 
 
c1cdb5b
 
 
 
 
 
 
 
e18b997
14fbdb2
 
 
e18b997
c1cdb5b
 
 
 
 
14fbdb2
 
 
 
 
 
 
c1cdb5b
14fbdb2
 
 
c1cdb5b
19a98be
 
 
14fbdb2
 
 
c1cdb5b
19a98be
 
 
 
 
14fbdb2
c1cdb5b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0f33135
14fbdb2
 
 
 
 
 
 
 
 
e18b997
 
14fbdb2
e18b997
 
 
 
 
 
 
0f33135
7ecc783
0f33135
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#!/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"