Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions ansible/files/adminapi.sudoers.conf
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,21 @@ Cmnd_Alias PGBOUNCER = /bin/systemctl start pgbouncer.service, /bin/systemctl st
%adminapi ALL= NOPASSWD: /etc/adminapi/pg_upgrade_scripts/common.sh
%adminapi ALL= NOPASSWD: /etc/adminapi/pg_upgrade_scripts/pgsodium_getkey.sh
%adminapi ALL= NOPASSWD: /usr/bin/systemctl daemon-reload
# pgBackRest wrapper scripts: constrained helpers called by supabase-admin-agent.
# pgdata-chown runs as root (default); pgdata-signal runs as postgres so it can
# create/remove signal files owned by that user.
%adminapi ALL= NOPASSWD: /usr/local/lib/supabase-admin-agent/pgdata-chown
%adminapi ALL=(postgres) NOPASSWD: /usr/local/lib/supabase-admin-agent/pgdata-signal
# pgBackRest binary entries support two sudo chains used by supabase-admin-agent:
# NewRunner() — adminapi → /usr/bin/pgbackrest wrapper → sudo -u pgbackrest real_binary
# NewRunnerAs() — adminapi → sudo -u pgbackrest /usr/bin/pgbackrest → sudo -u pgbackrest real_binary
# Both paths require the wrapper entry; NewRunner() additionally needs the real binary entry
# because the wrapper itself calls sudo to drop privileges to the pgbackrest user.
%adminapi ALL=(pgbackrest) NOPASSWD: /var/lib/pgbackrest/.nix-profile/bin/pgbackrest
%adminapi ALL=(pgbackrest) NOPASSWD: /usr/bin/pgbackrest
# pgBackRest restore stops PostgreSQL, restores PGDATA, then starts PostgreSQL.
%adminapi ALL= NOPASSWD: /usr/bin/systemctl start postgresql.service
%adminapi ALL= NOPASSWD: /usr/bin/systemctl stop postgresql.service
%adminapi ALL= NOPASSWD: /usr/bin/systemctl reload postgresql.service
%adminapi ALL= NOPASSWD: /usr/bin/systemctl restart postgresql.service
%adminapi ALL= NOPASSWD: /usr/bin/systemctl show -p NRestarts postgresql.service
Expand Down
10 changes: 10 additions & 0 deletions ansible/files/logrotate_config/logrotate-pgbackrest.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/var/log/pgbackrest/*.log {
daily
size 50M
rotate 7
compress
delaycompress
missingok
notifempty
create 0660 pgbackrest postgres
}
14 changes: 8 additions & 6 deletions ansible/files/pgbackrest_config/pgbackrest.conf
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ archive-copy = y
backup-standby = prefer
compress-type = zst
delta = y
expire-auto = n
# expire-auto=y allows pgBackRest to expire old backups automatically after
# each new backup completes, keeping retention in sync without a separate
# expire command. Previously n; changed to work correctly with SAA-managed backups.
expire-auto = y
link-all = y
log-level-console = info
log-level-file = detail
log-subprocess = y
resume = n
start-fast = y

[supabase]
pg1-path = /var/lib/postgresql/data
pg1-socket-path = /run/postgresql
pg1-user = supabase_admin
# Note: the [supabase] stanza (pg1-path, pg1-socket-path, pg1-user) has been
# removed from this file. supabase-admin-agent owns that stanza and writes it
# to /etc/pgbackrest/conf.d/ at enable time. Having the stanza in both places
# causes pgBackRest error [031] (duplicate option).
5 changes: 4 additions & 1 deletion ansible/files/postgresql_config/pg_hba.conf.j2
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@
# TYPE DATABASE USER ADDRESS METHOD

# trust local connections
local all supabase_admin scram-sha-256
# supabase_admin: trust over Unix socket only — no network exposure. Access
# requires OS-level shell access to the host, which is already a stronger
# security boundary than a database password.
local all supabase_admin trust
local all all peer map=supabase_map
host all all 127.0.0.1/32 trust
host all all ::1/128 trust
Expand Down
34 changes: 34 additions & 0 deletions ansible/files/supabase_admin_agent_config/pgdata-chown
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# pgdata-chown — transfers PGDATA ownership for pgBackRest restore operations.
#
# Called via sudo by supabase-admin-agent (running as adminapi). Only two
# actions are accepted, and the target path must resolve to /data/pgdata or a
# path beneath it. realpath(1) is used to expand symlinks before the check,
# which prevents directory-traversal attacks (e.g. /data/pgdata/../../etc/sudoers).
#
# Usage: pgdata-chown <to-pgbackrest|to-postgres> <path>
set -euo pipefail

if [[ $# -ne 2 ]]; then
echo "usage: pgdata-chown <to-pgbackrest|to-postgres> <path>" >&2
exit 1
fi

ACTION="$1"
TARGET="$2"

REAL=$(realpath "$TARGET")
if [[ "$REAL" != "/data/pgdata" && "$REAL" != /data/pgdata/* ]]; then
echo "error: '${TARGET}' resolves to '${REAL}', which is not under /data/pgdata" >&2
exit 1
fi

case "$ACTION" in
to-pgbackrest|to-postgres)
exec /usr/bin/chown -R "${ACTION:3}:postgres" "$REAL"
;;
*)
echo "error: unknown action '${ACTION}'; expected to-pgbackrest or to-postgres" >&2
exit 1
;;
esac
44 changes: 44 additions & 0 deletions ansible/files/supabase_admin_agent_config/pgdata-signal
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# pgdata-signal — creates or removes PostgreSQL signal files and stale pid files.
# Called via sudo (as postgres) by supabase-admin-agent (running as adminapi).
#
# All file paths are hardcoded to prevent path injection. No external
# path argument is accepted.
#
# Usage: pgdata-signal <create|remove> <recovery|standby>
# pgdata-signal remove-pid
set -euo pipefail

# Special-case: remove-pid removes the stale postmaster.pid file that would
# prevent PostgreSQL from starting after a restore. Handled as a single-arg
# command to keep the sudoers entry scoped to this script rather than allowing
# a broad "rm" entry.
if [[ $# -eq 1 && "$1" == "remove-pid" ]]; then
exec /usr/bin/rm -f "/data/pgdata/postmaster.pid"
fi

if [[ $# -ne 2 ]]; then
echo "usage: pgdata-signal <create|remove> <recovery|standby>" >&2
echo " pgdata-signal remove-pid" >&2
exit 1
fi

ACTION="$1"
SIGNAL_TYPE="$2"

case "$SIGNAL_TYPE" in
recovery|standby) FILE="/data/pgdata/${SIGNAL_TYPE}.signal" ;;
*)
echo "error: unknown signal type '${SIGNAL_TYPE}'; expected recovery or standby" >&2
exit 1
;;
esac

case "$ACTION" in
create) exec /usr/bin/touch "${FILE}" ;;
remove) exec /usr/bin/rm -f "${FILE}" ;;
*)
echo "error: unknown action '${ACTION}'; expected create or remove" >&2
exit 1
;;
esac
1 change: 1 addition & 0 deletions ansible/tasks/finalize-ami.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
- { file: 'logrotate-postgres-auth.conf' }
- { file: 'logrotate-postgres-csv.conf' }
- { file: 'logrotate-walg.conf' }
- { file: 'logrotate-pgbackrest.conf' }
loop_control:
loop_var: 'logrotate_item'

Expand Down
27 changes: 27 additions & 0 deletions ansible/tasks/internal/supabase-admin-agent.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,33 @@
dest: /etc/sudoers.d/supabase-admin-agent
mode: "0440"

- name: supabase-admin-agent - pgbackrest helper scripts dir
file:
path: /usr/local/lib/supabase-admin-agent
state: directory
owner: root
group: root
mode: "0755"

- name: supabase-admin-agent - pgdata-chown script
copy:
src: files/supabase_admin_agent_config/pgdata-chown
dest: /usr/local/lib/supabase-admin-agent/pgdata-chown
owner: root
group: root
# 0700: always invoked via "sudo" (as root); no other user needs execute.
mode: "0700"

- name: supabase-admin-agent - pgdata-signal script
copy:
src: files/supabase_admin_agent_config/pgdata-signal
dest: /usr/local/lib/supabase-admin-agent/pgdata-signal
owner: root
group: root
# 0755: invoked via "sudo -u postgres"; the postgres OS user needs the
# world-execute bit since it is neither owner nor group member.
mode: "0755"

- name: Setting arch (x86)
set_fact:
arch: "x86"
Expand Down
40 changes: 34 additions & 6 deletions ansible/tasks/setup-pgbackrest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@
- 'postgres ALL=(pgbackrest) NOPASSWD: /usr/bin/bash'
- 'postgres ALL=(pgbackrest) NOPASSWD: /usr/bin/nix'
- 'pgbackrest ALL=(pgbackrest) NOPASSWD: /usr/bin/bash'
# Required for the NewRunnerAs() path: supabase-admin-agent runs the
# /usr/bin/pgbackrest wrapper as the pgbackrest user, but the wrapper
# internally calls "sudo -u pgbackrest <real_binary>". Without this entry
# that inner sudo fails even though the outer caller is already pgbackrest.
- 'pgbackrest ALL=(pgbackrest) NOPASSWD: /var/lib/pgbackrest/.nix-profile/bin/pgbackrest'

- name: Install pgBackRest
ansible.builtin.shell: |
Expand All @@ -43,20 +48,43 @@
- name: Create needed directories for pgBackRest
ansible.legacy.file:
group: postgres
mode: '0770'
mode: "{{ backrest_dir['mode'] | default('0770', true) }}"
owner: pgbackrest
path: "{{ backrest_dir }}"
path: "{{ backrest_dir['dir'] }}"
state: directory
loop:
- /etc/pgbackrest/conf.d
- /var/lib/pgbackrest
- /var/spool/pgbackrest
- /var/log/pgbackrest
# conf.d is setgid (02770) so files written by adminapi (postgres group)
# automatically inherit the postgres group. Without setgid, pgbackrest
# (running as the pgbackrest user) cannot read conf files created by adminapi.
- {dir: /etc/pgbackrest/conf.d, mode: '02770'}
- {dir: /var/lib/pgbackrest}
- {dir: /var/spool/pgbackrest}
- {dir: /var/log/pgbackrest}
loop_control:
loop_var: backrest_dir
when:
- nixpkg_mode

# supabase-admin-agent opens these log files with O_APPEND|O_WRONLY (no
# O_CREATE). They must exist before SAA runs; a missing file causes the
# pgBackRest enable command to fail before any backup work is attempted.
# access_time/modification_time: preserve keeps this task idempotent.
- name: Pre-create pgBackRest SAA log files
ansible.builtin.file:
access_time: preserve
group: postgres
mode: '0660'
modification_time: preserve
owner: pgbackrest
path: "{{ item }}"
state: touch
loop:
- /var/log/pgbackrest/saa-pgb.log
- /var/log/pgbackrest/wal-push.log
- /var/log/pgbackrest/wal-fetch.log
when:
- nixpkg_mode

- name: Symlink pgbackrest.conf
ansible.legacy.file:
force: true
Expand Down
Loading