agagsder commited on
Commit
14fbdb2
·
verified ·
1 Parent(s): 7ecc783

Run PostgreSQL locally with bucket backups

Browse files
Files changed (1) hide show
  1. start.sh +125 -58
start.sh CHANGED
@@ -1,27 +1,19 @@
1
  #!/bin/sh
2
  set -eu
3
 
4
- PGDATA=/data/postgresql
5
- REDIS_DATA_DIR=/data/redis
 
 
 
6
  APP_DATA_DIR=/data/sub2api
7
  APP_PID=""
8
  REDIS_PID=""
 
 
 
9
 
10
- PG_MARKER_DIRS="
11
- pg_commit_ts
12
- pg_dynshmem
13
- pg_multixact/members
14
- pg_multixact/offsets
15
- pg_notify
16
- pg_serial
17
- pg_snapshots
18
- pg_stat
19
- pg_stat_tmp
20
- pg_subtrans
21
- pg_twophase
22
- "
23
-
24
- PG_RECREATE_DIRS="
25
  pg_commit_ts
26
  pg_dynshmem
27
  pg_logical
@@ -45,26 +37,14 @@ pg_wal/summaries
45
  "
46
 
47
  ensure_pg_dirs() {
48
- for rel in $PG_RECREATE_DIRS; do
49
  dir="$PGDATA/$rel"
50
  if [ ! -d "$dir" ]; then
51
  mkdir -p "$dir"
52
  chown postgres:postgres "$dir"
53
  chmod 700 "$dir"
54
- echo "Restored PostgreSQL directory: $rel"
55
  fi
56
- done
57
-
58
- for rel in $PG_MARKER_DIRS; do
59
- marker="$PGDATA/$rel/.hf-keep"
60
- if [ ! -f "$marker" ]; then
61
- printf '%s\n' "HF Spaces persistent directory marker" > "$marker"
62
- chown postgres:postgres "$marker"
63
- fi
64
- done
65
-
66
- for rel in pg_logical pg_logical/mappings pg_logical/snapshots pg_multixact pg_replslot pg_tblspc pg_wal pg_wal/archive_status pg_wal/summaries; do
67
- rm -f "$PGDATA/$rel/.hf-keep"
68
  done
69
  }
70
 
@@ -72,18 +52,101 @@ start_postgres() {
72
  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
73
  }
74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  if [ -z "$DATABASE_PASSWORD" ]; then
76
  echo "DATABASE_PASSWORD is required" >&2
77
  exit 1
78
  fi
79
 
80
- mkdir -p "$PGDATA" "$REDIS_DATA_DIR" "$APP_DATA_DIR"
81
- chown -R postgres:postgres "$PGDATA"
82
- chmod 700 "$PGDATA"
83
  chown -R redis:redis "$REDIS_DATA_DIR"
84
  chown -R sub2api:sub2api "$APP_DATA_DIR"
85
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  if [ ! -s "$PGDATA/PG_VERSION" ]; then
 
 
 
 
87
  PASSWORD_FILE=$(mktemp)
88
  chmod 600 "$PASSWORD_FILE"
89
  printf '%s\n' "$DATABASE_PASSWORD" > "$PASSWORD_FILE"
@@ -92,37 +155,34 @@ if [ ! -s "$PGDATA/PG_VERSION" ]; then
92
  rm -f "$PASSWORD_FILE"
93
  fi
94
 
95
- rm -f "$PGDATA/postmaster.pid"
96
  ensure_pg_dirs
 
 
 
97
  start_postgres
98
 
99
  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
100
  su-exec postgres createdb -h /tmp -U "$DATABASE_USER" -O "$DATABASE_USER" "$DATABASE_DBNAME"
101
  fi
102
 
103
- su-exec redis redis-server \
104
- --bind 127.0.0.1 \
105
- --port 6379 \
106
- --dir "$REDIS_DATA_DIR" \
107
- --save 60 1 \
108
- --appendonly yes \
109
- --appendfsync everysec &
110
- REDIS_PID=$!
111
-
112
- cleanup() {
113
- trap - EXIT INT TERM
114
- if [ -n "$APP_PID" ] && kill -0 "$APP_PID" 2>/dev/null; then
115
- kill -TERM "$APP_PID" 2>/dev/null || true
116
- wait "$APP_PID" 2>/dev/null || true
117
  fi
118
- if [ -n "$REDIS_PID" ] && kill -0 "$REDIS_PID" 2>/dev/null; then
119
- kill -TERM "$REDIS_PID" 2>/dev/null || true
120
- wait "$REDIS_PID" 2>/dev/null || true
121
  fi
122
- su-exec postgres pg_ctl -D "$PGDATA" -m fast -w stop >/dev/null 2>&1 || true
123
- }
124
- trap cleanup EXIT INT TERM
 
125
 
 
126
  attempt=0
127
  until redis-cli -h 127.0.0.1 -p 6379 ping 2>/dev/null | grep -q PONG; do
128
  if ! kill -0 "$REDIS_PID" 2>/dev/null; then
