From 99ac48b06a2c744dba71f2afb17ff54957f301bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Tue, 8 Sep 2026 20:21:34 +0300 Subject: [PATCH 01/18] feat(nix): add site-update tool Given a git sha, pg major, and system, fetches the site-env- catalog entry and flips /nix/var/nix/profiles/site via nix-env --set. Skips realise+set if already current. Fixes MPG-12. --- nix/packages/extension-catalog.nix | 2 +- nix/packages/site-env.nix | 32 ++++++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/nix/packages/extension-catalog.nix b/nix/packages/extension-catalog.nix index d8d5591216..c10a5fd9b9 100644 --- a/nix/packages/extension-catalog.nix +++ b/nix/packages/extension-catalog.nix @@ -158,7 +158,7 @@ manifest="''${1:?Usage: $0 path-to/pg-extensions.json}" profile="''${NIX_PROFILE:-/nix/var/nix/profiles/site-extensions}" readarray -t paths < <(site-extensions-resolve "$manifest") - nix-store -r --option stalled-download-timeout 120 "''${paths[@]}" >/dev/null + nix-store --realise --option stalled-download-timeout 120 "''${paths[@]}" >/dev/null nix-env --profile "$profile" --install "''${paths[@]}" --remove-all ''; }; diff --git a/nix/packages/site-env.nix b/nix/packages/site-env.nix index 4fa580e99a..525d0143b5 100644 --- a/nix/packages/site-env.nix +++ b/nix/packages/site-env.nix @@ -28,9 +28,37 @@ lib.optionals pkgs.stdenv.isLinux [ self'.packages.gatekeeper ] ); }; + + # Given a git sha, pg major, and system, fetches the site-env catalog entry + site-update = pkgs.writeShellApplication { + name = "site-update"; + runtimeInputs = [ + pkgs.awscli2 + pkgs.jq + pkgs.nix + ]; + text = '' + sha="''${1:?Usage: $0 }" + major="''${2:?Usage: $0 }" + system="''${3:?Usage: $0 }" + + catalog="/tmp/site-env-catalog-''${sha}-''${major}-''${system}.json" + aws s3 cp "s3://supabase-internal-artifacts/nix-catalog/''${sha}-site-env_''${major}-''${system}.json" \ + "$catalog" --region ap-southeast-1 + + path="$(jq -er --arg s "$system" '.[$s]' "$catalog")" + [[ "$(readlink -f /nix/var/nix/profiles/site)" == "$path" ]] && exit 0 + nix-store --realise --option stalled-download-timeout 120 "$path" >/dev/null + nix-env --profile /nix/var/nix/profiles/site --set "$path" + ''; + }; in { - packages = siteEnvs; - legacyPackages = siteEnvs; + packages = siteEnvs // { + inherit site-update; + }; + legacyPackages = siteEnvs // { + inherit site-update; + }; }; } From 1b18ce25558c410c890726ccb41e545d8b1a4dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 01:56:10 +0300 Subject: [PATCH 02/18] ci: publish site-env catalogs, extend release path filter Extends ami-release-nix.yml's existing catalog step to also publish site-env- and site-update catalogs. Adds nix/** to the release trigger path filter. MPG-15 --- .github/workflows/ami-release-nix.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/ami-release-nix.yml b/.github/workflows/ami-release-nix.yml index 773aee018c..d9d9a7cfc6 100644 --- a/.github/workflows/ami-release-nix.yml +++ b/.github/workflows/ami-release-nix.yml @@ -11,6 +11,7 @@ on: - flake.lock - flake.nix - nix/packages/build-ami.nix + - nix/** workflow_dispatch: permissions: @@ -203,6 +204,23 @@ jobs: echo "Catalog uploaded to ${CATALOG_S3}" + - name: Update site-env catalogs + run: | + GIT_SHA="${{ steps.resolve-git-sha.outputs.sha }}" + SYSTEM=$(nix eval --impure --raw --expr 'builtins.currentSystem') + + SITE_ENV_PATH=$(nix eval --raw ".#site-env-${POSTGRES_MAJOR_VERSION}.outPath") + jq -n --arg sys "$SYSTEM" --arg path "$SITE_ENV_PATH" '{($sys): $path}' > /tmp/site-env-catalog.json + aws s3 cp /tmp/site-env-catalog.json \ + "s3://${{ secrets.SHARED_AWS_ARTIFACTS_BUCKET }}/nix-catalog/${GIT_SHA}-site-env_${POSTGRES_MAJOR_VERSION}-${SYSTEM}.json" \ + --content-type "application/json" + + SITE_UPDATE_PATH=$(nix eval --raw ".#site-update.outPath") + jq -n --arg sys "$SYSTEM" --arg path "$SITE_UPDATE_PATH" '{($sys): $path}' > /tmp/site-update-catalog.json + aws s3 cp /tmp/site-update-catalog.json \ + "s3://${{ secrets.SHARED_AWS_ARTIFACTS_BUCKET }}/nix-catalog/${GIT_SHA}-site-update-${SYSTEM}.json" \ + --content-type "application/json" + - name: Create release uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0 with: From 730068b7d24978eaae2a89f229771aec8ce7197e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 01:56:10 +0300 Subject: [PATCH 03/18] feat(nix): add site-update tool Given a git sha and pg major, fetches the site-env- catalog entry and flips /nix/var/nix/profiles/site- via nix-env --set. System arch is inferred (uname), never a footgun to pass wrong. Per-major profile so an in-flight pg upgrade can prep the new major's site-env without touching the old one's live profile. Verifies the resolved store path is actually tagged for the requested major before setting it live. Skips realise+set if already current. Fixes MPG-12. --- ansible/tasks/stage2-setup-postgres.yml | 11 +++++++++++ nix/packages/site-env.nix | 20 +++++++++++++------- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/ansible/tasks/stage2-setup-postgres.yml b/ansible/tasks/stage2-setup-postgres.yml index d4823238a7..529c8995e8 100644 --- a/ansible/tasks/stage2-setup-postgres.yml +++ b/ansible/tasks/stage2-setup-postgres.yml @@ -77,6 +77,17 @@ nix-env --set {{ postgres_env_path.stdout }} " + - name: Resolve site-update store path + ansible.builtin.shell: | + . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && + nix build --no-link --print-out-paths github:supabase/postgres/{{ git_commit_sha }}#site-update + register: site_update_path + + - name: Install site-update + ansible.builtin.shell: | + . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && + nix-env --profile /nix/var/nix/profiles/site-update --set {{ site_update_path.stdout }} + - name: Install supascan for baseline validation ansible.builtin.shell: | sudo -u ubuntu bash -c ". /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && nix profile install github:supabase/postgres/{{ git_commit_sha }}#supascan" diff --git a/nix/packages/site-env.nix b/nix/packages/site-env.nix index 525d0143b5..66bfbfe3a1 100644 --- a/nix/packages/site-env.nix +++ b/nix/packages/site-env.nix @@ -1,5 +1,5 @@ # These are envs (package sets per pg major version) deployed to instances -# at /nix/var/nix/profiles/site and updated regularly. +# at /nix/var/nix/profiles/site- and updated regularly. { perSystem = { @@ -29,7 +29,7 @@ ); }; - # Given a git sha, pg major, and system, fetches the site-env catalog entry + # Given a git sha and pg major, fetches the site-env catalog entry site-update = pkgs.writeShellApplication { name = "site-update"; runtimeInputs = [ @@ -38,18 +38,24 @@ pkgs.nix ]; text = '' - sha="''${1:?Usage: $0 }" - major="''${2:?Usage: $0 }" - system="''${3:?Usage: $0 }" + sha="''${1:?Usage: $0 }" + major="''${2:?Usage: $0 }" + system="$(uname -m)-linux" + profile="/nix/var/nix/profiles/site-''${major}" catalog="/tmp/site-env-catalog-''${sha}-''${major}-''${system}.json" aws s3 cp "s3://supabase-internal-artifacts/nix-catalog/''${sha}-site-env_''${major}-''${system}.json" \ "$catalog" --region ap-southeast-1 path="$(jq -er --arg s "$system" '.[$s]' "$catalog")" - [[ "$(readlink -f /nix/var/nix/profiles/site)" == "$path" ]] && exit 0 + [[ "$(basename "$path")" == *"-site-env-''${major}" ]] || { + echo "error: resolved path $path is not tagged for major $major" >&2 + exit 1 + } + + [[ "$(readlink -f "$profile")" == "$path" ]] && exit 0 nix-store --realise --option stalled-download-timeout 120 "$path" >/dev/null - nix-env --profile /nix/var/nix/profiles/site --set "$path" + nix-env --profile "$profile" --set "$path" ''; }; in From bcdd996eb9e2a87fa3d47dc4d9af86f56b1a7e07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 17:29:01 +0300 Subject: [PATCH 04/18] test(nix): add minimal NixOS VM test for site-update SITE_UPDATE_CATALOG env var lets tests bypass the S3 fetch with a local catalog file. Covers happy path, idempotent re-run, and refusal on a major-tag mismatch. --- nix/ext/tests/site-update.nix | 28 ++++++++++++++++++++++++++++ nix/packages/site-env.nix | 9 ++++++--- 2 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 nix/ext/tests/site-update.nix diff --git a/nix/ext/tests/site-update.nix b/nix/ext/tests/site-update.nix new file mode 100644 index 0000000000..0e3052b3ac --- /dev/null +++ b/nix/ext/tests/site-update.nix @@ -0,0 +1,28 @@ +{ self, pkgs }: +let + system = pkgs.pkgsLinux.stdenv.hostPlatform.system; + site-update = self.packages.${system}.site-update; + site-env-17 = self.packages.${system}."site-env-17"; +in +pkgs.testers.runNixOSTest { + name = "site-update"; + nodes.machine = + { ... }: + { + environment.systemPackages = [ + site-update + site-env-17 + ]; + }; + testScript = '' + machine.succeed("echo '{\"${system}\": \"${site-env-17}\"}' > /tmp/catalog.json") + machine.succeed("SITE_UPDATE_CATALOG=/tmp/catalog.json site-update deadbeef 17") + machine.succeed("[ \"$(readlink -f /nix/var/nix/profiles/site-17)\" = \"${site-env-17}\" ]") + + # idempotent: same catalog again is a no-op success + machine.succeed("SITE_UPDATE_CATALOG=/tmp/catalog.json site-update deadbeef 17") + + # wrong major for the resolved path: must refuse + machine.fail("SITE_UPDATE_CATALOG=/tmp/catalog.json site-update deadbeef 15") + ''; +} diff --git a/nix/packages/site-env.nix b/nix/packages/site-env.nix index 66bfbfe3a1..ce2db3000b 100644 --- a/nix/packages/site-env.nix +++ b/nix/packages/site-env.nix @@ -43,9 +43,12 @@ system="$(uname -m)-linux" profile="/nix/var/nix/profiles/site-''${major}" - catalog="/tmp/site-env-catalog-''${sha}-''${major}-''${system}.json" - aws s3 cp "s3://supabase-internal-artifacts/nix-catalog/''${sha}-site-env_''${major}-''${system}.json" \ - "$catalog" --region ap-southeast-1 + catalog="''${SITE_UPDATE_CATALOG:-}" + if [[ -z "$catalog" ]]; then + catalog="/tmp/site-env-catalog-''${sha}-''${major}-''${system}.json" + aws s3 cp "s3://supabase-internal-artifacts/nix-catalog/''${sha}-site-env_''${major}-''${system}.json" \ + "$catalog" --region ap-southeast-1 + fi path="$(jq -er --arg s "$system" '.[$s]' "$catalog")" [[ "$(basename "$path")" == *"-site-env-''${major}" ]] || { From 430559e340d448ff21c2836205713603659fb370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 18:11:15 +0300 Subject: [PATCH 05/18] feat(nix): unify site-update around a generic arg Drop the major-specific interface: site-update now takes (e.g. site-env-17, postgres-env-17) instead of , so the same tool works for any single-package catalog entry, not just site-env. Profile becomes /nix/var/nix/profiles/ directly. Catalog S3 key simplifies to --.json. postgres-env- packages already exist (nix/packages/postgres-env.nix); publishing their catalog and switching ansible off the live 'nix build github:...' resolution is a follow-up, not yet done here. --- .github/workflows/ami-release-nix.yml | 5 +++-- nix/ext/tests/site-update.nix | 10 +++++----- nix/packages/site-env.nix | 20 +++++++++++--------- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ami-release-nix.yml b/.github/workflows/ami-release-nix.yml index d9d9a7cfc6..d8ceb703e6 100644 --- a/.github/workflows/ami-release-nix.yml +++ b/.github/workflows/ami-release-nix.yml @@ -209,10 +209,11 @@ jobs: GIT_SHA="${{ steps.resolve-git-sha.outputs.sha }}" SYSTEM=$(nix eval --impure --raw --expr 'builtins.currentSystem') - SITE_ENV_PATH=$(nix eval --raw ".#site-env-${POSTGRES_MAJOR_VERSION}.outPath") + SITE_ENV_NAME="site-env-${POSTGRES_MAJOR_VERSION}" + SITE_ENV_PATH=$(nix eval --raw ".#${SITE_ENV_NAME}.outPath") jq -n --arg sys "$SYSTEM" --arg path "$SITE_ENV_PATH" '{($sys): $path}' > /tmp/site-env-catalog.json aws s3 cp /tmp/site-env-catalog.json \ - "s3://${{ secrets.SHARED_AWS_ARTIFACTS_BUCKET }}/nix-catalog/${GIT_SHA}-site-env_${POSTGRES_MAJOR_VERSION}-${SYSTEM}.json" \ + "s3://${{ secrets.SHARED_AWS_ARTIFACTS_BUCKET }}/nix-catalog/${GIT_SHA}-${SITE_ENV_NAME}-${SYSTEM}.json" \ --content-type "application/json" SITE_UPDATE_PATH=$(nix eval --raw ".#site-update.outPath") diff --git a/nix/ext/tests/site-update.nix b/nix/ext/tests/site-update.nix index 0e3052b3ac..4971b26275 100644 --- a/nix/ext/tests/site-update.nix +++ b/nix/ext/tests/site-update.nix @@ -16,13 +16,13 @@ pkgs.testers.runNixOSTest { }; testScript = '' machine.succeed("echo '{\"${system}\": \"${site-env-17}\"}' > /tmp/catalog.json") - machine.succeed("SITE_UPDATE_CATALOG=/tmp/catalog.json site-update deadbeef 17") - machine.succeed("[ \"$(readlink -f /nix/var/nix/profiles/site-17)\" = \"${site-env-17}\" ]") + machine.succeed("SITE_UPDATE_CATALOG=/tmp/catalog.json site-update deadbeef site-env-17") + machine.succeed("[ \"$(readlink -f /nix/var/nix/profiles/site-env-17)\" = \"${site-env-17}\" ]") # idempotent: same catalog again is a no-op success - machine.succeed("SITE_UPDATE_CATALOG=/tmp/catalog.json site-update deadbeef 17") + machine.succeed("SITE_UPDATE_CATALOG=/tmp/catalog.json site-update deadbeef site-env-17") - # wrong major for the resolved path: must refuse - machine.fail("SITE_UPDATE_CATALOG=/tmp/catalog.json site-update deadbeef 15") + # wrong env for the resolved path: must refuse + machine.fail("SITE_UPDATE_CATALOG=/tmp/catalog.json site-update deadbeef site-env-15") ''; } diff --git a/nix/packages/site-env.nix b/nix/packages/site-env.nix index ce2db3000b..c379746cb0 100644 --- a/nix/packages/site-env.nix +++ b/nix/packages/site-env.nix @@ -1,5 +1,5 @@ # These are envs (package sets per pg major version) deployed to instances -# at /nix/var/nix/profiles/site- and updated regularly. +# at /nix/var/nix/profiles/ and updated regularly. { perSystem = { @@ -29,7 +29,9 @@ ); }; - # Given a git sha and pg major, fetches the site-env catalog entry + # Given a git sha and a named env (e.g. site-env-17, postgres-env-17), + # fetches its catalog entry and flips /nix/var/nix/profiles/ to it. + # Generic across any single-package catalog entry named -.json. site-update = pkgs.writeShellApplication { name = "site-update"; runtimeInputs = [ @@ -38,21 +40,21 @@ pkgs.nix ]; text = '' - sha="''${1:?Usage: $0 }" - major="''${2:?Usage: $0 }" + sha="''${1:?Usage: $0 }" + env="''${2:?Usage: $0 }" system="$(uname -m)-linux" - profile="/nix/var/nix/profiles/site-''${major}" + profile="/nix/var/nix/profiles/''${env}" catalog="''${SITE_UPDATE_CATALOG:-}" if [[ -z "$catalog" ]]; then - catalog="/tmp/site-env-catalog-''${sha}-''${major}-''${system}.json" - aws s3 cp "s3://supabase-internal-artifacts/nix-catalog/''${sha}-site-env_''${major}-''${system}.json" \ + catalog="/tmp/''${env}-catalog-''${sha}-''${system}.json" + aws s3 cp "s3://supabase-internal-artifacts/nix-catalog/''${sha}-''${env}-''${system}.json" \ "$catalog" --region ap-southeast-1 fi path="$(jq -er --arg s "$system" '.[$s]' "$catalog")" - [[ "$(basename "$path")" == *"-site-env-''${major}" ]] || { - echo "error: resolved path $path is not tagged for major $major" >&2 + [[ "$(basename "$path")" == *"-''${env}" ]] || { + echo "error: resolved path $path is not tagged for env $env" >&2 exit 1 } From 0a4c70bf09bef98569c291c77d345851602d36aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 18:17:16 +0300 Subject: [PATCH 06/18] rename site-update to update-profile, take (profile, sha) not (sha, env) Argument order flips so the profile identity comes first; profile_name (the catalog/profile identifier) and profile_path (the actual /nix/var/nix/profiles/ path) are now distinct variables rather than conflating the two. --- .github/workflows/ami-release-nix.yml | 8 ++--- ansible/tasks/stage2-setup-postgres.yml | 10 +++--- .../{site-update.nix => update-profile.nix} | 14 ++++---- nix/packages/site-env.nix | 34 +++++++++---------- 4 files changed, 33 insertions(+), 33 deletions(-) rename nix/ext/tests/{site-update.nix => update-profile.nix} (53%) diff --git a/.github/workflows/ami-release-nix.yml b/.github/workflows/ami-release-nix.yml index d8ceb703e6..919e893960 100644 --- a/.github/workflows/ami-release-nix.yml +++ b/.github/workflows/ami-release-nix.yml @@ -216,10 +216,10 @@ jobs: "s3://${{ secrets.SHARED_AWS_ARTIFACTS_BUCKET }}/nix-catalog/${GIT_SHA}-${SITE_ENV_NAME}-${SYSTEM}.json" \ --content-type "application/json" - SITE_UPDATE_PATH=$(nix eval --raw ".#site-update.outPath") - jq -n --arg sys "$SYSTEM" --arg path "$SITE_UPDATE_PATH" '{($sys): $path}' > /tmp/site-update-catalog.json - aws s3 cp /tmp/site-update-catalog.json \ - "s3://${{ secrets.SHARED_AWS_ARTIFACTS_BUCKET }}/nix-catalog/${GIT_SHA}-site-update-${SYSTEM}.json" \ + UPDATE_PROFILE_PATH=$(nix eval --raw ".#update-profile.outPath") + jq -n --arg sys "$SYSTEM" --arg path "$UPDATE_PROFILE_PATH" '{($sys): $path}' > /tmp/update-profile-catalog.json + aws s3 cp /tmp/update-profile-catalog.json \ + "s3://${{ secrets.SHARED_AWS_ARTIFACTS_BUCKET }}/nix-catalog/${GIT_SHA}-update-profile-${SYSTEM}.json" \ --content-type "application/json" - name: Create release diff --git a/ansible/tasks/stage2-setup-postgres.yml b/ansible/tasks/stage2-setup-postgres.yml index 529c8995e8..4a4a83e739 100644 --- a/ansible/tasks/stage2-setup-postgres.yml +++ b/ansible/tasks/stage2-setup-postgres.yml @@ -77,16 +77,16 @@ nix-env --set {{ postgres_env_path.stdout }} " - - name: Resolve site-update store path + - name: Resolve update-profile store path ansible.builtin.shell: | . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && - nix build --no-link --print-out-paths github:supabase/postgres/{{ git_commit_sha }}#site-update - register: site_update_path + nix build --no-link --print-out-paths github:supabase/postgres/{{ git_commit_sha }}#update-profile + register: update_profile_path - - name: Install site-update + - name: Install update-profile ansible.builtin.shell: | . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && - nix-env --profile /nix/var/nix/profiles/site-update --set {{ site_update_path.stdout }} + nix-env --profile /nix/var/nix/profiles/update-profile --set {{ update_profile_path.stdout }} - name: Install supascan for baseline validation ansible.builtin.shell: | diff --git a/nix/ext/tests/site-update.nix b/nix/ext/tests/update-profile.nix similarity index 53% rename from nix/ext/tests/site-update.nix rename to nix/ext/tests/update-profile.nix index 4971b26275..aa5c4a8588 100644 --- a/nix/ext/tests/site-update.nix +++ b/nix/ext/tests/update-profile.nix @@ -1,28 +1,28 @@ { self, pkgs }: let system = pkgs.pkgsLinux.stdenv.hostPlatform.system; - site-update = self.packages.${system}.site-update; + update-profile = self.packages.${system}.update-profile; site-env-17 = self.packages.${system}."site-env-17"; in pkgs.testers.runNixOSTest { - name = "site-update"; + name = "update-profile"; nodes.machine = { ... }: { environment.systemPackages = [ - site-update + update-profile site-env-17 ]; }; testScript = '' machine.succeed("echo '{\"${system}\": \"${site-env-17}\"}' > /tmp/catalog.json") - machine.succeed("SITE_UPDATE_CATALOG=/tmp/catalog.json site-update deadbeef site-env-17") + machine.succeed("UPDATE_PROFILE_CATALOG=/tmp/catalog.json update-profile site-env-17 deadbeef") machine.succeed("[ \"$(readlink -f /nix/var/nix/profiles/site-env-17)\" = \"${site-env-17}\" ]") # idempotent: same catalog again is a no-op success - machine.succeed("SITE_UPDATE_CATALOG=/tmp/catalog.json site-update deadbeef site-env-17") + machine.succeed("UPDATE_PROFILE_CATALOG=/tmp/catalog.json update-profile site-env-17 deadbeef") - # wrong env for the resolved path: must refuse - machine.fail("SITE_UPDATE_CATALOG=/tmp/catalog.json site-update deadbeef site-env-15") + # wrong profile for the resolved path: must refuse + machine.fail("UPDATE_PROFILE_CATALOG=/tmp/catalog.json update-profile site-env-15 deadbeef") ''; } diff --git a/nix/packages/site-env.nix b/nix/packages/site-env.nix index c379746cb0..b09a0a0d78 100644 --- a/nix/packages/site-env.nix +++ b/nix/packages/site-env.nix @@ -29,47 +29,47 @@ ); }; - # Given a git sha and a named env (e.g. site-env-17, postgres-env-17), - # fetches its catalog entry and flips /nix/var/nix/profiles/ to it. - # Generic across any single-package catalog entry named -.json. - site-update = pkgs.writeShellApplication { - name = "site-update"; + # Given a profile name (e.g. site-env-17, postgres-env-17) and a git sha, + # fetches that name's catalog entry and flips /nix/var/nix/profiles/ + # to it. Generic across any single-package catalog entry named -.json. + update-profile = pkgs.writeShellApplication { + name = "update-profile"; runtimeInputs = [ pkgs.awscli2 pkgs.jq pkgs.nix ]; text = '' - sha="''${1:?Usage: $0 }" - env="''${2:?Usage: $0 }" + profile_name="''${1:?Usage: $0 }" + sha="''${2:?Usage: $0 }" system="$(uname -m)-linux" - profile="/nix/var/nix/profiles/''${env}" + profile_path="/nix/var/nix/profiles/''${profile_name}" - catalog="''${SITE_UPDATE_CATALOG:-}" + catalog="''${UPDATE_PROFILE_CATALOG:-}" if [[ -z "$catalog" ]]; then - catalog="/tmp/''${env}-catalog-''${sha}-''${system}.json" - aws s3 cp "s3://supabase-internal-artifacts/nix-catalog/''${sha}-''${env}-''${system}.json" \ + catalog="/tmp/''${profile_name}-catalog-''${sha}-''${system}.json" + aws s3 cp "s3://supabase-internal-artifacts/nix-catalog/''${sha}-''${profile_name}-''${system}.json" \ "$catalog" --region ap-southeast-1 fi path="$(jq -er --arg s "$system" '.[$s]' "$catalog")" - [[ "$(basename "$path")" == *"-''${env}" ]] || { - echo "error: resolved path $path is not tagged for env $env" >&2 + [[ "$(basename "$path")" == *"-''${profile_name}" ]] || { + echo "error: resolved path $path is not tagged for profile $profile_name" >&2 exit 1 } - [[ "$(readlink -f "$profile")" == "$path" ]] && exit 0 + [[ "$(readlink -f "$profile_path")" == "$path" ]] && exit 0 nix-store --realise --option stalled-download-timeout 120 "$path" >/dev/null - nix-env --profile "$profile" --set "$path" + nix-env --profile "$profile_path" --set "$path" ''; }; in { packages = siteEnvs // { - inherit site-update; + inherit update-profile; }; legacyPackages = siteEnvs // { - inherit site-update; + inherit update-profile; }; }; } From eee3f284380f93f4e7a40c52ec9209b992b0c51f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 18:48:16 +0300 Subject: [PATCH 07/18] rename site-extensions-update to update-site-extensions, split apply step MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extracts the realise+install tail into update-profile-paths (generic: given a profile and already-resolved paths, installs them replacing all existing ones). update-site-extensions keeps its own manifest resolution (genuinely different domain — local catalog, not S3-by-sha) and delegates the apply step. update-profile is untouched. --- nix/packages/extension-catalog.nix | 14 ++++++-------- nix/packages/site-env.nix | 18 ++++++++++++++++-- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/nix/packages/extension-catalog.nix b/nix/packages/extension-catalog.nix index c10a5fd9b9..d6e54aab9e 100644 --- a/nix/packages/extension-catalog.nix +++ b/nix/packages/extension-catalog.nix @@ -110,8 +110,8 @@ makeWrapper ${self'.packages.site-extensions-resolve}/bin/site-extensions-resolve \ "$out/bin/site-extensions-resolve" \ --set PG_EXTENSIONS_CATALOG "$out/share/pg-extensions-catalog.json" - makeWrapper ${self'.packages.site-extensions-update}/bin/site-extensions-update \ - "$out/bin/site-extensions-update" \ + makeWrapper ${self'.packages.update-site-extensions}/bin/update-site-extensions \ + "$out/bin/update-site-extensions" \ --set PG_EXTENSIONS_CATALOG "$out/share/pg-extensions-catalog.json" '' ) @@ -148,18 +148,16 @@ # Takes manifest json as argument. # Downloads paths and installs them as an env into the profile, replacing all existing ones. - site-extensions-update = pkgs.writeShellApplication { - name = "site-extensions-update"; + update-site-extensions = pkgs.writeShellApplication { + name = "update-site-extensions"; runtimeInputs = [ self'.packages.site-extensions-resolve - pkgs.nix + self'.packages.update-profile-paths ]; text = '' manifest="''${1:?Usage: $0 path-to/pg-extensions.json}" - profile="''${NIX_PROFILE:-/nix/var/nix/profiles/site-extensions}" readarray -t paths < <(site-extensions-resolve "$manifest") - nix-store --realise --option stalled-download-timeout 120 "''${paths[@]}" >/dev/null - nix-env --profile "$profile" --install "''${paths[@]}" --remove-all + update-profile-paths site-extensions "''${paths[@]}" ''; }; }; diff --git a/nix/packages/site-env.nix b/nix/packages/site-env.nix index b09a0a0d78..62f7c38116 100644 --- a/nix/packages/site-env.nix +++ b/nix/packages/site-env.nix @@ -63,13 +63,27 @@ nix-env --profile "$profile_path" --set "$path" ''; }; + + # Given a profile name and already-resolved store paths, installs them + # as an env into /nix/var/nix/profiles/, replacing all existing ones. + update-profile-paths = pkgs.writeShellApplication { + name = "update-profile-paths"; + runtimeInputs = [ pkgs.nix ]; + text = '' + profile_name="''${1:?Usage: $0 ...}" + shift + [ "$#" -ge 1 ] || { echo "Usage: $0 ..." >&2; exit 1; } + nix-store --realise --option stalled-download-timeout 120 "$@" >/dev/null + nix-env --profile "/nix/var/nix/profiles/''${profile_name}" --install "$@" --remove-all + ''; + }; in { packages = siteEnvs // { - inherit update-profile; + inherit update-profile update-profile-paths; }; legacyPackages = siteEnvs // { - inherit update-profile; + inherit update-profile update-profile-paths; }; }; } From 1421a461235be71388c348348ac38482d0641a30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 19:13:11 +0300 Subject: [PATCH 08/18] update-profile: make sha optional when UPDATE_PROFILE_CATALOG is set sha was required unconditionally even though it's only used to build the S3 fetch path; the catalog-override test hook never touches it. --- nix/ext/tests/update-profile.nix | 7 ++++--- nix/packages/site-env.nix | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/nix/ext/tests/update-profile.nix b/nix/ext/tests/update-profile.nix index aa5c4a8588..f34ddf37d2 100644 --- a/nix/ext/tests/update-profile.nix +++ b/nix/ext/tests/update-profile.nix @@ -16,13 +16,14 @@ pkgs.testers.runNixOSTest { }; testScript = '' machine.succeed("echo '{\"${system}\": \"${site-env-17}\"}' > /tmp/catalog.json") - machine.succeed("UPDATE_PROFILE_CATALOG=/tmp/catalog.json update-profile site-env-17 deadbeef") + # sha is only needed to fetch from S3 — omitted here since UPDATE_PROFILE_CATALOG bypasses that + machine.succeed("UPDATE_PROFILE_CATALOG=/tmp/catalog.json update-profile site-env-17") machine.succeed("[ \"$(readlink -f /nix/var/nix/profiles/site-env-17)\" = \"${site-env-17}\" ]") # idempotent: same catalog again is a no-op success - machine.succeed("UPDATE_PROFILE_CATALOG=/tmp/catalog.json update-profile site-env-17 deadbeef") + machine.succeed("UPDATE_PROFILE_CATALOG=/tmp/catalog.json update-profile site-env-17") # wrong profile for the resolved path: must refuse - machine.fail("UPDATE_PROFILE_CATALOG=/tmp/catalog.json update-profile site-env-15 deadbeef") + machine.fail("UPDATE_PROFILE_CATALOG=/tmp/catalog.json update-profile site-env-15") ''; } diff --git a/nix/packages/site-env.nix b/nix/packages/site-env.nix index 62f7c38116..c2666e2489 100644 --- a/nix/packages/site-env.nix +++ b/nix/packages/site-env.nix @@ -41,12 +41,12 @@ ]; text = '' profile_name="''${1:?Usage: $0 }" - sha="''${2:?Usage: $0 }" system="$(uname -m)-linux" profile_path="/nix/var/nix/profiles/''${profile_name}" catalog="''${UPDATE_PROFILE_CATALOG:-}" if [[ -z "$catalog" ]]; then + sha="''${2:?Usage: $0 }" catalog="/tmp/''${profile_name}-catalog-''${sha}-''${system}.json" aws s3 cp "s3://supabase-internal-artifacts/nix-catalog/''${sha}-''${profile_name}-''${system}.json" \ "$catalog" --region ap-southeast-1 From 0cd5b68d3f17348f64ab31599c4f39c6fa1c3109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Wed, 9 Sep 2026 19:41:37 +0300 Subject: [PATCH 09/18] update-profile: drop pkgs.awscli2, assume aws is provided by the environment AMIs already install AWS CLI v2 system-wide (ansible/tasks/internal/install-aws-cli.yml); bundling it again via Nix duplicated a 1.51 GiB closure (verified via nix path-info) with zero overlap with anything else installed on instances. Matches existing practice elsewhere in this repo (pam_jit_pg, supautils, pg_upgrade all call aws bare, assuming it's on PATH). --- nix/packages/site-env.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/packages/site-env.nix b/nix/packages/site-env.nix index c2666e2489..b53ef165c9 100644 --- a/nix/packages/site-env.nix +++ b/nix/packages/site-env.nix @@ -32,10 +32,10 @@ # Given a profile name (e.g. site-env-17, postgres-env-17) and a git sha, # fetches that name's catalog entry and flips /nix/var/nix/profiles/ # to it. Generic across any single-package catalog entry named -.json. + # Assumes `aws` is provided by the environment (AMIs already install AWS CLI v2). update-profile = pkgs.writeShellApplication { name = "update-profile"; runtimeInputs = [ - pkgs.awscli2 pkgs.jq pkgs.nix ]; From 540ec41affd7fc1d9684cbb9b26f32bdd9c59dba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Sat, 12 Sep 2026 13:09:25 +0300 Subject: [PATCH 10/18] inline update-profile-paths back into update-site-extensions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It had exactly one caller and no test of its own, and can't be shared with update-profile anyway (nix-env --install --remove-all produces a wrapper user-environment path, not the original package path that update-profile's skip-if-current/tag checks require — verified empirically). No reuse benefit, so drop the abstraction. Co-Authored-By: Claude Sonnet 5 --- nix/packages/extension-catalog.nix | 6 ++++-- nix/packages/site-env.nix | 18 ++---------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/nix/packages/extension-catalog.nix b/nix/packages/extension-catalog.nix index d6e54aab9e..43525e38cd 100644 --- a/nix/packages/extension-catalog.nix +++ b/nix/packages/extension-catalog.nix @@ -152,12 +152,14 @@ name = "update-site-extensions"; runtimeInputs = [ self'.packages.site-extensions-resolve - self'.packages.update-profile-paths + pkgs.nix ]; text = '' manifest="''${1:?Usage: $0 path-to/pg-extensions.json}" + profile="/nix/var/nix/profiles/site-extensions" readarray -t paths < <(site-extensions-resolve "$manifest") - update-profile-paths site-extensions "''${paths[@]}" + nix-store --realise --option stalled-download-timeout 120 "''${paths[@]}" >/dev/null + nix-env --profile "$profile" --install "''${paths[@]}" --remove-all ''; }; }; diff --git a/nix/packages/site-env.nix b/nix/packages/site-env.nix index b53ef165c9..f6ee317c55 100644 --- a/nix/packages/site-env.nix +++ b/nix/packages/site-env.nix @@ -63,27 +63,13 @@ nix-env --profile "$profile_path" --set "$path" ''; }; - - # Given a profile name and already-resolved store paths, installs them - # as an env into /nix/var/nix/profiles/, replacing all existing ones. - update-profile-paths = pkgs.writeShellApplication { - name = "update-profile-paths"; - runtimeInputs = [ pkgs.nix ]; - text = '' - profile_name="''${1:?Usage: $0 ...}" - shift - [ "$#" -ge 1 ] || { echo "Usage: $0 ..." >&2; exit 1; } - nix-store --realise --option stalled-download-timeout 120 "$@" >/dev/null - nix-env --profile "/nix/var/nix/profiles/''${profile_name}" --install "$@" --remove-all - ''; - }; in { packages = siteEnvs // { - inherit update-profile update-profile-paths; + inherit update-profile; }; legacyPackages = siteEnvs // { - inherit update-profile update-profile-paths; + inherit update-profile; }; }; } From 4077c973c1f01047bf02b4337dfde9ecc014dad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Fri, 18 Sep 2026 04:15:56 +0300 Subject: [PATCH 11/18] update-profile, update-site-extensions: assume nix is on PATH Co-Authored-By: Claude Sonnet 5 --- nix/ext/tests/update-profile.nix | 5 ++--- nix/packages/extension-catalog.nix | 8 ++------ nix/packages/site-env.nix | 10 ++-------- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/nix/ext/tests/update-profile.nix b/nix/ext/tests/update-profile.nix index f34ddf37d2..bb3a2c269c 100644 --- a/nix/ext/tests/update-profile.nix +++ b/nix/ext/tests/update-profile.nix @@ -16,14 +16,13 @@ pkgs.testers.runNixOSTest { }; testScript = '' machine.succeed("echo '{\"${system}\": \"${site-env-17}\"}' > /tmp/catalog.json") - # sha is only needed to fetch from S3 — omitted here since UPDATE_PROFILE_CATALOG bypasses that machine.succeed("UPDATE_PROFILE_CATALOG=/tmp/catalog.json update-profile site-env-17") machine.succeed("[ \"$(readlink -f /nix/var/nix/profiles/site-env-17)\" = \"${site-env-17}\" ]") - # idempotent: same catalog again is a no-op success + # idempotent machine.succeed("UPDATE_PROFILE_CATALOG=/tmp/catalog.json update-profile site-env-17") - # wrong profile for the resolved path: must refuse + # path not tagged for this profile machine.fail("UPDATE_PROFILE_CATALOG=/tmp/catalog.json update-profile site-env-15") ''; } diff --git a/nix/packages/extension-catalog.nix b/nix/packages/extension-catalog.nix index 43525e38cd..93f0ac2db7 100644 --- a/nix/packages/extension-catalog.nix +++ b/nix/packages/extension-catalog.nix @@ -146,14 +146,10 @@ ''; }; - # Takes manifest json as argument. - # Downloads paths and installs them as an env into the profile, replacing all existing ones. + # nix comes from the environment: the AMI installs it. update-site-extensions = pkgs.writeShellApplication { name = "update-site-extensions"; - runtimeInputs = [ - self'.packages.site-extensions-resolve - pkgs.nix - ]; + runtimeInputs = [ self'.packages.site-extensions-resolve ]; text = '' manifest="''${1:?Usage: $0 path-to/pg-extensions.json}" profile="/nix/var/nix/profiles/site-extensions" diff --git a/nix/packages/site-env.nix b/nix/packages/site-env.nix index f6ee317c55..7621db3f71 100644 --- a/nix/packages/site-env.nix +++ b/nix/packages/site-env.nix @@ -29,16 +29,10 @@ ); }; - # Given a profile name (e.g. site-env-17, postgres-env-17) and a git sha, - # fetches that name's catalog entry and flips /nix/var/nix/profiles/ - # to it. Generic across any single-package catalog entry named -.json. - # Assumes `aws` is provided by the environment (AMIs already install AWS CLI v2). + # aws and nix come from the environment: the AMI installs both. update-profile = pkgs.writeShellApplication { name = "update-profile"; - runtimeInputs = [ - pkgs.jq - pkgs.nix - ]; + runtimeInputs = [ pkgs.jq ]; text = '' profile_name="''${1:?Usage: $0 }" system="$(uname -m)-linux" From 5d7a25b4ff2a425f2ed1acdd4add0cdddc119915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Fri, 18 Sep 2026 04:37:49 +0300 Subject: [PATCH 12/18] trim comments Co-Authored-By: Claude Sonnet 5 --- nix/packages/extension-catalog.nix | 2 +- nix/packages/site-env.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nix/packages/extension-catalog.nix b/nix/packages/extension-catalog.nix index 93f0ac2db7..a32c176499 100644 --- a/nix/packages/extension-catalog.nix +++ b/nix/packages/extension-catalog.nix @@ -146,7 +146,7 @@ ''; }; - # nix comes from the environment: the AMI installs it. + # nix comes from the environment. update-site-extensions = pkgs.writeShellApplication { name = "update-site-extensions"; runtimeInputs = [ self'.packages.site-extensions-resolve ]; diff --git a/nix/packages/site-env.nix b/nix/packages/site-env.nix index 7621db3f71..9c9c5b36e2 100644 --- a/nix/packages/site-env.nix +++ b/nix/packages/site-env.nix @@ -29,7 +29,7 @@ ); }; - # aws and nix come from the environment: the AMI installs both. + # aws and nix come from the environment. update-profile = pkgs.writeShellApplication { name = "update-profile"; runtimeInputs = [ pkgs.jq ]; From cbcc8aba0f0eb2893f773cf3d7d0cf9b3185208c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Fri, 18 Sep 2026 06:02:34 +0300 Subject: [PATCH 13/18] split update-site (catalog fetch) out of update-profile update-profile now just does . update-site detects the active site-env-* profile, fetches its catalog entry, and calls update-profile. Bake/publish update-site instead of update-profile. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/ami-release-nix.yml | 8 ++--- ansible/tasks/stage2-setup-postgres.yml | 10 +++--- nix/ext/tests/update-profile.nix | 7 ++--- nix/packages/site-env.nix | 41 ++++++++++++++++--------- 4 files changed, 39 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ami-release-nix.yml b/.github/workflows/ami-release-nix.yml index 919e893960..998a121f83 100644 --- a/.github/workflows/ami-release-nix.yml +++ b/.github/workflows/ami-release-nix.yml @@ -216,10 +216,10 @@ jobs: "s3://${{ secrets.SHARED_AWS_ARTIFACTS_BUCKET }}/nix-catalog/${GIT_SHA}-${SITE_ENV_NAME}-${SYSTEM}.json" \ --content-type "application/json" - UPDATE_PROFILE_PATH=$(nix eval --raw ".#update-profile.outPath") - jq -n --arg sys "$SYSTEM" --arg path "$UPDATE_PROFILE_PATH" '{($sys): $path}' > /tmp/update-profile-catalog.json - aws s3 cp /tmp/update-profile-catalog.json \ - "s3://${{ secrets.SHARED_AWS_ARTIFACTS_BUCKET }}/nix-catalog/${GIT_SHA}-update-profile-${SYSTEM}.json" \ + UPDATE_SITE_PATH=$(nix eval --raw ".#update-site.outPath") + jq -n --arg sys "$SYSTEM" --arg path "$UPDATE_SITE_PATH" '{($sys): $path}' > /tmp/update-site-catalog.json + aws s3 cp /tmp/update-site-catalog.json \ + "s3://${{ secrets.SHARED_AWS_ARTIFACTS_BUCKET }}/nix-catalog/${GIT_SHA}-update-site-${SYSTEM}.json" \ --content-type "application/json" - name: Create release diff --git a/ansible/tasks/stage2-setup-postgres.yml b/ansible/tasks/stage2-setup-postgres.yml index 4a4a83e739..60f114bf64 100644 --- a/ansible/tasks/stage2-setup-postgres.yml +++ b/ansible/tasks/stage2-setup-postgres.yml @@ -77,16 +77,16 @@ nix-env --set {{ postgres_env_path.stdout }} " - - name: Resolve update-profile store path + - name: Resolve update-site store path ansible.builtin.shell: | . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && - nix build --no-link --print-out-paths github:supabase/postgres/{{ git_commit_sha }}#update-profile - register: update_profile_path + nix build --no-link --print-out-paths github:supabase/postgres/{{ git_commit_sha }}#update-site + register: update_site_path - - name: Install update-profile + - name: Install update-site ansible.builtin.shell: | . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && - nix-env --profile /nix/var/nix/profiles/update-profile --set {{ update_profile_path.stdout }} + nix-env --profile /nix/var/nix/profiles/update-site --set {{ update_site_path.stdout }} - name: Install supascan for baseline validation ansible.builtin.shell: | diff --git a/nix/ext/tests/update-profile.nix b/nix/ext/tests/update-profile.nix index bb3a2c269c..757b64a69e 100644 --- a/nix/ext/tests/update-profile.nix +++ b/nix/ext/tests/update-profile.nix @@ -15,14 +15,13 @@ pkgs.testers.runNixOSTest { ]; }; testScript = '' - machine.succeed("echo '{\"${system}\": \"${site-env-17}\"}' > /tmp/catalog.json") - machine.succeed("UPDATE_PROFILE_CATALOG=/tmp/catalog.json update-profile site-env-17") + machine.succeed("update-profile site-env-17 ${site-env-17}") machine.succeed("[ \"$(readlink -f /nix/var/nix/profiles/site-env-17)\" = \"${site-env-17}\" ]") # idempotent - machine.succeed("UPDATE_PROFILE_CATALOG=/tmp/catalog.json update-profile site-env-17") + machine.succeed("update-profile site-env-17 ${site-env-17}") # path not tagged for this profile - machine.fail("UPDATE_PROFILE_CATALOG=/tmp/catalog.json update-profile site-env-15") + machine.fail("update-profile site-env-15 ${site-env-17}") ''; } diff --git a/nix/packages/site-env.nix b/nix/packages/site-env.nix index 9c9c5b36e2..ccba56b625 100644 --- a/nix/packages/site-env.nix +++ b/nix/packages/site-env.nix @@ -32,21 +32,11 @@ # aws and nix come from the environment. update-profile = pkgs.writeShellApplication { name = "update-profile"; - runtimeInputs = [ pkgs.jq ]; text = '' - profile_name="''${1:?Usage: $0 }" - system="$(uname -m)-linux" + profile_name="''${1:?Usage: $0 }" + path="''${2:?Usage: $0 }" profile_path="/nix/var/nix/profiles/''${profile_name}" - catalog="''${UPDATE_PROFILE_CATALOG:-}" - if [[ -z "$catalog" ]]; then - sha="''${2:?Usage: $0 }" - catalog="/tmp/''${profile_name}-catalog-''${sha}-''${system}.json" - aws s3 cp "s3://supabase-internal-artifacts/nix-catalog/''${sha}-''${profile_name}-''${system}.json" \ - "$catalog" --region ap-southeast-1 - fi - - path="$(jq -er --arg s "$system" '.[$s]' "$catalog")" [[ "$(basename "$path")" == *"-''${profile_name}" ]] || { echo "error: resolved path $path is not tagged for profile $profile_name" >&2 exit 1 @@ -57,13 +47,36 @@ nix-env --profile "$profile_path" --set "$path" ''; }; + + # Updates whichever site-env-* profile is already active on this host. + update-site = pkgs.writeShellApplication { + name = "update-site"; + runtimeInputs = [ + pkgs.jq + update-profile + ]; + text = '' + sha="''${1:?Usage: $0 }" + system="$(uname -m)-linux" + shopt -s nullglob + candidates=(/nix/var/nix/profiles/site-env-*) + profile_name="$(basename "''${candidates[0]:?no site-env-* profile found}")" + catalog="/tmp/''${profile_name}-catalog-''${sha}-''${system}.json" + + aws s3 cp "s3://supabase-internal-artifacts/nix-catalog/''${sha}-''${profile_name}-''${system}.json" \ + "$catalog" --region ap-southeast-1 + path="$(jq -er --arg s "$system" '.[$s]' "$catalog")" + + update-profile "$profile_name" "$path" + ''; + }; in { packages = siteEnvs // { - inherit update-profile; + inherit update-profile update-site; }; legacyPackages = siteEnvs // { - inherit update-profile; + inherit update-profile update-site; }; }; } From 57dd1e4c7b3c61c7140c728457704a255e0d04d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Fri, 18 Sep 2026 06:40:11 +0300 Subject: [PATCH 14/18] site profile: single 'site' profile with self-describing marker Each site-env- writes its variant into $out/site-env-name. update-profile drops the tag-check (no longer meaningful once profile name is fixed). update-site reads the current profile's marker to know which catalog entry to fetch next, instead of guessing/globbing. Bootstrap the site profile explicitly in stage2 ansible, like postgres-env. Move the VM test out of nix/ext/tests (not an extension test) into nix/checks.nix as an explicit 'site' check. Co-Authored-By: Claude Sonnet 5 --- ansible/tasks/stage2-setup-postgres.yml | 11 ++++++++++ nix/checks.nix | 24 ++++++++++++++++++++++ nix/ext/tests/update-profile.nix | 27 ------------------------- nix/packages/site-env.nix | 21 ++++++++----------- 4 files changed, 43 insertions(+), 40 deletions(-) delete mode 100644 nix/ext/tests/update-profile.nix diff --git a/ansible/tasks/stage2-setup-postgres.yml b/ansible/tasks/stage2-setup-postgres.yml index 60f114bf64..1c8073199d 100644 --- a/ansible/tasks/stage2-setup-postgres.yml +++ b/ansible/tasks/stage2-setup-postgres.yml @@ -77,6 +77,17 @@ nix-env --set {{ postgres_env_path.stdout }} " + - name: Resolve site env store path + ansible.builtin.shell: | + . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && + nix build --no-link --print-out-paths github:supabase/postgres/{{ git_commit_sha }}#site-env-{{ postgresql_major_version }} + register: site_env_path + + - name: Install site env from nix binary cache + ansible.builtin.shell: | + . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && + nix-env --profile /nix/var/nix/profiles/site --set {{ site_env_path.stdout }} + - name: Resolve update-site store path ansible.builtin.shell: | . /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh && diff --git a/nix/checks.nix b/nix/checks.nix index 3fdd5ba2c0..1790e045f1 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -939,6 +939,30 @@ wal-g-3 ; devShell = self'.devShells.default; + site = + let + system = pkgs.pkgsLinux.stdenv.hostPlatform.system; + update-profile = self.packages.${system}.update-profile; + site-env-17 = self.packages.${system}."site-env-17"; + in + pkgs.testers.runNixOSTest { + name = "site"; + nodes.machine = + { ... }: + { + environment.systemPackages = [ + update-profile + site-env-17 + ]; + }; + testScript = '' + machine.succeed("update-profile site ${site-env-17}") + machine.succeed("[ \"$(readlink -f /nix/var/nix/profiles/site)\" = \"${site-env-17}\" ]") + + # idempotent + machine.succeed("update-profile site ${site-env-17}") + ''; + }; } // (import ./ext/tests { inherit self; diff --git a/nix/ext/tests/update-profile.nix b/nix/ext/tests/update-profile.nix deleted file mode 100644 index 757b64a69e..0000000000 --- a/nix/ext/tests/update-profile.nix +++ /dev/null @@ -1,27 +0,0 @@ -{ self, pkgs }: -let - system = pkgs.pkgsLinux.stdenv.hostPlatform.system; - update-profile = self.packages.${system}.update-profile; - site-env-17 = self.packages.${system}."site-env-17"; -in -pkgs.testers.runNixOSTest { - name = "update-profile"; - nodes.machine = - { ... }: - { - environment.systemPackages = [ - update-profile - site-env-17 - ]; - }; - testScript = '' - machine.succeed("update-profile site-env-17 ${site-env-17}") - machine.succeed("[ \"$(readlink -f /nix/var/nix/profiles/site-env-17)\" = \"${site-env-17}\" ]") - - # idempotent - machine.succeed("update-profile site-env-17 ${site-env-17}") - - # path not tagged for this profile - machine.fail("update-profile site-env-15 ${site-env-17}") - ''; -} diff --git a/nix/packages/site-env.nix b/nix/packages/site-env.nix index ccba56b625..e3e945033c 100644 --- a/nix/packages/site-env.nix +++ b/nix/packages/site-env.nix @@ -1,5 +1,4 @@ # These are envs (package sets per pg major version) deployed to instances -# at /nix/var/nix/profiles/ and updated regularly. { perSystem = { @@ -14,6 +13,7 @@ pkgs.buildEnv { name = "site-env-${version}"; paths = [ self'.legacyPackages."psql_${version}".exts.supautils ] ++ extraPaths; + postBuild = "echo site-env-${version} > $out/site-env-name"; }; siteEnvs = { @@ -29,6 +29,7 @@ ); }; + # Set the named nix profile to the provided nix store path. # aws and nix come from the environment. update-profile = pkgs.writeShellApplication { name = "update-profile"; @@ -37,18 +38,14 @@ path="''${2:?Usage: $0 }" profile_path="/nix/var/nix/profiles/''${profile_name}" - [[ "$(basename "$path")" == *"-''${profile_name}" ]] || { - echo "error: resolved path $path is not tagged for profile $profile_name" >&2 - exit 1 - } - [[ "$(readlink -f "$profile_path")" == "$path" ]] && exit 0 nix-store --realise --option stalled-download-timeout 120 "$path" >/dev/null nix-env --profile "$profile_path" --set "$path" ''; }; - # Updates whichever site-env-* profile is already active on this host. + # Fetch catalog and update site profile from given postgres repo hash. + # aws and nix come from the environment. update-site = pkgs.writeShellApplication { name = "update-site"; runtimeInputs = [ @@ -58,16 +55,14 @@ text = '' sha="''${1:?Usage: $0 }" system="$(uname -m)-linux" - shopt -s nullglob - candidates=(/nix/var/nix/profiles/site-env-*) - profile_name="$(basename "''${candidates[0]:?no site-env-* profile found}")" - catalog="/tmp/''${profile_name}-catalog-''${sha}-''${system}.json" + variant="$(cat /nix/var/nix/profiles/site/site-env-name)" + catalog="/tmp/''${variant}-catalog-''${sha}-''${system}.json" - aws s3 cp "s3://supabase-internal-artifacts/nix-catalog/''${sha}-''${profile_name}-''${system}.json" \ + aws s3 cp "s3://supabase-internal-artifacts/nix-catalog/''${sha}-''${variant}-''${system}.json" \ "$catalog" --region ap-southeast-1 path="$(jq -er --arg s "$system" '.[$s]' "$catalog")" - update-profile "$profile_name" "$path" + update-profile site "$path" ''; }; in From 05b504d6d1ad1485e99640cf9982b5198382e86a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Fri, 18 Sep 2026 07:27:27 +0300 Subject: [PATCH 15/18] bake site profile into postgresql.conf, add supautils-load VM test dynamic_library_path now points at /nix/var/nix/profiles/site/lib first, falling back to $libdir. Extends the 'site' check to start postgres with that config and confirm supautils loads via the site profile. Co-Authored-By: Claude Sonnet 5 --- .../postgresql_config/postgresql.conf.j2 | 2 +- nix/checks.nix | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/ansible/files/postgresql_config/postgresql.conf.j2 b/ansible/files/postgresql_config/postgresql.conf.j2 index 154ec13416..4aa75eda95 100644 --- a/ansible/files/postgresql_config/postgresql.conf.j2 +++ b/ansible/files/postgresql_config/postgresql.conf.j2 @@ -692,7 +692,7 @@ jit_provider = 'llvmjit' # JIT library to use # - Other Defaults - -#dynamic_library_path = '$libdir' +dynamic_library_path = '/nix/var/nix/profiles/site/lib:$libdir' #gin_fuzzy_search_limit = 0 #------------------------------------------------------------------------------ diff --git a/nix/checks.nix b/nix/checks.nix index 1790e045f1..215dbed73e 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -944,6 +944,13 @@ system = pkgs.pkgsLinux.stdenv.hostPlatform.system; update-profile = self.packages.${system}.update-profile; site-env-17 = self.packages.${system}."site-env-17"; + psql_17 = self.legacyPackages.${system}."psql_17".bin; + pgConf = pkgs.writeText "postgresql-test.conf" '' + dynamic_library_path = '/nix/var/nix/profiles/site/lib:$libdir' + session_preload_libraries = 'supautils' + listen_addresses = 'localhost' + unix_socket_directories = '/tmp' + ''; in pkgs.testers.runNixOSTest { name = "site"; @@ -954,6 +961,12 @@ update-profile site-env-17 ]; + users.users.postgres = { + isSystemUser = true; + group = "postgres"; + shell = pkgs.bash; + }; + users.groups.postgres = { }; }; testScript = '' machine.succeed("update-profile site ${site-env-17}") @@ -961,6 +974,13 @@ # idempotent machine.succeed("update-profile site ${site-env-17}") + + # postgres can load supautils via the site profile's dynamic_library_path + machine.succeed("install -d -o postgres -g postgres /tmp/pgdata") + machine.succeed("su postgres -c '${psql_17}/bin/initdb -D /tmp/pgdata'") + machine.succeed("install -o postgres -g postgres ${pgConf} /tmp/pgdata/postgresql.conf") + machine.succeed("su postgres -c '${psql_17}/bin/pg_ctl -D /tmp/pgdata -l /tmp/pg.log start'") + machine.succeed("su postgres -c '${psql_17}/bin/psql -h localhost -d postgres -c \"select 1\"'") ''; }; } From 8011d87dd2c18548214e43531333852a5c11cb52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Fri, 18 Sep 2026 08:26:58 +0300 Subject: [PATCH 16/18] site-env catalogs: use public artifacts bucket + curl, drop aws dep Matches the original nix-catalog intent (a plain HTTPS-fetchable blob) rather than requiring AWS credentials on the consuming instance. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/ami-release-nix.yml | 4 ++-- nix/packages/site-env.nix | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ami-release-nix.yml b/.github/workflows/ami-release-nix.yml index 998a121f83..437f3030c5 100644 --- a/.github/workflows/ami-release-nix.yml +++ b/.github/workflows/ami-release-nix.yml @@ -213,13 +213,13 @@ jobs: SITE_ENV_PATH=$(nix eval --raw ".#${SITE_ENV_NAME}.outPath") jq -n --arg sys "$SYSTEM" --arg path "$SITE_ENV_PATH" '{($sys): $path}' > /tmp/site-env-catalog.json aws s3 cp /tmp/site-env-catalog.json \ - "s3://${{ secrets.SHARED_AWS_ARTIFACTS_BUCKET }}/nix-catalog/${GIT_SHA}-${SITE_ENV_NAME}-${SYSTEM}.json" \ + "s3://supabase-public-artifacts-bucket/nix-catalog/${GIT_SHA}-${SITE_ENV_NAME}-${SYSTEM}.json" \ --content-type "application/json" UPDATE_SITE_PATH=$(nix eval --raw ".#update-site.outPath") jq -n --arg sys "$SYSTEM" --arg path "$UPDATE_SITE_PATH" '{($sys): $path}' > /tmp/update-site-catalog.json aws s3 cp /tmp/update-site-catalog.json \ - "s3://${{ secrets.SHARED_AWS_ARTIFACTS_BUCKET }}/nix-catalog/${GIT_SHA}-update-site-${SYSTEM}.json" \ + "s3://supabase-public-artifacts-bucket/nix-catalog/${GIT_SHA}-update-site-${SYSTEM}.json" \ --content-type "application/json" - name: Create release diff --git a/nix/packages/site-env.nix b/nix/packages/site-env.nix index e3e945033c..d9b829a721 100644 --- a/nix/packages/site-env.nix +++ b/nix/packages/site-env.nix @@ -45,7 +45,7 @@ }; # Fetch catalog and update site profile from given postgres repo hash. - # aws and nix come from the environment. + # curl and nix come from the environment. update-site = pkgs.writeShellApplication { name = "update-site"; runtimeInputs = [ @@ -58,8 +58,8 @@ variant="$(cat /nix/var/nix/profiles/site/site-env-name)" catalog="/tmp/''${variant}-catalog-''${sha}-''${system}.json" - aws s3 cp "s3://supabase-internal-artifacts/nix-catalog/''${sha}-''${variant}-''${system}.json" \ - "$catalog" --region ap-southeast-1 + curl -sSf "https://supabase-public-artifacts-bucket.s3.amazonaws.com/nix-catalog/''${sha}-''${variant}-''${system}.json" \ + -o "$catalog" path="$(jq -er --arg s "$system" '.[$s]' "$catalog")" update-profile site "$path" From 95e4d4ead4590bf74021954170ac50ea7a73ee27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Fri, 18 Sep 2026 08:35:35 +0300 Subject: [PATCH 17/18] revert site-env catalogs to private bucket + aws Sticking with the already-proven SHARED_AWS_ARTIFACTS_BUCKET/IAM role pair used by the existing psql_ catalog, rather than the untested supabase-public-artifacts-bucket write. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/ami-release-nix.yml | 4 ++-- nix/packages/site-env.nix | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ami-release-nix.yml b/.github/workflows/ami-release-nix.yml index 437f3030c5..998a121f83 100644 --- a/.github/workflows/ami-release-nix.yml +++ b/.github/workflows/ami-release-nix.yml @@ -213,13 +213,13 @@ jobs: SITE_ENV_PATH=$(nix eval --raw ".#${SITE_ENV_NAME}.outPath") jq -n --arg sys "$SYSTEM" --arg path "$SITE_ENV_PATH" '{($sys): $path}' > /tmp/site-env-catalog.json aws s3 cp /tmp/site-env-catalog.json \ - "s3://supabase-public-artifacts-bucket/nix-catalog/${GIT_SHA}-${SITE_ENV_NAME}-${SYSTEM}.json" \ + "s3://${{ secrets.SHARED_AWS_ARTIFACTS_BUCKET }}/nix-catalog/${GIT_SHA}-${SITE_ENV_NAME}-${SYSTEM}.json" \ --content-type "application/json" UPDATE_SITE_PATH=$(nix eval --raw ".#update-site.outPath") jq -n --arg sys "$SYSTEM" --arg path "$UPDATE_SITE_PATH" '{($sys): $path}' > /tmp/update-site-catalog.json aws s3 cp /tmp/update-site-catalog.json \ - "s3://supabase-public-artifacts-bucket/nix-catalog/${GIT_SHA}-update-site-${SYSTEM}.json" \ + "s3://${{ secrets.SHARED_AWS_ARTIFACTS_BUCKET }}/nix-catalog/${GIT_SHA}-update-site-${SYSTEM}.json" \ --content-type "application/json" - name: Create release diff --git a/nix/packages/site-env.nix b/nix/packages/site-env.nix index d9b829a721..e3e945033c 100644 --- a/nix/packages/site-env.nix +++ b/nix/packages/site-env.nix @@ -45,7 +45,7 @@ }; # Fetch catalog and update site profile from given postgres repo hash. - # curl and nix come from the environment. + # aws and nix come from the environment. update-site = pkgs.writeShellApplication { name = "update-site"; runtimeInputs = [ @@ -58,8 +58,8 @@ variant="$(cat /nix/var/nix/profiles/site/site-env-name)" catalog="/tmp/''${variant}-catalog-''${sha}-''${system}.json" - curl -sSf "https://supabase-public-artifacts-bucket.s3.amazonaws.com/nix-catalog/''${sha}-''${variant}-''${system}.json" \ - -o "$catalog" + aws s3 cp "s3://supabase-internal-artifacts/nix-catalog/''${sha}-''${variant}-''${system}.json" \ + "$catalog" --region ap-southeast-1 path="$(jq -er --arg s "$system" '.[$s]' "$catalog")" update-profile site "$path" From 5dc5f8fe4c3bb3012bac87981acf24fe6f80f25a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Fri, 18 Sep 2026 08:41:02 +0300 Subject: [PATCH 18/18] restore shortened update-site-extensions doc comment Co-Authored-By: Claude Sonnet 5 --- nix/packages/extension-catalog.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nix/packages/extension-catalog.nix b/nix/packages/extension-catalog.nix index a32c176499..d4724b27d9 100644 --- a/nix/packages/extension-catalog.nix +++ b/nix/packages/extension-catalog.nix @@ -146,6 +146,7 @@ ''; }; + # Downloads the manifest's paths and installs them as the profile's env, replacing all existing ones. # nix comes from the environment. update-site-extensions = pkgs.writeShellApplication { name = "update-site-extensions";