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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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/10] 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; }; }; }