From be252180ca9ca28b6b6d427a748ee926ec5dc649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Mon, 19 Oct 2026 13:49:27 +0300 Subject: [PATCH 1/7] feat(nix): refresh glibc collation version on postgres start Add a oneshot unit shipped in the postgres env profile. After each postgres start it runs ALTER DATABASE ... REFRESH COLLATION VERSION for glibc en_US databases whose recorded and actual glibc versions both sit in 2.31..2.44, where English ordering is identical. Other databases are left alone. The build fails when nixpkgs glibc is above 2.44. Co-Authored-By: Claude Sonnet 5.5 --- ansible/tasks/stage2-setup-postgres.yml | 22 ++++ nix/ext/tests/collation-refresh.nix | 121 ++++++++++++++++++++ nix/packages/postgres-collation-refresh.nix | 55 +++++++++ nix/packages/postgres-collation-refresh.sh | 52 +++++++++ nix/packages/postgres-env.nix | 16 ++- testinfra/test_ami_nix.py | 19 +++ 6 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 nix/ext/tests/collation-refresh.nix create mode 100644 nix/packages/postgres-collation-refresh.nix create mode 100644 nix/packages/postgres-collation-refresh.sh diff --git a/ansible/tasks/stage2-setup-postgres.yml b/ansible/tasks/stage2-setup-postgres.yml index d4823238a7..7d119e761a 100644 --- a/ansible/tasks/stage2-setup-postgres.yml +++ b/ansible/tasks/stage2-setup-postgres.yml @@ -77,6 +77,28 @@ nix-env --set {{ postgres_env_path.stdout }} " + # Link through .nix-profile, not the store path: `systemctl enable` resolves + # the symlink and pins the unit to the store path of this generation. + - name: Link collation refresh unit from the postgres env profile + ansible.builtin.file: + src: /var/lib/postgresql/.nix-profile/lib/systemd/system/postgresql-collation-refresh.service + dest: /etc/systemd/system/postgresql-collation-refresh.service + state: link + + - name: Make postgresql want the collation refresh unit + ansible.builtin.copy: + dest: /etc/systemd/system/postgresql.service.d/collation-refresh.conf + content: | + [Unit] + Wants=postgresql-collation-refresh.service + owner: root + group: root + mode: '0644' + + - name: Reload systemd for the collation refresh unit + ansible.builtin.systemd_service: + daemon_reload: true + - 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/ext/tests/collation-refresh.nix b/nix/ext/tests/collation-refresh.nix new file mode 100644 index 0000000000..c8fdfe9cc8 --- /dev/null +++ b/nix/ext/tests/collation-refresh.nix @@ -0,0 +1,121 @@ +{ self, pkgs }: +let + system = pkgs.pkgsLinux.stdenv.hostPlatform.system; + testLib = import ./lib.nix { inherit self pkgs; }; + refresh = self.packages.${system}."postgres-collation-refresh-17"; +in +pkgs.testers.runNixOSTest { + name = "collation-refresh"; + nodes.server = + { ... }: + { + imports = [ + (testLib.makeSupabaseTestConfig { + majorVersion = "17"; + }) + ]; + + i18n.supportedLocales = [ + "C.UTF-8/UTF-8" + "en_US.UTF-8/UTF-8" + "de_DE.UTF-8/UTF-8" + ]; + + systemd.packages = [ refresh ]; + systemd.services.postgresql.wants = [ "postgresql-collation-refresh.service" ]; + }; + testScript = '' + PSQL = "psql -h localhost -U supabase_admin -d postgres -At -v ON_ERROR_STOP=1" + REFRESH = "${refresh}/bin/postgres_collation_refresh" + + def sql(query): + return server.succeed(PSQL + " -c \"" + query + "\"").strip() + + def recorded(db): + return sql("select coalesce(datcollversion, '-') from pg_database where datname = '" + db + "'") + + def set_recorded(db, version): + sql("update pg_database set datcollversion = '" + version + "' where datname = '" + db + "'") + + def create(db, options): + sql("create database " + db + " " + options + " template template0") + + start_all() + server.wait_for_unit("supabase-db-init.service") + server.wait_for_unit("postgresql-collation-refresh.service") + + create("g", "locale_provider = libc locale = 'en_US.UTF-8'") + create("old", "locale_provider = libc locale = 'en_US.UTF-8'") + create("de", "locale_provider = libc locale = 'de_DE.UTF-8'") + create("c", "locale_provider = libc locale = 'C'") + create("i", "locale_provider = icu locale = 'en_US.UTF-8' icu_locale = 'en-US'") + actual = sql("select pg_database_collation_actual_version(oid) from pg_database where datname = 'g'") + icu_actual = recorded("i") + print(f"glibc collation version: {actual}, icu: {icu_actual}") + + def fresh(): + for db in ["g", "old", "de"]: + set_recorded(db, actual) + + with subtest("Nothing stale is a no-op"): + server.succeed(REFRESH) + + with subtest("A stale glibc en_US database inside the bounds is refreshed"): + set_recorded("g", "2.31") + server.succeed(REFRESH) + assert recorded("g") == actual, f"expected {actual}, got {recorded('g')}" + + with subtest("ICU, non-English glibc and C databases are left alone and do not fail the run"): + set_recorded("de", "2.31") + set_recorded("i", "1.0") + server.succeed(REFRESH) + assert recorded("de") == "2.31" + assert recorded("i") == "1.0" + assert recorded("c") == "-" + set_recorded("i", icu_actual) + + with subtest("A glibc database older than 2.31 fails the run and the other databases are still refreshed"): + fresh() + set_recorded("old", "2.28") + set_recorded("g", "2.31") + status, _ = server.execute(REFRESH) + assert status == 1, f"expected exit 1, got {status}" + assert recorded("old") == "2.28" + assert recorded("g") == actual, f"expected {actual}, got {recorded('g')}" + + with subtest("A database that fails to refresh does not stop the others"): + fresh() + sql("create role refresher login") + sql("create database a_other locale_provider = libc locale = 'en_US.UTF-8' template template0") + sql("create database b_owned owner refresher locale_provider = libc locale = 'en_US.UTF-8' template template0") + set_recorded("a_other", "2.31") + set_recorded("b_owned", "2.31") + status, _ = server.execute("PGUSER=refresher " + REFRESH) + assert status == 1, f"expected exit 1, got {status}" + assert recorded("a_other") == "2.31" + assert recorded("b_owned") == actual, f"expected {actual}, got {recorded('b_owned')}" + set_recorded("a_other", actual) + + with subtest("Databases without a recorded version are ignored"): + sql("update pg_database set datcollversion = null where datname = 'template0'") + server.succeed(REFRESH) + assert recorded("template0") == "-" + + with subtest("A postgres restart reruns the unit and refreshes"): + fresh() + set_recorded("g", "2.31") + server.succeed("systemctl restart postgresql") + server.wait_until_succeeds( + PSQL + " -c \"select 1 from pg_database where datname = 'g' and datcollversion = '" + actual + "'\" | grep -q 1" + ) + + with subtest("A refused database fails the unit and leaves postgres active"): + fresh() + set_recorded("old", "2.28") + server.succeed("systemctl restart postgresql") + server.wait_for_unit("postgresql.service") + server.wait_until_succeeds("systemctl is-failed postgresql-collation-refresh.service") + server.succeed("systemctl is-active postgresql.service") + assert recorded("old") == "2.28" + ''; +} diff --git a/nix/packages/postgres-collation-refresh.nix b/nix/packages/postgres-collation-refresh.nix new file mode 100644 index 0000000000..cd42efd3ca --- /dev/null +++ b/nix/packages/postgres-collation-refresh.nix @@ -0,0 +1,55 @@ +{ + lib, + stdenv, + writeShellApplication, + writeTextDir, + symlinkJoin, + coreutils, + psql, +}: +let + minGlibc = "2.31"; + maxGlibc = "2.44"; + + script = writeShellApplication { + name = "postgres_collation_refresh"; + runtimeInputs = [ + coreutils + psql + ]; + runtimeEnv = { + MIN_GLIBC = minGlibc; + MAX_GLIBC = maxGlibc; + }; + text = builtins.readFile ./postgres-collation-refresh.sh; + }; + + unit = writeTextDir "lib/systemd/system/postgresql-collation-refresh.service" '' + [Unit] + Description=Refresh default collation version + After=postgresql.service + PartOf=postgresql.service + ConditionHost=!hibernation-* + + [Service] + Type=oneshot + RemainAfterExit=yes + User=postgres + TimeoutStartSec=120 + Nice=19 + ExecStart=${lib.getExe script} + ''; +in +assert lib.assertMsg + ( + !stdenv.hostPlatform.isGnu + || lib.versionAtLeast maxGlibc (lib.versions.majorMinor stdenv.cc.libc.version) + ) + "glibc ${stdenv.cc.libc.version} is above the reviewed maximum ${maxGlibc}. Review English collation changes, then raise maxGlibc in nix/packages/postgres-collation-refresh.nix."; +symlinkJoin { + name = "postgres-collation-refresh"; + paths = [ + script + unit + ]; +} diff --git a/nix/packages/postgres-collation-refresh.sh b/nix/packages/postgres-collation-refresh.sh new file mode 100644 index 0000000000..3ce16d99a5 --- /dev/null +++ b/nix/packages/postgres-collation-refresh.sh @@ -0,0 +1,52 @@ +# shellcheck shell=bash +export PGHOST="${PGHOST:-localhost}" +export PGUSER="${PGUSER:-supabase_admin}" + +run_sql() { + psql -X -q -A -t -v ON_ERROR_STOP=1 -d postgres "$@" +} + +# English ordering is identical across MIN_GLIBC..MAX_GLIBC, so a stale glibc +# en_US database is only refreshed when both versions sit in that range. +checked_cte() { + cat < pg_database_collation_actual_version(oid) +), +checked as ( + select datname, + recorded between string_to_array('$MIN_GLIBC', '.')::int[] and string_to_array('$MAX_GLIBC', '.')::int[] + and actual between string_to_array('$MIN_GLIBC', '.')::int[] and string_to_array('$MAX_GLIBC', '.')::int[] as in_bounds + from stale +) +SQL +} + +if [ "$(run_sql -c "select pg_is_in_recovery()")" = "t" ]; then + echo "collation_refresh: replica, skipping" + exit 0 +fi + +# No ON_ERROR_STOP: one failing database must not stop the others. Failures +# surface in the remaining count below. +psql -X -q -A -t -d postgres < Date: Mon, 19 Oct 2026 15:38:59 +0300 Subject: [PATCH 2/7] refactor(nix): ship one collation refresh package using profile psql Take psql from the postgres env profile PATH instead of a per-version build input, so a single package serves every Postgres version. Drop Nice and ConditionHost from the unit and merge the failure subtests in the VM test. Co-Authored-By: Claude Sonnet 5.5 --- nix/ext/tests/collation-refresh.nix | 22 +++++---------------- nix/packages/postgres-collation-refresh.nix | 9 ++------- nix/packages/postgres-env.nix | 12 ++--------- 3 files changed, 9 insertions(+), 34 deletions(-) diff --git a/nix/ext/tests/collation-refresh.nix b/nix/ext/tests/collation-refresh.nix index c8fdfe9cc8..ac0281e49d 100644 --- a/nix/ext/tests/collation-refresh.nix +++ b/nix/ext/tests/collation-refresh.nix @@ -2,7 +2,8 @@ let system = pkgs.pkgsLinux.stdenv.hostPlatform.system; testLib = import ./lib.nix { inherit self pkgs; }; - refresh = self.packages.${system}."postgres-collation-refresh-17"; + refresh = self.packages.${system}.postgres-collation-refresh; + psql = self.packages.${system}."psql_17/bin"; in pkgs.testers.runNixOSTest { name = "collation-refresh"; @@ -23,6 +24,7 @@ pkgs.testers.runNixOSTest { systemd.packages = [ refresh ]; systemd.services.postgresql.wants = [ "postgresql-collation-refresh.service" ]; + systemd.services.postgresql-collation-refresh.path = [ psql ]; }; testScript = '' PSQL = "psql -h localhost -U supabase_admin -d postgres -At -v ON_ERROR_STOP=1" @@ -74,15 +76,6 @@ pkgs.testers.runNixOSTest { assert recorded("c") == "-" set_recorded("i", icu_actual) - with subtest("A glibc database older than 2.31 fails the run and the other databases are still refreshed"): - fresh() - set_recorded("old", "2.28") - set_recorded("g", "2.31") - status, _ = server.execute(REFRESH) - assert status == 1, f"expected exit 1, got {status}" - assert recorded("old") == "2.28" - assert recorded("g") == actual, f"expected {actual}, got {recorded('g')}" - with subtest("A database that fails to refresh does not stop the others"): fresh() sql("create role refresher login") @@ -101,19 +94,14 @@ pkgs.testers.runNixOSTest { server.succeed(REFRESH) assert recorded("template0") == "-" - with subtest("A postgres restart reruns the unit and refreshes"): + with subtest("A postgres restart reruns the unit: it refreshes what it can, fails on a glibc database older than 2.31, and leaves postgres active"): fresh() + set_recorded("old", "2.28") set_recorded("g", "2.31") server.succeed("systemctl restart postgresql") server.wait_until_succeeds( PSQL + " -c \"select 1 from pg_database where datname = 'g' and datcollversion = '" + actual + "'\" | grep -q 1" ) - - with subtest("A refused database fails the unit and leaves postgres active"): - fresh() - set_recorded("old", "2.28") - server.succeed("systemctl restart postgresql") - server.wait_for_unit("postgresql.service") server.wait_until_succeeds("systemctl is-failed postgresql-collation-refresh.service") server.succeed("systemctl is-active postgresql.service") assert recorded("old") == "2.28" diff --git a/nix/packages/postgres-collation-refresh.nix b/nix/packages/postgres-collation-refresh.nix index cd42efd3ca..8bfcd790f9 100644 --- a/nix/packages/postgres-collation-refresh.nix +++ b/nix/packages/postgres-collation-refresh.nix @@ -5,7 +5,6 @@ writeTextDir, symlinkJoin, coreutils, - psql, }: let minGlibc = "2.31"; @@ -13,10 +12,7 @@ let script = writeShellApplication { name = "postgres_collation_refresh"; - runtimeInputs = [ - coreutils - psql - ]; + runtimeInputs = [ coreutils ]; runtimeEnv = { MIN_GLIBC = minGlibc; MAX_GLIBC = maxGlibc; @@ -29,14 +25,13 @@ let Description=Refresh default collation version After=postgresql.service PartOf=postgresql.service - ConditionHost=!hibernation-* [Service] Type=oneshot RemainAfterExit=yes User=postgres TimeoutStartSec=120 - Nice=19 + Environment=PATH=/var/lib/postgresql/.nix-profile/bin ExecStart=${lib.getExe script} ''; in diff --git a/nix/packages/postgres-env.nix b/nix/packages/postgres-env.nix index 91fb344673..03a8f8058c 100644 --- a/nix/packages/postgres-env.nix +++ b/nix/packages/postgres-env.nix @@ -9,12 +9,6 @@ let # Make a bundle of packages, as a single derivation, to be installed into the # postgres user's nix profile, during image provisioning or instance update. - makeCollationRefresh = - version: - pkgs.callPackage ./postgres-collation-refresh.nix { - psql = self'.packages."psql_${version}/bin"; - }; - makePostgresEnv = version: pkgs.symlinkJoin { @@ -27,7 +21,7 @@ ] ++ lib.optionals pkgs.stdenv.isLinux [ self'.packages."postgresql_${version}_debug" - self'.packages."postgres-collation-refresh-${version}" + self'.packages.postgres-collation-refresh ] ++ lib.optionals (pkgs.stdenv.isLinux && version != "15") [ self'.packages.gatekeeper ] # orioledb.so ships as a separate extension package (nix/ext/orioledb.nix), not @@ -45,9 +39,7 @@ postgres-env-orioledb-17 = makePostgresEnv "orioledb-17"; } // lib.optionalAttrs pkgs.stdenv.isLinux { - postgres-collation-refresh-15 = makeCollationRefresh "15"; - postgres-collation-refresh-17 = makeCollationRefresh "17"; - postgres-collation-refresh-orioledb-17 = makeCollationRefresh "orioledb-17"; + postgres-collation-refresh = pkgs.callPackage ./postgres-collation-refresh.nix { }; }; }; } From 8b33b22fd7259771284ae812207fb264b48c9475 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Tue, 20 Oct 2026 03:51:42 +0300 Subject: [PATCH 3/7] refactor(nix): ship the collation refresh unit and script in the site profile Move the package into its own directory and add it to the site envs. The unit stays a plain source file that the package installs verbatim, and ansible links it from the site profile instead of copying it. Co-Authored-By: Claude Sonnet 5.5 --- ansible/tasks/setup-postgres.yml | 25 +++++++++++++ ansible/tasks/stage2-setup-postgres.yml | 22 ------------ nix/ext/tests/collation-refresh.nix | 6 ++-- nix/packages/default.nix | 1 + .../default.nix} | 36 +++++++------------ .../postgres-collation-refresh.sh | 0 .../postgresql-collation-refresh.service | 12 +++++++ nix/packages/postgres-env.nix | 8 +---- nix/packages/site-env.nix | 6 +++- testinfra/test_ami_nix.py | 10 +++--- 10 files changed, 65 insertions(+), 61 deletions(-) rename nix/packages/{postgres-collation-refresh.nix => postgres-collation-refresh/default.nix} (60%) rename nix/packages/{ => postgres-collation-refresh}/postgres-collation-refresh.sh (100%) create mode 100644 nix/packages/postgres-collation-refresh/postgresql-collation-refresh.service diff --git a/ansible/tasks/setup-postgres.yml b/ansible/tasks/setup-postgres.yml index 9dd0ddcd9c..1007601644 100644 --- a/ansible/tasks/setup-postgres.yml +++ b/ansible/tasks/setup-postgres.yml @@ -318,6 +318,31 @@ enabled: true name: 'postgresql' +# Link through the site profile, not the store path: `systemctl enable` resolves +# the symlink and pins the unit to the store path of the current generation. +- name: Link the collation refresh unit from the site profile + when: stage2 + ansible.builtin.file: + src: /nix/var/nix/profiles/site/lib/systemd/system/postgresql-collation-refresh.service + dest: /etc/systemd/system/postgresql-collation-refresh.service + state: link + +- name: Make postgresql want the collation refresh unit + when: stage2 + ansible.builtin.copy: + dest: /etc/systemd/system/postgresql.service.d/collation-refresh.conf + content: | + [Unit] + Wants=postgresql-collation-refresh.service + owner: root + group: root + mode: '0644' + +- name: Reload systemd for the collation refresh unit + when: stage2 + ansible.builtin.systemd_service: + daemon_reload: true + - name: Add lang and locale items to .bashrc when: not stage2 ansible.builtin.lineinfile: diff --git a/ansible/tasks/stage2-setup-postgres.yml b/ansible/tasks/stage2-setup-postgres.yml index 7d119e761a..d4823238a7 100644 --- a/ansible/tasks/stage2-setup-postgres.yml +++ b/ansible/tasks/stage2-setup-postgres.yml @@ -77,28 +77,6 @@ nix-env --set {{ postgres_env_path.stdout }} " - # Link through .nix-profile, not the store path: `systemctl enable` resolves - # the symlink and pins the unit to the store path of this generation. - - name: Link collation refresh unit from the postgres env profile - ansible.builtin.file: - src: /var/lib/postgresql/.nix-profile/lib/systemd/system/postgresql-collation-refresh.service - dest: /etc/systemd/system/postgresql-collation-refresh.service - state: link - - - name: Make postgresql want the collation refresh unit - ansible.builtin.copy: - dest: /etc/systemd/system/postgresql.service.d/collation-refresh.conf - content: | - [Unit] - Wants=postgresql-collation-refresh.service - owner: root - group: root - mode: '0644' - - - name: Reload systemd for the collation refresh unit - ansible.builtin.systemd_service: - daemon_reload: true - - 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/ext/tests/collation-refresh.nix b/nix/ext/tests/collation-refresh.nix index ac0281e49d..09a406fedb 100644 --- a/nix/ext/tests/collation-refresh.nix +++ b/nix/ext/tests/collation-refresh.nix @@ -3,7 +3,6 @@ let system = pkgs.pkgsLinux.stdenv.hostPlatform.system; testLib = import ./lib.nix { inherit self pkgs; }; refresh = self.packages.${system}.postgres-collation-refresh; - psql = self.packages.${system}."psql_17/bin"; in pkgs.testers.runNixOSTest { name = "collation-refresh"; @@ -22,9 +21,12 @@ pkgs.testers.runNixOSTest { "de_DE.UTF-8/UTF-8" ]; + systemd.tmpfiles.rules = [ + "L+ /var/lib/postgresql/.nix-profile - - - - ${self.packages.${system}."psql_17/bin"}" + "L+ /nix/var/nix/profiles/site - - - - ${refresh}" + ]; systemd.packages = [ refresh ]; systemd.services.postgresql.wants = [ "postgresql-collation-refresh.service" ]; - systemd.services.postgresql-collation-refresh.path = [ psql ]; }; testScript = '' PSQL = "psql -h localhost -U supabase_admin -d postgres -At -v ON_ERROR_STOP=1" diff --git a/nix/packages/default.nix b/nix/packages/default.nix index 6fa7d97e06..416a136eb2 100644 --- a/nix/packages/default.nix +++ b/nix/packages/default.nix @@ -61,6 +61,7 @@ }; docs = pkgs.callPackage ./docs.nix { }; pgbouncer = pkgs.callPackage ../pgbouncer.nix { }; + postgres-collation-refresh = pkgs.callPackage ./postgres-collation-refresh { }; github-matrix = pkgs.callPackage ./github-matrix { nix-eval-jobs = inputs'.nix-eval-jobs.packages.default; }; diff --git a/nix/packages/postgres-collation-refresh.nix b/nix/packages/postgres-collation-refresh/default.nix similarity index 60% rename from nix/packages/postgres-collation-refresh.nix rename to nix/packages/postgres-collation-refresh/default.nix index 8bfcd790f9..328c056ac9 100644 --- a/nix/packages/postgres-collation-refresh.nix +++ b/nix/packages/postgres-collation-refresh/default.nix @@ -2,14 +2,21 @@ lib, stdenv, writeShellApplication, - writeTextDir, + runCommand, symlinkJoin, coreutils, }: let minGlibc = "2.31"; maxGlibc = "2.44"; - +in +assert lib.assertMsg + ( + !stdenv.hostPlatform.isGnu + || lib.versionAtLeast maxGlibc (lib.versions.majorMinor stdenv.cc.libc.version) + ) + "glibc ${stdenv.cc.libc.version} is above the reviewed maximum ${maxGlibc}. Review English collation changes, then raise maxGlibc in nix/packages/postgres-collation-refresh/default.nix."; +let script = writeShellApplication { name = "postgres_collation_refresh"; runtimeInputs = [ coreutils ]; @@ -19,32 +26,13 @@ let }; text = builtins.readFile ./postgres-collation-refresh.sh; }; - - unit = writeTextDir "lib/systemd/system/postgresql-collation-refresh.service" '' - [Unit] - Description=Refresh default collation version - After=postgresql.service - PartOf=postgresql.service - - [Service] - Type=oneshot - RemainAfterExit=yes - User=postgres - TimeoutStartSec=120 - Environment=PATH=/var/lib/postgresql/.nix-profile/bin - ExecStart=${lib.getExe script} - ''; in -assert lib.assertMsg - ( - !stdenv.hostPlatform.isGnu - || lib.versionAtLeast maxGlibc (lib.versions.majorMinor stdenv.cc.libc.version) - ) - "glibc ${stdenv.cc.libc.version} is above the reviewed maximum ${maxGlibc}. Review English collation changes, then raise maxGlibc in nix/packages/postgres-collation-refresh.nix."; symlinkJoin { name = "postgres-collation-refresh"; paths = [ script - unit + (runCommand "postgres-collation-refresh-unit" { } '' + install -Dm0644 ${./postgresql-collation-refresh.service} $out/lib/systemd/system/postgresql-collation-refresh.service + '') ]; } diff --git a/nix/packages/postgres-collation-refresh.sh b/nix/packages/postgres-collation-refresh/postgres-collation-refresh.sh similarity index 100% rename from nix/packages/postgres-collation-refresh.sh rename to nix/packages/postgres-collation-refresh/postgres-collation-refresh.sh diff --git a/nix/packages/postgres-collation-refresh/postgresql-collation-refresh.service b/nix/packages/postgres-collation-refresh/postgresql-collation-refresh.service new file mode 100644 index 0000000000..55ea3cbcf0 --- /dev/null +++ b/nix/packages/postgres-collation-refresh/postgresql-collation-refresh.service @@ -0,0 +1,12 @@ +[Unit] +Description=Refresh default collation version +After=postgresql.service +PartOf=postgresql.service + +[Service] +Type=oneshot +RemainAfterExit=yes +User=postgres +TimeoutStartSec=120 +Environment=PATH=/var/lib/postgresql/.nix-profile/bin +ExecStart=/nix/var/nix/profiles/site/bin/postgres_collation_refresh diff --git a/nix/packages/postgres-env.nix b/nix/packages/postgres-env.nix index 03a8f8058c..0ae009fff7 100644 --- a/nix/packages/postgres-env.nix +++ b/nix/packages/postgres-env.nix @@ -19,10 +19,7 @@ self'.packages.supabase-groonga self'.packages."postgresql_${version}_src" ] - ++ lib.optionals pkgs.stdenv.isLinux [ - self'.packages."postgresql_${version}_debug" - self'.packages.postgres-collation-refresh - ] + ++ lib.optionals pkgs.stdenv.isLinux [ self'.packages."postgresql_${version}_debug" ] ++ lib.optionals (pkgs.stdenv.isLinux && version != "15") [ self'.packages.gatekeeper ] # orioledb.so ships as a separate extension package (nix/ext/orioledb.nix), not # part of the postgresql derivation itself, so its debug output isn't covered by @@ -37,9 +34,6 @@ postgres-env-15 = makePostgresEnv "15"; postgres-env-17 = makePostgresEnv "17"; postgres-env-orioledb-17 = makePostgresEnv "orioledb-17"; - } - // lib.optionalAttrs pkgs.stdenv.isLinux { - postgres-collation-refresh = pkgs.callPackage ./postgres-collation-refresh.nix { }; }; }; } diff --git a/nix/packages/site-env.nix b/nix/packages/site-env.nix index 4fa580e99a..7b8e272cb2 100644 --- a/nix/packages/site-env.nix +++ b/nix/packages/site-env.nix @@ -13,7 +13,11 @@ version: extraPaths: pkgs.buildEnv { name = "site-env-${version}"; - paths = [ self'.legacyPackages."psql_${version}".exts.supautils ] ++ extraPaths; + paths = [ + self'.legacyPackages."psql_${version}".exts.supautils + self'.packages.postgres-collation-refresh + ] + ++ extraPaths; }; siteEnvs = { diff --git a/testinfra/test_ami_nix.py b/testinfra/test_ami_nix.py index 39a6f1033e..3c40ce5526 100644 --- a/testinfra/test_ami_nix.py +++ b/testinfra/test_ami_nix.py @@ -1617,13 +1617,13 @@ def test_postgres_prestart_does_not_reset_core_limit(host): def test_collation_refresh_unit_is_linked_and_not_failed(host): - """The collation refresh unit comes from the postgres env profile, is wanted - by postgresql.service, and does not fail on a freshly built image.""" + """The collation refresh unit is linked from the site profile, wanted by + postgresql.service, and does not fail on a freshly built image.""" unit = "postgresql-collation-refresh.service" + link = run_ssh_command(host["ssh"], f"readlink /etc/systemd/system/{unit}") - assert link["succeeded"], f"{unit} is not linked: {link['stderr']}" - assert link["stdout"].strip().startswith("/var/lib/postgresql/.nix-profile/"), ( - f"{unit} must link through the nix profile, got {link['stdout']!r}" + assert link["stdout"].strip().startswith("/nix/var/nix/profiles/site/"), ( + f"{unit} must link through the site profile, got {link['stdout']!r}" ) wants = run_ssh_command(host["ssh"], "systemctl show postgresql.service -p Wants") From c4f6c5e1110af22e65cdce31cc306b6602353090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Tue, 20 Oct 2026 05:51:23 +0300 Subject: [PATCH 4/7] refactor(nix): run the collation refresh as a SQL file Replace the shell script with a SQL file run by psql from the unit. The package installs the SQL and the unit, ansible links the unit and postgresql.service wants it. Shrink the VM test to a table of cases. Co-Authored-By: Claude Sonnet 5.5 --- .../postgresql_config/postgresql.service | 1 + ansible/tasks/setup-postgres.yml | 32 ++----- nix/ext/tests/collation-refresh.nix | 91 ++++++++----------- .../postgres-collation-refresh/default.nix | 30 ++---- .../postgres-collation-refresh.sh | 52 ----------- .../postgres-collation-refresh.sql | 25 +++++ .../postgresql-collation-refresh.service | 3 +- testinfra/test_ami_nix.py | 16 +--- 8 files changed, 83 insertions(+), 167 deletions(-) delete mode 100644 nix/packages/postgres-collation-refresh/postgres-collation-refresh.sh create mode 100644 nix/packages/postgres-collation-refresh/postgres-collation-refresh.sql diff --git a/ansible/files/postgresql_config/postgresql.service b/ansible/files/postgresql_config/postgresql.service index 68c37140bd..e3eecd2b0b 100644 --- a/ansible/files/postgresql_config/postgresql.service +++ b/ansible/files/postgresql_config/postgresql.service @@ -1,6 +1,7 @@ [Unit] Description=PostgreSQL database server Documentation=man:postgres(1) +Wants=postgresql-collation-refresh.service {% if supabase_internal is defined %} Requires=database-optimizations.service After=database-optimizations.service diff --git a/ansible/tasks/setup-postgres.yml b/ansible/tasks/setup-postgres.yml index 1007601644..bd5390512e 100644 --- a/ansible/tasks/setup-postgres.yml +++ b/ansible/tasks/setup-postgres.yml @@ -253,6 +253,13 @@ loop_control: loop_var: 'systemd_svc_item' +- name: Link the collation refresh unit from the site profile + when: stage2 + ansible.builtin.file: + src: /nix/var/nix/profiles/site/lib/systemd/system/postgresql-collation-refresh.service + dest: /etc/systemd/system/postgresql-collation-refresh.service + state: link + - name: initialize pg required state ansible.builtin.file: group: 'postgres' @@ -318,31 +325,6 @@ enabled: true name: 'postgresql' -# Link through the site profile, not the store path: `systemctl enable` resolves -# the symlink and pins the unit to the store path of the current generation. -- name: Link the collation refresh unit from the site profile - when: stage2 - ansible.builtin.file: - src: /nix/var/nix/profiles/site/lib/systemd/system/postgresql-collation-refresh.service - dest: /etc/systemd/system/postgresql-collation-refresh.service - state: link - -- name: Make postgresql want the collation refresh unit - when: stage2 - ansible.builtin.copy: - dest: /etc/systemd/system/postgresql.service.d/collation-refresh.conf - content: | - [Unit] - Wants=postgresql-collation-refresh.service - owner: root - group: root - mode: '0644' - -- name: Reload systemd for the collation refresh unit - when: stage2 - ansible.builtin.systemd_service: - daemon_reload: true - - name: Add lang and locale items to .bashrc when: not stage2 ansible.builtin.lineinfile: diff --git a/nix/ext/tests/collation-refresh.nix b/nix/ext/tests/collation-refresh.nix index 09a406fedb..e426fce6ff 100644 --- a/nix/ext/tests/collation-refresh.nix +++ b/nix/ext/tests/collation-refresh.nix @@ -3,6 +3,7 @@ let system = pkgs.pkgsLinux.stdenv.hostPlatform.system; testLib = import ./lib.nix { inherit self pkgs; }; refresh = self.packages.${system}.postgres-collation-refresh; + psql = self.packages.${system}."psql_17/bin"; in pkgs.testers.runNixOSTest { name = "collation-refresh"; @@ -22,7 +23,7 @@ pkgs.testers.runNixOSTest { ]; systemd.tmpfiles.rules = [ - "L+ /var/lib/postgresql/.nix-profile - - - - ${self.packages.${system}."psql_17/bin"}" + "L+ /var/lib/postgresql/.nix-profile - - - - ${psql}" "L+ /nix/var/nix/profiles/site - - - - ${refresh}" ]; systemd.packages = [ refresh ]; @@ -30,7 +31,7 @@ pkgs.testers.runNixOSTest { }; testScript = '' PSQL = "psql -h localhost -U supabase_admin -d postgres -At -v ON_ERROR_STOP=1" - REFRESH = "${refresh}/bin/postgres_collation_refresh" + UNIT = "postgresql-collation-refresh.service" def sql(query): return server.succeed(PSQL + " -c \"" + query + "\"").strip() @@ -41,71 +42,57 @@ pkgs.testers.runNixOSTest { def set_recorded(db, version): sql("update pg_database set datcollversion = '" + version + "' where datname = '" + db + "'") - def create(db, options): - sql("create database " + db + " " + options + " template template0") - start_all() server.wait_for_unit("supabase-db-init.service") - server.wait_for_unit("postgresql-collation-refresh.service") - - create("g", "locale_provider = libc locale = 'en_US.UTF-8'") - create("old", "locale_provider = libc locale = 'en_US.UTF-8'") - create("de", "locale_provider = libc locale = 'de_DE.UTF-8'") - create("c", "locale_provider = libc locale = 'C'") - create("i", "locale_provider = icu locale = 'en_US.UTF-8' icu_locale = 'en-US'") - actual = sql("select pg_database_collation_actual_version(oid) from pg_database where datname = 'g'") - icu_actual = recorded("i") - print(f"glibc collation version: {actual}, icu: {icu_actual}") + server.wait_for_unit(UNIT) - def fresh(): - for db in ["g", "old", "de"]: - set_recorded(db, actual) + DBS = { + "g": "libc locale = 'en_US.UTF-8'", + "old": "libc locale = 'en_US.UTF-8'", + "de": "libc locale = 'de_DE.UTF-8'", + "c": "libc locale = 'C'", + "i": "icu locale = 'en_US.UTF-8' icu_locale = 'en-US'", + } + for db, options in DBS.items(): + sql("create database " + db + " locale_provider = " + options + " template template0") + actual = recorded("g") + fresh = {"g": actual, "old": actual, "de": actual, "i": recorded("i")} - with subtest("Nothing stale is a no-op"): - server.succeed(REFRESH) + def reset(stale): + for db, version in {**fresh, **stale}.items(): + set_recorded(db, version) - with subtest("A stale glibc en_US database inside the bounds is refreshed"): - set_recorded("g", "2.31") - server.succeed(REFRESH) - assert recorded("g") == actual, f"expected {actual}, got {recorded('g')}" + def check(title, stale, refreshed, fails): + with subtest(title): + reset(stale) + status, _ = server.execute("systemctl restart " + UNIT) + assert (status != 0) == fails, f"unit status {status}, expected failure: {fails}" + for db, version in stale.items(): + expected = actual if db in refreshed else version + assert recorded(db) == expected, f"{db}: expected {expected}, got {recorded(db)}" + assert recorded("c") == "-" - with subtest("ICU, non-English glibc and C databases are left alone and do not fail the run"): - set_recorded("de", "2.31") - set_recorded("i", "1.0") - server.succeed(REFRESH) - assert recorded("de") == "2.31" - assert recorded("i") == "1.0" - assert recorded("c") == "-" - set_recorded("i", icu_actual) + check("Nothing stale", {}, [], False) + check("A stale glibc en_US database inside the bounds is refreshed", {"g": "2.31"}, ["g"], False) + check("ICU and non-English glibc databases are left alone", {"de": "2.31", "i": "1.0"}, [], False) + check("A database older than 2.31 fails the unit, the others are refreshed", {"old": "2.28", "g": "2.31"}, ["g"], True) with subtest("A database that fails to refresh does not stop the others"): - fresh() sql("create role refresher login") sql("create database a_other locale_provider = libc locale = 'en_US.UTF-8' template template0") sql("create database b_owned owner refresher locale_provider = libc locale = 'en_US.UTF-8' template template0") - set_recorded("a_other", "2.31") - set_recorded("b_owned", "2.31") - status, _ = server.execute("PGUSER=refresher " + REFRESH) - assert status == 1, f"expected exit 1, got {status}" + reset({"a_other": "2.31", "b_owned": "2.31"}) + status, _ = server.execute("psql -h localhost -U refresher -d postgres -X -q -f /nix/var/nix/profiles/site/share/postgres-collation-refresh.sql") + assert status != 0 assert recorded("a_other") == "2.31" - assert recorded("b_owned") == actual, f"expected {actual}, got {recorded('b_owned')}" - set_recorded("a_other", actual) - - with subtest("Databases without a recorded version are ignored"): - sql("update pg_database set datcollversion = null where datname = 'template0'") - server.succeed(REFRESH) - assert recorded("template0") == "-" + assert recorded("b_owned") == actual - with subtest("A postgres restart reruns the unit: it refreshes what it can, fails on a glibc database older than 2.31, and leaves postgres active"): - fresh() - set_recorded("old", "2.28") - set_recorded("g", "2.31") + with subtest("A postgres restart reruns the unit and leaves postgres active when it fails"): + reset({"old": "2.28", "g": "2.31", "a_other": actual}) server.succeed("systemctl restart postgresql") - server.wait_until_succeeds( - PSQL + " -c \"select 1 from pg_database where datname = 'g' and datcollversion = '" + actual + "'\" | grep -q 1" - ) - server.wait_until_succeeds("systemctl is-failed postgresql-collation-refresh.service") + server.wait_until_succeeds("systemctl is-failed " + UNIT) server.succeed("systemctl is-active postgresql.service") + assert recorded("g") == actual assert recorded("old") == "2.28" ''; } diff --git a/nix/packages/postgres-collation-refresh/default.nix b/nix/packages/postgres-collation-refresh/default.nix index 328c056ac9..2ac32bb17a 100644 --- a/nix/packages/postgres-collation-refresh/default.nix +++ b/nix/packages/postgres-collation-refresh/default.nix @@ -1,10 +1,8 @@ { lib, stdenv, - writeShellApplication, runCommand, - symlinkJoin, - coreutils, + replaceVars, }: let minGlibc = "2.31"; @@ -16,23 +14,9 @@ assert lib.assertMsg || lib.versionAtLeast maxGlibc (lib.versions.majorMinor stdenv.cc.libc.version) ) "glibc ${stdenv.cc.libc.version} is above the reviewed maximum ${maxGlibc}. Review English collation changes, then raise maxGlibc in nix/packages/postgres-collation-refresh/default.nix."; -let - script = writeShellApplication { - name = "postgres_collation_refresh"; - runtimeInputs = [ coreutils ]; - runtimeEnv = { - MIN_GLIBC = minGlibc; - MAX_GLIBC = maxGlibc; - }; - text = builtins.readFile ./postgres-collation-refresh.sh; - }; -in -symlinkJoin { - name = "postgres-collation-refresh"; - paths = [ - script - (runCommand "postgres-collation-refresh-unit" { } '' - install -Dm0644 ${./postgresql-collation-refresh.service} $out/lib/systemd/system/postgresql-collation-refresh.service - '') - ]; -} +runCommand "postgres-collation-refresh" { } '' + install -Dm0644 ${ + replaceVars ./postgres-collation-refresh.sql { inherit minGlibc maxGlibc; } + } $out/share/postgres-collation-refresh.sql + install -Dm0644 ${./postgresql-collation-refresh.service} $out/lib/systemd/system/postgresql-collation-refresh.service +'' diff --git a/nix/packages/postgres-collation-refresh/postgres-collation-refresh.sh b/nix/packages/postgres-collation-refresh/postgres-collation-refresh.sh deleted file mode 100644 index 3ce16d99a5..0000000000 --- a/nix/packages/postgres-collation-refresh/postgres-collation-refresh.sh +++ /dev/null @@ -1,52 +0,0 @@ -# shellcheck shell=bash -export PGHOST="${PGHOST:-localhost}" -export PGUSER="${PGUSER:-supabase_admin}" - -run_sql() { - psql -X -q -A -t -v ON_ERROR_STOP=1 -d postgres "$@" -} - -# English ordering is identical across MIN_GLIBC..MAX_GLIBC, so a stale glibc -# en_US database is only refreshed when both versions sit in that range. -checked_cte() { - cat < pg_database_collation_actual_version(oid) -), -checked as ( - select datname, - recorded between string_to_array('$MIN_GLIBC', '.')::int[] and string_to_array('$MAX_GLIBC', '.')::int[] - and actual between string_to_array('$MIN_GLIBC', '.')::int[] and string_to_array('$MAX_GLIBC', '.')::int[] as in_bounds - from stale -) -SQL -} - -if [ "$(run_sql -c "select pg_is_in_recovery()")" = "t" ]; then - echo "collation_refresh: replica, skipping" - exit 0 -fi - -# No ON_ERROR_STOP: one failing database must not stop the others. Failures -# surface in the remaining count below. -psql -X -q -A -t -d postgres < pg_database_collation_actual_version(oid); + +\set ON_ERROR_STOP off +select format('alter database %I refresh collation version', datname) from stale where in_bounds order by datname +\gexec + +\set ON_ERROR_STOP on +do $$ begin + if exists (select from stale) then + raise exception 'collation_refresh: % database(s) still stale', (select count(*) from stale); + end if; +end $$; diff --git a/nix/packages/postgres-collation-refresh/postgresql-collation-refresh.service b/nix/packages/postgres-collation-refresh/postgresql-collation-refresh.service index 55ea3cbcf0..fc4d226168 100644 --- a/nix/packages/postgres-collation-refresh/postgresql-collation-refresh.service +++ b/nix/packages/postgres-collation-refresh/postgresql-collation-refresh.service @@ -8,5 +8,4 @@ Type=oneshot RemainAfterExit=yes User=postgres TimeoutStartSec=120 -Environment=PATH=/var/lib/postgresql/.nix-profile/bin -ExecStart=/nix/var/nix/profiles/site/bin/postgres_collation_refresh +ExecStart=/var/lib/postgresql/.nix-profile/bin/psql -X -q -h localhost -U supabase_admin -d postgres -f /nix/var/nix/profiles/site/share/postgres-collation-refresh.sql diff --git a/testinfra/test_ami_nix.py b/testinfra/test_ami_nix.py index 3c40ce5526..fe5d614615 100644 --- a/testinfra/test_ami_nix.py +++ b/testinfra/test_ami_nix.py @@ -1617,22 +1617,12 @@ def test_postgres_prestart_does_not_reset_core_limit(host): def test_collation_refresh_unit_is_linked_and_not_failed(host): - """The collation refresh unit is linked from the site profile, wanted by - postgresql.service, and does not fail on a freshly built image.""" + """The collation refresh unit links from the site profile and is active.""" unit = "postgresql-collation-refresh.service" - link = run_ssh_command(host["ssh"], f"readlink /etc/systemd/system/{unit}") - assert link["stdout"].strip().startswith("/nix/var/nix/profiles/site/"), ( - f"{unit} must link through the site profile, got {link['stdout']!r}" - ) - - wants = run_ssh_command(host["ssh"], "systemctl show postgresql.service -p Wants") - assert unit in wants["stdout"], f"postgresql.service does not want {unit}" - + assert link["stdout"].startswith("/nix/var/nix/profiles/site/"), link state = run_ssh_command(host["ssh"], f"systemctl is-active {unit}") - assert state["stdout"].strip() == "active", ( - f"{unit} expected active, got {state['stdout'].strip()!r}" - ) + assert state["stdout"].strip() == "active", state def _crash_a_backend_and_wait_for_recovery(host): From 40e989b0d02aca41c59e024bd3981b461391644e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Mon, 26 Oct 2026 11:47:55 +0200 Subject: [PATCH 5/7] refactor(nix): name stale databases in the collation refresh error Refs: MPG-1396 Co-Authored-By: Claude Sonnet 5.5 --- .../postgres-collation-refresh.sql | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/nix/packages/postgres-collation-refresh/postgres-collation-refresh.sql b/nix/packages/postgres-collation-refresh/postgres-collation-refresh.sql index 34affd077c..0442eea325 100644 --- a/nix/packages/postgres-collation-refresh/postgres-collation-refresh.sql +++ b/nix/packages/postgres-collation-refresh/postgres-collation-refresh.sql @@ -18,8 +18,11 @@ select format('alter database %I refresh collation version', datname) from stale \gexec \set ON_ERROR_STOP on -do $$ begin - if exists (select from stale) then - raise exception 'collation_refresh: % database(s) still stale', (select count(*) from stale); +do $$ +declare names text; +begin + select string_agg(format('%I', datname), ', ' order by datname) into names from stale; + if names is not null then + raise exception 'collation_refresh: still stale: %', names; end if; end $$; From bd2a7bd13f590f67a3c6fafc72611ad10de54448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Mon, 26 Oct 2026 13:23:22 +0200 Subject: [PATCH 6/7] test: fix the collation refresh restart race and log the unit journal Reset the failed state before restarting postgres so is-failed cannot match the previous run. Print the unit journal when the testinfra check finds the unit not active. Refs: MPG-1396 Co-Authored-By: Claude Sonnet 5.5 --- nix/ext/tests/collation-refresh.nix | 1 + testinfra/test_ami_nix.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/nix/ext/tests/collation-refresh.nix b/nix/ext/tests/collation-refresh.nix index e426fce6ff..662b44423c 100644 --- a/nix/ext/tests/collation-refresh.nix +++ b/nix/ext/tests/collation-refresh.nix @@ -89,6 +89,7 @@ pkgs.testers.runNixOSTest { with subtest("A postgres restart reruns the unit and leaves postgres active when it fails"): reset({"old": "2.28", "g": "2.31", "a_other": actual}) + server.succeed("systemctl reset-failed " + UNIT) server.succeed("systemctl restart postgresql") server.wait_until_succeeds("systemctl is-failed " + UNIT) server.succeed("systemctl is-active postgresql.service") diff --git a/testinfra/test_ami_nix.py b/testinfra/test_ami_nix.py index fe5d614615..89b1ddb1a3 100644 --- a/testinfra/test_ami_nix.py +++ b/testinfra/test_ami_nix.py @@ -1622,7 +1622,8 @@ def test_collation_refresh_unit_is_linked_and_not_failed(host): link = run_ssh_command(host["ssh"], f"readlink /etc/systemd/system/{unit}") assert link["stdout"].startswith("/nix/var/nix/profiles/site/"), link state = run_ssh_command(host["ssh"], f"systemctl is-active {unit}") - assert state["stdout"].strip() == "active", state + journal = run_ssh_command(host["ssh"], f"journalctl -u {unit} --no-pager -n 40") + assert state["stdout"].strip() == "active", journal["stdout"] def _crash_a_backend_and_wait_for_recovery(host): From 34e18f33438db044111d0908d9a5ea29d41d2270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Mon, 26 Oct 2026 13:46:52 +0200 Subject: [PATCH 7/7] test(nix): cover both collation refresh bounds with one out of range database Refs: MPG-1396 Co-Authored-By: Claude Sonnet 5.5 --- nix/ext/tests/collation-refresh.nix | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/nix/ext/tests/collation-refresh.nix b/nix/ext/tests/collation-refresh.nix index 662b44423c..483339af50 100644 --- a/nix/ext/tests/collation-refresh.nix +++ b/nix/ext/tests/collation-refresh.nix @@ -48,7 +48,7 @@ pkgs.testers.runNixOSTest { DBS = { "g": "libc locale = 'en_US.UTF-8'", - "old": "libc locale = 'en_US.UTF-8'", + "out": "libc locale = 'en_US.UTF-8'", "de": "libc locale = 'de_DE.UTF-8'", "c": "libc locale = 'C'", "i": "icu locale = 'en_US.UTF-8' icu_locale = 'en-US'", @@ -56,7 +56,7 @@ pkgs.testers.runNixOSTest { for db, options in DBS.items(): sql("create database " + db + " locale_provider = " + options + " template template0") actual = recorded("g") - fresh = {"g": actual, "old": actual, "de": actual, "i": recorded("i")} + fresh = {"g": actual, "out": actual, "de": actual, "i": recorded("i")} def reset(stale): for db, version in {**fresh, **stale}.items(): @@ -75,7 +75,8 @@ pkgs.testers.runNixOSTest { check("Nothing stale", {}, [], False) check("A stale glibc en_US database inside the bounds is refreshed", {"g": "2.31"}, ["g"], False) check("ICU and non-English glibc databases are left alone", {"de": "2.31", "i": "1.0"}, [], False) - check("A database older than 2.31 fails the unit, the others are refreshed", {"old": "2.28", "g": "2.31"}, ["g"], True) + check("A database above 2.44 fails the unit, the others are refreshed", {"out": "2.99", "g": "2.31"}, ["g"], True) + check("A database below 2.31 fails the unit", {"out": "2.28"}, [], True) with subtest("A database that fails to refresh does not stop the others"): sql("create role refresher login") @@ -88,12 +89,12 @@ pkgs.testers.runNixOSTest { assert recorded("b_owned") == actual with subtest("A postgres restart reruns the unit and leaves postgres active when it fails"): - reset({"old": "2.28", "g": "2.31", "a_other": actual}) + reset({"out": "2.99", "g": "2.31", "a_other": actual}) server.succeed("systemctl reset-failed " + UNIT) server.succeed("systemctl restart postgresql") server.wait_until_succeeds("systemctl is-failed " + UNIT) server.succeed("systemctl is-active postgresql.service") assert recorded("g") == actual - assert recorded("old") == "2.28" + assert recorded("out") == "2.99" ''; }