tenseleyflow/shithub / 5cdd4d4

Browse files

S37: Postgres deploy scripts (WAL archive + daily backup + hook grants)

Authored by espadonne
SHA
5cdd4d4488b5713146f87e238961e0378673ac7f
Parents
4b67e74
Tree
fdd9521

3 changed files

StatusFile+-
A deploy/postgres/archive_command.sh 24 0
A deploy/postgres/backup-daily.sh 35 0
A deploy/postgres/hook-role-grants.sql 46 0
deploy/postgres/archive_command.shadded
@@ -0,0 +1,24 @@
1
+#!/usr/bin/env bash
2
+# SPDX-License-Identifier: AGPL-3.0-or-later
3
+#
4
+# Postgres archive_command. Postgres calls this with two args:
5
+#   $1 = absolute path to the WAL segment in pgdata
6
+#   $2 = the segment filename
7
+#
8
+# Contract: exit 0 ONLY when the file is durably stored. Postgres
9
+# refuses to recycle the segment until we report success — getting
10
+# this wrong fills the disk. We use rclone copyto with --no-update-
11
+# modtime so the bucket is the source of truth on retention.
12
+#
13
+# Wired by deploy/ansible/roles/postgres/templates/postgresql.conf.j2.
14
+
15
+set -euo pipefail
16
+
17
+SRC="$1"
18
+NAME="$2"
19
+BUCKET="${SHITHUB_WAL_BUCKET:-spaces-prod:shithub-wal}"
20
+
21
+# Atomic-ish: rclone copyto streams to a temp object, then renames.
22
+rclone --config /root/.config/rclone/rclone.conf \
23
+       --quiet \
24
+       copyto "$SRC" "$BUCKET/$(date +%Y/%m/%d)/$NAME"
deploy/postgres/backup-daily.shadded
@@ -0,0 +1,35 @@
1
+#!/usr/bin/env bash
2
+# SPDX-License-Identifier: AGPL-3.0-or-later
3
+#
4
+# Daily logical backup. Run from cron (or a systemd timer) as the
5
+# postgres user. We take a custom-format pg_dump of the shithub DB,
6
+# stream it to Spaces, and keep one local copy in /var/backups for
7
+# the operator to grab in a hurry. Lifecycle on the bucket prunes
8
+# anything older than 30 days; PITR rolls forward from the WAL
9
+# archive (see archive_command.sh).
10
+#
11
+# Exit non-zero on any failure so the systemd timer surfaces it
12
+# (OnFailure= → alertmanager).
13
+
14
+set -euo pipefail
15
+
16
+DB="${SHITHUB_DB:-shithub}"
17
+BUCKET="${SHITHUB_BACKUP_BUCKET:-spaces-prod:shithub-backups}"
18
+LOCAL_DIR="${SHITHUB_BACKUP_LOCAL:-/var/backups/shithub}"
19
+STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
20
+NAME="${DB}-${STAMP}.dump"
21
+
22
+mkdir -p "$LOCAL_DIR"
23
+
24
+pg_dump --format=custom --compress=9 --no-owner --no-privileges \
25
+        --file="$LOCAL_DIR/$NAME" "$DB"
26
+
27
+# Verify the dump is structurally sound before we ship it.
28
+pg_restore --list "$LOCAL_DIR/$NAME" >/dev/null
29
+
30
+rclone --config /root/.config/rclone/rclone.conf \
31
+       copyto "$LOCAL_DIR/$NAME" "$BUCKET/daily/$(date -u +%Y/%m/%d)/$NAME"
32
+
33
+# Local retention: keep the last 7 dumps; bucket lifecycle handles
34
+# the long tail.
35
+ls -1t "$LOCAL_DIR"/*.dump 2>/dev/null | tail -n +8 | xargs -r rm -f
deploy/postgres/hook-role-grants.sqladded
@@ -0,0 +1,46 @@
1
+-- SPDX-License-Identifier: AGPL-3.0-or-later
2
+--
3
+-- Standalone hook-role grants. The Ansible postgres role applies
4
+-- the same grants idempotently; this file exists so an operator
5
+-- can re-apply (or audit) the exact write surface without running
6
+-- the full playbook.
7
+--
8
+-- Contract: shithub_hook is the role assumed by `shithubd hook ...`
9
+-- subprocesses (post-receive, pre-receive). It MUST NOT have any
10
+-- access beyond what's listed here. If a hook subcommand needs a
11
+-- new table, add it here in the same PR — grep `shithub_hook` in
12
+-- cmd/shithubd/hook.go to confirm.
13
+--
14
+-- Apply as the shithub DB owner:
15
+--   psql -U shithub -d shithub -f hook-role-grants.sql
16
+
17
+BEGIN;
18
+
19
+-- The role is created idempotently by the Ansible role; if you're
20
+-- applying this by hand on a fresh DB, uncomment:
21
+-- CREATE ROLE shithub_hook LOGIN PASSWORD :'hook_password';
22
+
23
+-- Read surface: the hook needs to look up the pushing user, the
24
+-- target repo, and the collaborator/permission rows to authorize
25
+-- the push.
26
+GRANT SELECT ON users               TO shithub_hook;
27
+GRANT SELECT ON repos               TO shithub_hook;
28
+GRANT SELECT ON repo_collaborators  TO shithub_hook;
29
+GRANT SELECT ON orgs                TO shithub_hook;
30
+GRANT SELECT ON org_members         TO shithub_hook;
31
+
32
+-- Write surface: every row the hook subcommand inserts. Nothing
33
+-- here gets UPDATE or DELETE — those happen out-of-band through
34
+-- the web app or worker.
35
+GRANT INSERT ON push_events       TO shithub_hook;
36
+GRANT INSERT ON jobs              TO shithub_hook;
37
+GRANT INSERT ON domain_events     TO shithub_hook;
38
+GRANT INSERT ON auth_audit_log    TO shithub_hook;
39
+
40
+-- Sequences for the SERIAL/BIGSERIAL ids on the insert tables.
41
+GRANT USAGE, SELECT ON SEQUENCE push_events_id_seq    TO shithub_hook;
42
+GRANT USAGE, SELECT ON SEQUENCE jobs_id_seq           TO shithub_hook;
43
+GRANT USAGE, SELECT ON SEQUENCE domain_events_id_seq  TO shithub_hook;
44
+GRANT USAGE, SELECT ON SEQUENCE auth_audit_log_id_seq TO shithub_hook;
45
+
46
+COMMIT;