@@ -140,10 +200,18 @@ done
140
  su-exec sub2api /app/sub2api &
141
  APP_PID=$!
142
 
 
 
 
 
 
 
 
 
 
143
  while kill -0 "$APP_PID" 2>/dev/null; do
144
- ensure_pg_dirs
145
  if ! su-exec postgres pg_ctl -D "$PGDATA" status >/dev/null 2>&1; then
146
- echo "PostgreSQL stopped unexpectedly; attempting recovery" >&2
147
  rm -f "$PGDATA/postmaster.pid"
148
  if ! start_postgres; then
149
  echo "PostgreSQL recovery failed; stopping Sub2API" >&2
@@ -151,7 +219,6 @@ while kill -0 "$APP_PID" 2>/dev/null; do
151
  wait "$APP_PID" 2>/dev/null || true
152
  exit 1
153
  fi
154
- echo "PostgreSQL recovered"
155
  fi
156
  sleep 2
157
  done
 
1
  #!/bin/sh
2
  set -eu
3
 
4
+ PGDATA=/tmp/sub2api-postgresql
5
+ LEGACY_PGDATA=/data/postgresql
6
+ BACKUP_DIR=/data/postgresql-backup
7
+ BACKUP_FILE="$BACKUP_DIR/latest.dump"
8
+ REDIS_DATA_DIR=/tmp/sub2api-redis
9
  APP_DATA_DIR=/data/sub2api
10
  APP_PID=""
11
  REDIS_PID=""
12
+ BACKUP_PID=""
13
+ MIGRATED=0
14
+ NEW_CLUSTER=0
15
 
16
+ PG_DIRS="
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  pg_commit_ts
18
  pg_dynshmem
19
  pg_logical
 
37
  "
38
 
39
  ensure_pg_dirs() {
40
+ for rel in $PG_DIRS; do
41
  dir="$PGDATA/$rel"
42
  if [ ! -d "$dir" ]; then
43
  mkdir -p "$dir"
44
  chown postgres:postgres "$dir"
45
  chmod 700 "$dir"
 
46
  fi
47
+ rm -f "$dir/.hf-keep"
 
 
 
 
 
 
 
 
 
 
 
48
  done
49
  }
50
 
 
52
  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
53
  }
54
 
55
+ start_redis() {
56
+ su-exec redis redis-server \
57
+ --bind 127.0.0.1 \
58
+ --port 6379 \
59
+ --dir "$REDIS_DATA_DIR" \
60
+ --save 60 1 \
61
+ --appendonly yes \
62
+ --appendfsync everysec &
63
+ REDIS_PID=$!
64
+ }
65
+
66
+ backup_postgres() {
67
+ if ! su-exec postgres pg_ctl -D "$PGDATA" status >/dev/null 2>&1; then
68
+ return 1
69
+ fi
70
+
71
+ mkdir -p "$BACKUP_DIR"
72
+ local_dump="/tmp/sub2api-backup.$.dump"
73
+ bucket_dump="$BACKUP_DIR/.latest.$$.dump"
74
+ rm -f "$local_dump" "$bucket_dump"
75
+
76
+ if ! su-exec postgres pg_dump -Fc -d "$DATABASE_DBNAME" -f "$local_dump"; then
77
+ rm -f "$local_dump"
78
+ return 1
79
+ fi
80
+ chmod 600 "$local_dump"
81
+
82
+ if ! cp "$local_dump" "$bucket_dump"; then
83
+ rm -f "$local_dump" "$bucket_dump"
84
+ return 1
85
+ fi
86
+ if ! mv -f "$bucket_dump" "$BACKUP_FILE"; then
87
+ rm -f "$local_dump" "$bucket_dump"
88
+ return 1
89
+ fi
90
+
91
+ rm -f "$local_dump"
92
+ echo "PostgreSQL backup saved to persistent storage"
93
+ }
94
+
95
+ cleanup() {
96
+ trap - EXIT INT TERM
97
+ if [ -n "$APP_PID" ] && kill -0 "$APP_PID" 2>/dev/null; then
98
+ kill -TERM "$APP_PID" 2>/dev/null || true
99
+ wait "$APP_PID" 2>/dev/null || true
100
+ fi
101
+ if [ -n "$BACKUP_PID" ] && kill -0 "$BACKUP_PID" 2>/dev/null; then
102
+ kill -TERM "$BACKUP_PID" 2>/dev/null || true
103
+ wait "$BACKUP_PID" 2>/dev/null || true
104
+ fi
105
+ backup_postgres >/dev/null 2>&1 || true
106
+ if [ -n "$REDIS_PID" ] && kill -0 "$REDIS_PID" 2>/dev/null; then
107
+ kill -TERM "$REDIS_PID" 2>/dev/null || true
108
+ wait "$REDIS_PID" 2>/dev/null || true
109
+ fi
110
+ su-exec postgres pg_ctl -D "$PGDATA" -m fast -w stop >/dev/null 2>&1 || true
111
+ }
112
+ trap cleanup EXIT INT TERM
113
+
114
  if [ -z "$DATABASE_PASSWORD" ]; then
