From 357a124a540ddfb40023211f7eddaf24c1f08942 Mon Sep 17 00:00:00 2001 From: Crispy1975 <12525875+Crispy1975@users.noreply.github.com> Date: Sat, 11 Jul 2026 08:59:49 +0100 Subject: [PATCH 1/7] feat(wal-g): install wal-g 3.0.8 alongside v2 (INDATA-904) Adds a wal-g-3 nix package (v3.0.8) built and installed next to wal-g-2. The /usr/local/bin/wal-g symlink stays on v2, so v3 is available as wal-g-3 for backward-compat validation without changing the default. wal-g 3.x imports encoding/json/v2 directly, so the build needs GOEXPERIMENT=jsonv2 under Go 1.25 (threaded through walGCommon as a per-package goExperiment knob; v2 unaffected). --- .../files/postgresql_config/sbpostgres_apparmor | 2 ++ ansible/tasks/setup-wal-g.yml | 7 +++++++ nix/checks.nix | 1 + nix/packages/default.nix | 2 +- nix/packages/wal-g.nix | 17 +++++++++++++++++ testinfra/test_ami_nix.py | 16 +++++++++------- 6 files changed, 37 insertions(+), 8 deletions(-) diff --git a/ansible/files/postgresql_config/sbpostgres_apparmor b/ansible/files/postgresql_config/sbpostgres_apparmor index af35e9bb59..ea59999849 100644 --- a/ansible/files/postgresql_config/sbpostgres_apparmor +++ b/ansible/files/postgresql_config/sbpostgres_apparmor @@ -66,6 +66,7 @@ profile sbpostgres flags=(attach_disconnected) { /bin/cat Pix -> postgres_shell, /usr/bin/admin-mgr Pix -> postgres_shell, /nix/store/*/bin/wal-g-2 Pix -> postgres_shell, + /nix/store/*/bin/wal-g-3 Pix -> postgres_shell, /nix/store/*/bin/pgbackrest Pix -> postgres_shell, /nix/store/*/bin/pg_dump ix, /usr/bin/pgbackrest Pix -> pgbackrest_shell, @@ -107,6 +108,7 @@ profile sbpostgres flags=(attach_disconnected) { /opt/supabase-admin-agent/supabase-admin-agent-linux-amd64 ix, /nix/store/*/bin/.postgres-wrapped ix, /nix/store/*/bin/wal-g-2 ix, + /nix/store/*/bin/wal-g-3 ix, /nix/store/*/bin/pgbackrest ix, /nix/store/*/bin/pg_dump ix, /nix/store/*/bin/pg_archivecleanup ix, diff --git a/ansible/tasks/setup-wal-g.yml b/ansible/tasks/setup-wal-g.yml index 0d923cd05d..16379b5083 100644 --- a/ansible/tasks/setup-wal-g.yml +++ b/ansible/tasks/setup-wal-g.yml @@ -33,6 +33,13 @@ cmd: sudo -u wal-g bash -c ". /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && nix profile install github:supabase/postgres/{{ git_commit_sha }}#wal-g-2" become: true + # Installed alongside v2 (INDATA-904). The /usr/local/bin/wal-g symlink below + # still points at v2, so v3 is available (as wal-g-3) but not yet the default. + - name: Install wal-g 3 from nix binary cache + ansible.builtin.shell: + cmd: sudo -u wal-g bash -c ". /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && nix profile install github:supabase/postgres/{{ git_commit_sha }}#wal-g-3" + become: true + - name: nix collect garbage ansible.builtin.shell: cmd: sudo -u ubuntu bash -c ". /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && nix-collect-garbage -d" diff --git a/nix/checks.nix b/nix/checks.nix index 776491bc80..45ba024968 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -933,6 +933,7 @@ supabase-cli supascan wal-g-2 + wal-g-3 ; devShell = self'.devShells.default; } diff --git a/nix/packages/default.nix b/nix/packages/default.nix index a452a9b784..a8df80b26b 100644 --- a/nix/packages/default.nix +++ b/nix/packages/default.nix @@ -113,7 +113,7 @@ inherit (pkgs) yq; postgresql_15 = self'.packages."postgresql_15"; }; - inherit (pkgs.callPackage ./wal-g.nix { }) wal-g-2; + inherit (pkgs.callPackage ./wal-g.nix { }) wal-g-2 wal-g-3; inherit (supascan-pkgs) goss supascan supascan-specs; inherit (pg-startup-profiler-pkgs) pg-startup-profiler; inherit (pkgs.cargo-pgrx) diff --git a/nix/packages/wal-g.nix b/nix/packages/wal-g.nix index 07e6eb5720..a18cffb708 100644 --- a/nix/packages/wal-g.nix +++ b/nix/packages/wal-g.nix @@ -14,6 +14,9 @@ let vendorHash, sha256, majorVersion, + # Extra GOEXPERIMENT flags. wal-g 3.x imports encoding/json/v2 directly + # (internal/uploader.go), which Go 1.25 gates behind GOEXPERIMENT=jsonv2. + goExperiment ? null, }: buildGoModule rec { pname = "wal-g-${majorVersion}"; @@ -28,6 +31,10 @@ let inherit vendorHash; + env = lib.optionalAttrs (goExperiment != null) { + GOEXPERIMENT = goExperiment; + }; + nativeBuildInputs = [ installShellFiles ]; buildInputs = [ @@ -77,4 +84,14 @@ in vendorHash = "sha256-BbQuY6r30AkxlCZjY8JizaOrqEBdv7rIQet9KQwYB/g="; majorVersion = "2"; }; + + # wal-g v3.0.8 — installed alongside v2 (INDATA-904). + # OrioleDB support + backward-compat validation; binary is wal-g-3. + wal-g-3 = walGCommon { + version = "3.0.8"; + sha256 = "sha256-iDLC3Td/U1msqdpUbJWS+MBDznx7NbddGWFP4rrfSus="; + vendorHash = "sha256-K2J/Hi8TQs+UhudgTWsAmPUHKnwKP3cmx21CvDTjs6M="; + majorVersion = "3"; + goExperiment = "jsonv2"; + }; } diff --git a/testinfra/test_ami_nix.py b/testinfra/test_ami_nix.py index 85d3497ac0..da61f64f8f 100644 --- a/testinfra/test_ami_nix.py +++ b/testinfra/test_ami_nix.py @@ -1345,19 +1345,21 @@ def test_apparmor_allows_pg_dump(host): ) -def test_apparmor_allows_walg(host): - """Verify wal-g-2 can be executed under the sbpostgres AppArmor profile. +@pytest.mark.parametrize("walg_binary", ["wal-g-2", "wal-g-3"]) +def test_apparmor_allows_walg(host, walg_binary): + """Verify wal-g-2 and wal-g-3 can be executed under the sbpostgres AppArmor profile. - /nix/store/*/bin/wal-g-2 is listed as 'ix' in postgres_shell. We locate the - binary at runtime since the Nix store hash is not known ahead of time. + /nix/store/*/bin/wal-g-2 and /nix/store/*/bin/wal-g-3 are listed as 'ix' in + postgres_shell. We locate the binary at runtime since the Nix store hash is + not known ahead of time. """ find_result = run_ssh_command( host["ssh"], - "find /nix/store -maxdepth 3 -name 'wal-g-2' -type f 2>/dev/null | head -1", + f"find /nix/store -maxdepth 3 -name '{walg_binary}' -type f 2>/dev/null | head -1", ) walg_path = find_result["stdout"].strip() if not walg_path: - print("wal-g-2 not found in Nix store, skipping") + print(f"{walg_binary} not found in Nix store, skipping") return result = run_ssh_command( @@ -1366,7 +1368,7 @@ def test_apparmor_allows_walg(host): f"\"COPY (SELECT 1) TO PROGRAM '{walg_path} --version';\"", ) assert result["succeeded"], ( - f"wal-g-2 was blocked by AppArmor.\n" + f"{walg_binary} was blocked by AppArmor.\n" f"stdout: {result['stdout']}\nstderr: {result['stderr']}" ) From 3fa4558329289a0b8139dd79cfc1430620ea8702 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Thu, 23 Jul 2026 12:04:19 -0400 Subject: [PATCH 2/7] feat: wal-g v3 with latest patches for oriole compat --- nix/packages/wal-g.nix | 6 ++- ...-update-page-header-format-to-beta13.patch | 48 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 nix/packages/wal-g/0001-orioledb-update-page-header-format-to-beta13.patch diff --git a/nix/packages/wal-g.nix b/nix/packages/wal-g.nix index a18cffb708..7b6b378909 100644 --- a/nix/packages/wal-g.nix +++ b/nix/packages/wal-g.nix @@ -17,6 +17,7 @@ let # Extra GOEXPERIMENT flags. wal-g 3.x imports encoding/json/v2 directly # (internal/uploader.go), which Go 1.25 gates behind GOEXPERIMENT=jsonv2. goExperiment ? null, + patches ? [ ], }: buildGoModule rec { pname = "wal-g-${majorVersion}"; @@ -29,7 +30,7 @@ let inherit sha256; }; - inherit vendorHash; + inherit vendorHash patches; env = lib.optionalAttrs (goExperiment != null) { GOEXPERIMENT = goExperiment; @@ -93,5 +94,8 @@ in vendorHash = "sha256-K2J/Hi8TQs+UhudgTWsAmPUHKnwKP3cmx21CvDTjs6M="; majorVersion = "3"; goExperiment = "jsonv2"; + # Backport wal-g/wal-g#2112: update OrioleDB incremental-backup page + # header parsing to match the beta13 page format. + patches = [ ./wal-g/0001-orioledb-update-page-header-format-to-beta13.patch ]; }; } diff --git a/nix/packages/wal-g/0001-orioledb-update-page-header-format-to-beta13.patch b/nix/packages/wal-g/0001-orioledb-update-page-header-format-to-beta13.patch new file mode 100644 index 0000000000..0daa7cf43e --- /dev/null +++ b/nix/packages/wal-g/0001-orioledb-update-page-header-format-to-beta13.patch @@ -0,0 +1,48 @@ +diff --git a/internal/databases/postgres/orioledb/incremental_page_reader.go b/internal/databases/postgres/orioledb/incremental_page_reader.go +index a53786b760..45ad7e256c 100644 +--- a/internal/databases/postgres/orioledb/incremental_page_reader.go ++++ b/internal/databases/postgres/orioledb/incremental_page_reader.go +@@ -224,20 +224,7 @@ func (pageReader *incrementalPageReader) WriteDiffMapToHeader(headerWriter io.Wr + } + + type pageHeader struct { +- state uint32 +- usageCount uint32 +- pageChangeCount uint32 +- checkpointNum uint32 +- undoLocation uint64 +- csn uint64 +- rightLink uint64 +- flagsField1Field2 uint32 +- maxKeyLen uint16 +- prevInsertOffset uint16 +- chunksCount uint16 +- itemsCount uint16 +- hikeysEnd uint16 +- dataSize uint16 ++ checkpointNum uint32 + } + + func (header *pageHeader) isValid() bool { +@@ -249,21 +236,7 @@ func (header *pageHeader) isValid() bool { + func parsePageHeader(reader io.Reader) (*pageHeader, error) { + pageHeader := pageHeader{} + fields := []parsingutil.FieldToParse{ +- {Field: &pageHeader.state, Name: "state"}, +- {Field: &pageHeader.usageCount, Name: "usageCount"}, +- {Field: &pageHeader.pageChangeCount, Name: "pageChangeCount"}, +- + {Field: &pageHeader.checkpointNum, Name: "checkpointNum"}, +- {Field: &pageHeader.undoLocation, Name: "undoLocation"}, +- {Field: &pageHeader.csn, Name: "csn"}, +- {Field: &pageHeader.rightLink, Name: "rightLink"}, +- {Field: &pageHeader.flagsField1Field2, Name: "flagsField1Field2"}, +- {Field: &pageHeader.maxKeyLen, Name: "maxKeyLen"}, +- {Field: &pageHeader.prevInsertOffset, Name: "prevInsertOffset"}, +- {Field: &pageHeader.chunksCount, Name: "chunksCount"}, +- {Field: &pageHeader.itemsCount, Name: "itemsCount"}, +- {Field: &pageHeader.hikeysEnd, Name: "hikeysEnd"}, +- {Field: &pageHeader.dataSize, Name: "dataSize"}, + } + err := parsingutil.ParseMultipleFieldsFromReader(fields, reader) + if err != nil { From 4356432c95455dfa405068b289a4914e8de85853 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Thu, 23 Jul 2026 13:08:47 -0400 Subject: [PATCH 3/7] fix: if orioledb alias wal-g v3 instead of v2 --- ansible/tasks/setup-wal-g.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ansible/tasks/setup-wal-g.yml b/ansible/tasks/setup-wal-g.yml index 16379b5083..4249dab4e8 100644 --- a/ansible/tasks/setup-wal-g.yml +++ b/ansible/tasks/setup-wal-g.yml @@ -34,7 +34,7 @@ become: true # Installed alongside v2 (INDATA-904). The /usr/local/bin/wal-g symlink below - # still points at v2, so v3 is available (as wal-g-3) but not yet the default. + # only points at v3 on orioledb builds; v2 remains the default everywhere else. - name: Install wal-g 3 from nix binary cache ansible.builtin.shell: cmd: sudo -u wal-g bash -c ". /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && nix profile install github:supabase/postgres/{{ git_commit_sha }}#wal-g-3" @@ -45,11 +45,11 @@ cmd: sudo -u ubuntu bash -c ". /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && nix-collect-garbage -d" become: true - - name: Create symlink to make wal-g-v2 the default wal-g + - name: Create symlink to make wal-g the default (v3 on orioledb builds, v2 otherwise) ansible.builtin.file: dest: '/usr/local/bin/wal-g' force: true - src: '/home/wal-g/.nix-profile/bin/wal-g-2' + src: "/home/wal-g/.nix-profile/bin/wal-g-{{ '3' if is_psql_oriole else '2' }}" state: 'link' become: true From 11527683d9d55ac61700f99dfaabfed62f1c0915 Mon Sep 17 00:00:00 2001 From: Crispy1975 <12525875+Crispy1975@users.noreply.github.com> Date: Fri, 21 Aug 2026 10:38:47 +0100 Subject: [PATCH 4/7] test: xfail wal-g-3 apparmor check until the salt profile carries it --- testinfra/test_ami_nix.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/testinfra/test_ami_nix.py b/testinfra/test_ami_nix.py index cfd77554d6..07f5b3ee34 100644 --- a/testinfra/test_ami_nix.py +++ b/testinfra/test_ami_nix.py @@ -1345,7 +1345,21 @@ def test_apparmor_allows_pg_dump(host): ) -@pytest.mark.parametrize("walg_binary", ["wal-g-2", "wal-g-3"]) +@pytest.mark.parametrize( + "walg_binary", + [ + "wal-g-2", + pytest.param( + "wal-g-3", + marks=pytest.mark.xfail( + reason="salt highstate at boot replaces /etc/apparmor.d/sbpostgres " + "with a copy lacking the wal-g-3 rules; remove once the salt " + "formula profile carries them", + strict=False, + ), + ), + ], +) def test_apparmor_allows_walg(host, walg_binary): """Verify wal-g-2 and wal-g-3 can be executed under the sbpostgres AppArmor profile. From ebbb9956cea4e022009774e1687bc47cd8327735 Mon Sep 17 00:00:00 2001 From: Crispy1975 <12525875+Crispy1975@users.noreply.github.com> Date: Fri, 21 Aug 2026 10:50:57 +0100 Subject: [PATCH 5/7] test: disable boot-time salt in testinfra so CI validates the AMI's own config --- testinfra/test_ami_nix.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/testinfra/test_ami_nix.py b/testinfra/test_ami_nix.py index 07f5b3ee34..f962ac9749 100644 --- a/testinfra/test_ami_nix.py +++ b/testinfra/test_ami_nix.py @@ -331,6 +331,9 @@ def gzip_then_base64_encode(s: str) -> str: {"Key": "Name", "Value": "ci-ami-test-nix"}, {"Key": "creator", "Value": "testinfra-ci"}, {"Key": "testinfra-run-id", "Value": RUN_ID}, + # Keep the boot-time salt run (supabase-admin-agent) off + # so tests validate the AMI's own config, not salt's. + {"Key": "salt-enabled", "Value": "false"}, ], } ], @@ -1345,21 +1348,7 @@ def test_apparmor_allows_pg_dump(host): ) -@pytest.mark.parametrize( - "walg_binary", - [ - "wal-g-2", - pytest.param( - "wal-g-3", - marks=pytest.mark.xfail( - reason="salt highstate at boot replaces /etc/apparmor.d/sbpostgres " - "with a copy lacking the wal-g-3 rules; remove once the salt " - "formula profile carries them", - strict=False, - ), - ), - ], -) +@pytest.mark.parametrize("walg_binary", ["wal-g-2", "wal-g-3"]) def test_apparmor_allows_walg(host, walg_binary): """Verify wal-g-2 and wal-g-3 can be executed under the sbpostgres AppArmor profile. From e1b74aaa2d13e4e05f978dee41585d4006b5d0af Mon Sep 17 00:00:00 2001 From: Crispy1975 <12525875+Crispy1975@users.noreply.github.com> Date: Fri, 21 Aug 2026 11:06:19 +0100 Subject: [PATCH 6/7] chore: bump release vars for wal-g v3 rc AMI build --- ansible/vars.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ansible/vars.yml b/ansible/vars.yml index 930129c95e..f49c0ffbd8 100644 --- a/ansible/vars.yml +++ b/ansible/vars.yml @@ -11,9 +11,9 @@ postgres_major: # This is the source of truth for Postgres versions used in the Dockerfiles, and # is used to derive image tags and base images in the release matrix. postgres_release: - postgresorioledb-17: "17.9.0.018-orioledb" - postgres17: "17.6.1.165" - postgres15: "15.14.1.165" + postgresorioledb-17: "17.9.0.019-orioledb-rc20260821" + postgres17: "17.6.1.166-rc20260821" + postgres15: "15.14.1.166-rc20260821" # Docker release matrix — base images built first, layered images built on top. # tag and base_tag are derived at build time from postgres_release via release_key. # tag_suffix is appended to the release version to form the final image tag. From 9d50fff44a6a6a3f261c47d571e18536f787c449 Mon Sep 17 00:00:00 2001 From: Crispy1975 <12525875+Crispy1975@users.noreply.github.com> Date: Fri, 21 Aug 2026 12:15:57 +0100 Subject: [PATCH 7/7] test: expose instance tags via IMDS so the salt opt-out tag is honoured --- testinfra/test_ami_nix.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/testinfra/test_ami_nix.py b/testinfra/test_ami_nix.py index f962ac9749..c5d7885192 100644 --- a/testinfra/test_ami_nix.py +++ b/testinfra/test_ami_nix.py @@ -289,6 +289,8 @@ def gzip_then_base64_encode(s: str) -> str: MetadataOptions={ "HttpTokens": "required", "HttpEndpoint": "enabled", + # supabase-admin-agent reads the salt-enabled opt-out tag via IMDS + "InstanceMetadataTags": "enabled", }, IamInstanceProfile={"Name": "pg-ap-southeast-1"}, InstanceType="t4g.micro" if image.architecture == "arm64" else "t3.small",