115
  echo "DATABASE_PASSWORD is required" >&2
116
  exit 1
117
  fi
118
 
119
+ mkdir -p "$BACKUP_DIR" "$REDIS_DATA_DIR" "$APP_DATA_DIR"
 
 
120
  chown -R redis:redis "$REDIS_DATA_DIR"
121
  chown -R sub2api:sub2api "$APP_DATA_DIR"
122
 
123
+ # Prefer the logical backup after the first successful migration. The original
124
+ # bucket-backed cluster remains untouched as a one-time recovery source.
125
+ if [ ! -s "$PGDATA/PG_VERSION" ] && [ ! -s "$BACKUP_FILE" ] && [ -s "$LEGACY_PGDATA/PG_VERSION" ]; then
126
+ echo "Migrating legacy PostgreSQL data to local runtime storage"
127
+ copy_attempt=0
128
+ while :; do
129
+ rm -rf "$PGDATA"
130
+ mkdir -p "$PGDATA"
131
+ if cp -a "$LEGACY_PGDATA/." "$PGDATA/"; then
132
+ break
133
+ fi
134
+ copy_attempt=$((copy_attempt + 1))
135
+ if [ "$copy_attempt" -ge 3 ]; then
136
+ echo "Legacy PostgreSQL data copy failed" >&2
137
+ exit 1
138
+ fi
139
+ sleep 5
140
+ done
141
+ rm -f "$PGDATA/postmaster.pid" "$PGDATA/backup_label" "$PGDATA/tablespace_map"
142
+ MIGRATED=1
143
+ fi
144
+
145
  if [ ! -s "$PGDATA/PG_VERSION" ]; then
146
+ NEW_CLUSTER=1
147
+ mkdir -p "$PGDATA"
148
+ chown postgres:postgres "$PGDATA"
149
+ chmod 700 "$PGDATA"
150
  PASSWORD_FILE=$(mktemp)
151
  chmod 600 "$PASSWORD_FILE"
152
  printf '%s\n' "$DATABASE_PASSWORD" > "$PASSWORD_FILE"
 
155
  rm -f "$PASSWORD_FILE"
156
  fi
157
 
 
158
  ensure_pg_dirs
159
+ chown -R postgres:postgres "$PGDATA"
160
+ chmod 700 "$PGDATA"
161
+ rm -f "$PGDATA/postmaster.pid"
162
  start_postgres
163
 
164
  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
165
  su-exec postgres createdb -h /tmp -U "$DATABASE_USER" -O "$DATABASE_USER" "$DATABASE_DBNAME"
166
  fi
167
 
168
+ if [ "$NEW_CLUSTER" -eq 1 ] && [ "$MIGRATED" -eq 0 ] && [ -s "$BACKUP_FILE" ]; then
169
+ echo "Restoring PostgreSQL from persistent logical backup"
170
+ restore_dump=/tmp/sub2api-restore.dump
171
+ rm -f "$restore_dump"
172
+ if ! cp "$BACKUP_FILE" "$restore_dump"; then
173
+ echo "Persistent PostgreSQL backup could not be read" >&2
174
+ exit 1
 
 
 
 
 
 
 
175
  fi
176
+ if ! su-exec postgres pg_restore -l "$restore_dump" >/dev/null 2>&1; then
177
+ echo "Persistent PostgreSQL backup is invalid" >&2
178
+ exit 1
179
  fi
180
+ su-exec postgres pg_restore --clean --if-exists --no-owner --exit-on-error -d "$DATABASE_DBNAME" "$restore_dump"
181
+ rm -f "$restore_dump"
182
+ echo "PostgreSQL restore completed"
183
+ fi
184
 
185
+ start_redis
186
  attempt=0
187
  until redis-cli -h 127.0.0.1 -p 6379 ping 2>/dev/null | grep -q PONG; do
188
  if ! kill -0 "$REDIS_PID" 2>/dev/null; then
 
200
  su-exec sub2api /app/sub2api &
201
  APP_PID=$!
202
 
203
+ (
204
+ sleep 15
205
+ while kill -0 "$APP_PID" 2>/dev/null; do
206
+ backup_postgres || echo "PostgreSQL periodic backup failed" >&2
207
+ sleep 30
208
+ done
209
+ ) &
210
+ BACKUP_PID=$!
211
+
212
  while kill -0 "$APP_PID" 2>/dev/null; do
 
213
  if ! su-exec postgres pg_ctl -D "$PGDATA" status >/dev/null 2>&1; then
214
+ echo "PostgreSQL stopped unexpectedly; attempting local recovery" >&2
215
  rm -f "$PGDATA/postmaster.pid"
216
  if ! start_postgres; then
217
  echo "PostgreSQL recovery failed; stopping Sub2API" >&2
 
219
  wait "$APP_PID" 2>/dev/null || true
220
  exit 1
221
  fi
 
222
  fi
223
  sleep 2
224
  done