From 705284d913642bfce2db8d895eb6a3f90d548b09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Tue, 8 Sep 2026 22:40:51 +0300 Subject: [PATCH 01/29] ci: fail nix package builds that exceed glibc 2.31 floor Guards against binaries silently requiring a newer glibc than customer AMIs ship. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/nix-build.yml | 6 ++++++ scripts/check-glibc-floor.sh | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100755 scripts/check-glibc-floor.sh diff --git a/.github/workflows/nix-build.yml b/.github/workflows/nix-build.yml index e286015cca..f379d07a59 100644 --- a/.github/workflows/nix-build.yml +++ b/.github/workflows/nix-build.yml @@ -73,6 +73,9 @@ jobs: uses: ./.github/actions/nix-build-retry with: attr: .#${{ matrix.attr }} + - name: Check glibc floor + if: ${{ matrix.attr != '' }} + run: ./scripts/check-glibc-floor.sh "${{ matrix.attr }}" nix-build-checks-aarch64-linux: name: >- @@ -192,6 +195,9 @@ jobs: uses: ./.github/actions/nix-build-retry with: attr: .#${{ matrix.attr }} + - name: Check glibc floor + if: ${{ matrix.attr != '' }} + run: ./scripts/check-glibc-floor.sh "${{ matrix.attr }}" nix-build-checks-x86_64-linux: name: >- diff --git a/scripts/check-glibc-floor.sh b/scripts/check-glibc-floor.sh new file mode 100755 index 0000000000..6d888a9d50 --- /dev/null +++ b/scripts/check-glibc-floor.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# Fails if any file in ./result requires a glibc symbol version above MAX_ALLOWED. +set -Eeu -o pipefail + +MAX_ALLOWED="2.31" + +FLOOR=$(find -L result -type f -exec objdump -T {} \; 2>/dev/null \ + | grep -oE 'GLIBC_[0-9.]+' | sed 's/GLIBC_//' | sort -V | tail -1) + +if [ -z "$FLOOR" ]; then + exit 0 +fi + +echo "glibc floor: $FLOOR (max allowed: $MAX_ALLOWED)" +if [ "$(printf '%s\n%s' "$MAX_ALLOWED" "$FLOOR" | sort -V | tail -1)" != "$MAX_ALLOWED" ]; then + echo "::error::glibc floor $FLOOR exceeds max allowed $MAX_ALLOWED in ${1:-result}" + exit 1 +fi From e77bbbdd8bdb499bde74dda9fcccf659a2ffb21a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Tue, 8 Sep 2026 23:03:12 +0300 Subject: [PATCH 02/29] ci: report offending file path in glibc floor check Co-Authored-By: Claude Sonnet 5 --- scripts/check-glibc-floor.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/check-glibc-floor.sh b/scripts/check-glibc-floor.sh index 6d888a9d50..d3230167ca 100755 --- a/scripts/check-glibc-floor.sh +++ b/scripts/check-glibc-floor.sh @@ -4,15 +4,17 @@ set -Eeu -o pipefail MAX_ALLOWED="2.31" -FLOOR=$(find -L result -type f -exec objdump -T {} \; 2>/dev/null \ - | grep -oE 'GLIBC_[0-9.]+' | sed 's/GLIBC_//' | sort -V | tail -1) +HIT=$(find -L result -type f -exec sh -c \ + 'objdump -T "$1" 2>/dev/null | grep -oE "GLIBC_[0-9.]+" | sed -E "s#GLIBC_([0-9.]+)#\1 $1#"' _ {} \; \ + | sort -V | tail -1) -if [ -z "$FLOOR" ]; then +if [ -z "$HIT" ]; then exit 0 fi +FLOOR=${HIT%% *} echo "glibc floor: $FLOOR (max allowed: $MAX_ALLOWED)" if [ "$(printf '%s\n%s' "$MAX_ALLOWED" "$FLOOR" | sort -V | tail -1)" != "$MAX_ALLOWED" ]; then - echo "::error::glibc floor $FLOOR exceeds max allowed $MAX_ALLOWED in ${1:-result}" + echo "::error::glibc floor $HIT exceeds max allowed $MAX_ALLOWED in ${1:-result}" exit 1 fi From d4127eaca9d93d1742b345fe33373e8c2fb6d24b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Tue, 8 Sep 2026 23:17:16 +0300 Subject: [PATCH 03/29] style: shfmt check-glibc-floor.sh Co-Authored-By: Claude Sonnet 5 --- scripts/check-glibc-floor.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/check-glibc-floor.sh b/scripts/check-glibc-floor.sh index d3230167ca..ce2d178736 100755 --- a/scripts/check-glibc-floor.sh +++ b/scripts/check-glibc-floor.sh @@ -5,16 +5,16 @@ set -Eeu -o pipefail MAX_ALLOWED="2.31" HIT=$(find -L result -type f -exec sh -c \ - 'objdump -T "$1" 2>/dev/null | grep -oE "GLIBC_[0-9.]+" | sed -E "s#GLIBC_([0-9.]+)#\1 $1#"' _ {} \; \ - | sort -V | tail -1) + 'objdump -T "$1" 2>/dev/null | grep -oE "GLIBC_[0-9.]+" | sed -E "s#GLIBC_([0-9.]+)#\1 $1#"' _ {} \; | + sort -V | tail -1) if [ -z "$HIT" ]; then - exit 0 + exit 0 fi FLOOR=${HIT%% *} echo "glibc floor: $FLOOR (max allowed: $MAX_ALLOWED)" if [ "$(printf '%s\n%s' "$MAX_ALLOWED" "$FLOOR" | sort -V | tail -1)" != "$MAX_ALLOWED" ]; then - echo "::error::glibc floor $HIT exceeds max allowed $MAX_ALLOWED in ${1:-result}" - exit 1 + echo "::error::glibc floor $HIT exceeds max allowed $MAX_ALLOWED in ${1:-result}" + exit 1 fi From 1a8820eb4f9d0ad2c4181e807a93288631e027e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Sun, 13 Sep 2026 22:07:34 +0300 Subject: [PATCH 04/29] ci: move glibc floor check into nix flake check Runs as checks..glibc-floor, auto-discovered by the existing nix-eval matrix generator, so no workflow changes are needed. Scans every legacyPackages derivation instead of one step per matrix leg. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/nix-build.yml | 6 ------ nix/checks.nix | 27 +++++++++++++++++++++++++++ scripts/check-glibc-floor.sh | 20 -------------------- 3 files changed, 27 insertions(+), 26 deletions(-) delete mode 100755 scripts/check-glibc-floor.sh diff --git a/.github/workflows/nix-build.yml b/.github/workflows/nix-build.yml index f379d07a59..e286015cca 100644 --- a/.github/workflows/nix-build.yml +++ b/.github/workflows/nix-build.yml @@ -73,9 +73,6 @@ jobs: uses: ./.github/actions/nix-build-retry with: attr: .#${{ matrix.attr }} - - name: Check glibc floor - if: ${{ matrix.attr != '' }} - run: ./scripts/check-glibc-floor.sh "${{ matrix.attr }}" nix-build-checks-aarch64-linux: name: >- @@ -195,9 +192,6 @@ jobs: uses: ./.github/actions/nix-build-retry with: attr: .#${{ matrix.attr }} - - name: Check glibc floor - if: ${{ matrix.attr != '' }} - run: ./scripts/check-glibc-floor.sh "${{ matrix.attr }}" nix-build-checks-x86_64-linux: name: >- diff --git a/nix/checks.nix b/nix/checks.nix index 3fdd5ba2c0..7e1135ad7e 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -954,6 +954,33 @@ postgresql_17_src ; psql_orioledb-17_exts_orioledb_debug = self'.legacyPackages.psql_orioledb-17.exts.orioledb.debug; + glibc-floor = + pkgs.runCommand "glibc-floor-check" + { + nativeBuildInputs = [ pkgs.binutils ]; + paths = lib.collect lib.isDerivation self'.legacyPackages; + } + '' + MAX_ALLOWED="2.31" + HIT=$( + for p in $paths; do find -L "$p" -type f; done \ + | while IFS= read -r f; do + objdump -T "$f" 2>/dev/null | grep -oE 'GLIBC_[0-9.]+' | sed -E "s#GLIBC_([0-9.]+)#\1 $f#" + done \ + | sort -V | tail -1 + ) + + if [ -n "$HIT" ]; then + FLOOR=''${HIT%% *} + echo "glibc floor: $FLOOR (max allowed: $MAX_ALLOWED)" + if [ "$(printf '%s\n%s' "$MAX_ALLOWED" "$FLOOR" | sort -V | tail -1)" != "$MAX_ALLOWED" ]; then + echo "glibc floor $HIT exceeds max allowed $MAX_ALLOWED" + exit 1 + fi + fi + + touch $out + ''; }; }; } diff --git a/scripts/check-glibc-floor.sh b/scripts/check-glibc-floor.sh deleted file mode 100755 index ce2d178736..0000000000 --- a/scripts/check-glibc-floor.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -# Fails if any file in ./result requires a glibc symbol version above MAX_ALLOWED. -set -Eeu -o pipefail - -MAX_ALLOWED="2.31" - -HIT=$(find -L result -type f -exec sh -c \ - 'objdump -T "$1" 2>/dev/null | grep -oE "GLIBC_[0-9.]+" | sed -E "s#GLIBC_([0-9.]+)#\1 $1#"' _ {} \; | - sort -V | tail -1) - -if [ -z "$HIT" ]; then - exit 0 -fi - -FLOOR=${HIT%% *} -echo "glibc floor: $FLOOR (max allowed: $MAX_ALLOWED)" -if [ "$(printf '%s\n%s' "$MAX_ALLOWED" "$FLOOR" | sort -V | tail -1)" != "$MAX_ALLOWED" ]; then - echo "::error::glibc floor $HIT exceeds max allowed $MAX_ALLOWED in ${1:-result}" - exit 1 -fi From 19a5573700f8d0485c9a867d17051129197efa63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Sun, 13 Sep 2026 22:17:05 +0300 Subject: [PATCH 05/29] ci: rewrite glibc-floor check logic in nushell Same find/objdump/version-compare logic, packaged via pkgs.writers.writeNuBin. Verified standalone against a fake objdump (pass/fail/no-match cases) since the local linux-builder VM's clock is currently skewed and blocking real nix builds unrelated to this change. Co-Authored-By: Claude Sonnet 5 --- nix/checks.nix | 35 ++++++++++------------------ nix/tools/check-glibc-floor.nu | 42 ++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 23 deletions(-) create mode 100644 nix/tools/check-glibc-floor.nu diff --git a/nix/checks.nix b/nix/checks.nix index 7e1135ad7e..91a31a4046 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -955,30 +955,19 @@ ; psql_orioledb-17_exts_orioledb_debug = self'.legacyPackages.psql_orioledb-17.exts.orioledb.debug; glibc-floor = - pkgs.runCommand "glibc-floor-check" - { - nativeBuildInputs = [ pkgs.binutils ]; - paths = lib.collect lib.isDerivation self'.legacyPackages; - } + let + checkScript = pkgs.writers.writeNuBin "check-glibc-floor" { + makeWrapperArgs = [ + "--prefix" + "PATH" + ":" + "${lib.makeBinPath [ pkgs.binutils ]}" + ]; + } (builtins.readFile ./tools/check-glibc-floor.nu); + in + pkgs.runCommand "glibc-floor-check" { paths = lib.collect lib.isDerivation self'.legacyPackages; } '' - MAX_ALLOWED="2.31" - HIT=$( - for p in $paths; do find -L "$p" -type f; done \ - | while IFS= read -r f; do - objdump -T "$f" 2>/dev/null | grep -oE 'GLIBC_[0-9.]+' | sed -E "s#GLIBC_([0-9.]+)#\1 $f#" - done \ - | sort -V | tail -1 - ) - - if [ -n "$HIT" ]; then - FLOOR=''${HIT%% *} - echo "glibc floor: $FLOOR (max allowed: $MAX_ALLOWED)" - if [ "$(printf '%s\n%s' "$MAX_ALLOWED" "$FLOOR" | sort -V | tail -1)" != "$MAX_ALLOWED" ]; then - echo "glibc floor $HIT exceeds max allowed $MAX_ALLOWED" - exit 1 - fi - fi - + ${lib.getExe checkScript} 2.31 $paths touch $out ''; }; diff --git a/nix/tools/check-glibc-floor.nu b/nix/tools/check-glibc-floor.nu new file mode 100644 index 0000000000..f0c89bbfcf --- /dev/null +++ b/nix/tools/check-glibc-floor.nu @@ -0,0 +1,42 @@ +# Fails if any file under the given paths requires a glibc symbol version +# above the allowed floor. + +def "ver-key" [ver: string] { + $ver | split row "." | each { into int } +} + +def main [max_allowed: string, ...paths: string] { + let hits = ( + $paths + | each { |p| glob ($p + "/**/*") } + | flatten + | where { |f| ($f | path type) == file } + | each { |f| + let res = (do { ^objdump -T $f } | complete) + if $res.exit_code != 0 { + [] + } else { + $res.stdout | parse -r 'GLIBC_(?[0-9.]+)' | each { |m| { ver: $m.ver, file: $f } } + } + } + | flatten + ) + + if ($hits | is-empty) { + exit 0 + } + + let worst = ( + $hits + | insert key { |h| ver-key $h.ver } + | sort-by key + | last + ) + + print $"glibc floor: ($worst.ver) \(max allowed: ($max_allowed)\) — ($worst.file)" + + if (ver-key $worst.ver) > (ver-key $max_allowed) { + print $"glibc floor ($worst.ver) exceeds max allowed ($max_allowed) in ($worst.file)" + exit 1 + } +} From 06ebba7e1d5a440342e60b280e2aabed04e6f05c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Sun, 13 Sep 2026 22:27:03 +0300 Subject: [PATCH 06/29] ci: list every offending file in glibc-floor check Drop the single-worst summary line; print one line per file that exceeds the floor (deduped to its own worst symbol version), plus a short pass line when nothing offends. Co-Authored-By: Claude Sonnet 5 --- nix/tools/check-glibc-floor.nu | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/nix/tools/check-glibc-floor.nu b/nix/tools/check-glibc-floor.nu index f0c89bbfcf..03e569f005 100644 --- a/nix/tools/check-glibc-floor.nu +++ b/nix/tools/check-glibc-floor.nu @@ -6,37 +6,37 @@ def "ver-key" [ver: string] { } def main [max_allowed: string, ...paths: string] { - let hits = ( + let max_key = (ver-key $max_allowed) + + let offenders = ( $paths | each { |p| glob ($p + "/**/*") } | flatten | where { |f| ($f | path type) == file } | each { |f| let res = (do { ^objdump -T $f } | complete) - if $res.exit_code != 0 { + let vers = (if $res.exit_code == 0 { + $res.stdout | parse -r 'GLIBC_(?[0-9.]+)' | get ver + } else { [] + }) + if ($vers | is-empty) { + null } else { - $res.stdout | parse -r 'GLIBC_(?[0-9.]+)' | each { |m| { ver: $m.ver, file: $f } } + { file: $f, ver: ($vers | sort-by { |v| ver-key $v } | last) } } } - | flatten + | compact + | where { |h| (ver-key $h.ver) > $max_key } ) - if ($hits | is-empty) { + if ($offenders | is-empty) { + print $"glibc floor OK \(<= ($max_allowed)\)" exit 0 } - let worst = ( - $hits - | insert key { |h| ver-key $h.ver } - | sort-by key - | last - ) - - print $"glibc floor: ($worst.ver) \(max allowed: ($max_allowed)\) — ($worst.file)" - - if (ver-key $worst.ver) > (ver-key $max_allowed) { - print $"glibc floor ($worst.ver) exceeds max allowed ($max_allowed) in ($worst.file)" - exit 1 + for o in $offenders { + print $"glibc floor ($o.ver) exceeds max allowed ($max_allowed) in ($o.file)" } + exit 1 } From e6578892e4b6c1300a74eb75a3072c2458dfffb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Sun, 13 Sep 2026 22:43:08 +0300 Subject: [PATCH 07/29] ci: idiomatic nushell refactor for glibc-floor check Print offenders as a table instead of hand-formatted strings, run objdump via par-each, drop the do{}/exit_code plumbing (a failing objdump naturally yields no stdout, so it's already filtered out). Co-Authored-By: Claude Sonnet 5 --- nix/tools/check-glibc-floor.nu | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/nix/tools/check-glibc-floor.nu b/nix/tools/check-glibc-floor.nu index 03e569f005..1218a65c52 100644 --- a/nix/tools/check-glibc-floor.nu +++ b/nix/tools/check-glibc-floor.nu @@ -1,7 +1,7 @@ # Fails if any file under the given paths requires a glibc symbol version # above the allowed floor. -def "ver-key" [ver: string] { +def ver-key [ver: string] { $ver | split row "." | each { into int } } @@ -10,24 +10,19 @@ def main [max_allowed: string, ...paths: string] { let offenders = ( $paths - | each { |p| glob ($p + "/**/*") } + | each { |p| glob $"($p)/**/*" } | flatten | where { |f| ($f | path type) == file } - | each { |f| - let res = (do { ^objdump -T $f } | complete) - let vers = (if $res.exit_code == 0 { - $res.stdout | parse -r 'GLIBC_(?[0-9.]+)' | get ver - } else { - [] - }) - if ($vers | is-empty) { + | par-each { |file| + let versions = (^objdump -T $file | complete | get stdout | parse -r 'GLIBC_(?[0-9.]+)' | get ver) + if ($versions | is-empty) { null } else { - { file: $f, ver: ($vers | sort-by { |v| ver-key $v } | last) } + { file: $file, version: ($versions | sort-by { |v| ver-key $v } | last) } } } | compact - | where { |h| (ver-key $h.ver) > $max_key } + | where { |h| (ver-key $h.version) > $max_key } ) if ($offenders | is-empty) { @@ -35,8 +30,6 @@ def main [max_allowed: string, ...paths: string] { exit 0 } - for o in $offenders { - print $"glibc floor ($o.ver) exceeds max allowed ($max_allowed) in ($o.file)" - } + print $offenders exit 1 } From c5d724d22d02088108a845fc7cf2dd62b6b05023 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Sun, 13 Sep 2026 22:50:41 +0300 Subject: [PATCH 08/29] ci: scan both legacyPackages and packages in glibc-floor check packages is missing site-extensions-versions-* and only exposes flat psql_X/bin (not individual .exts.*), so union both trees rather than choosing one and risking a coverage gap. Co-Authored-By: Claude Sonnet 5 --- nix/checks.nix | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nix/checks.nix b/nix/checks.nix index 91a31a4046..8ed069bb1f 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -965,7 +965,10 @@ ]; } (builtins.readFile ./tools/check-glibc-floor.nu); in - pkgs.runCommand "glibc-floor-check" { paths = lib.collect lib.isDerivation self'.legacyPackages; } + pkgs.runCommand "glibc-floor-check" + { + paths = lib.collect lib.isDerivation (self'.legacyPackages // self'.packages); + } '' ${lib.getExe checkScript} 2.31 $paths touch $out From 897948dd6192e8cd6df566e6679eb12e7aeea7aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Sun, 13 Sep 2026 22:55:28 +0300 Subject: [PATCH 09/29] ci: filter to real ELF files before running objdump Packages contain far more non-ELF files (docs, control files, SQL, scripts) than ELF ones. Checking the magic bytes natively in nushell avoids spawning an objdump process per file that could never match. Co-Authored-By: Claude Sonnet 5 --- nix/tools/check-glibc-floor.nu | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nix/tools/check-glibc-floor.nu b/nix/tools/check-glibc-floor.nu index 1218a65c52..bd20d601c1 100644 --- a/nix/tools/check-glibc-floor.nu +++ b/nix/tools/check-glibc-floor.nu @@ -5,6 +5,10 @@ def ver-key [ver: string] { $ver | split row "." | each { into int } } +def is-elf [file: string] { + (open --raw $file | bytes at 0..<4) == 0x[7f454c46] +} + def main [max_allowed: string, ...paths: string] { let max_key = (ver-key $max_allowed) @@ -13,6 +17,7 @@ def main [max_allowed: string, ...paths: string] { | each { |p| glob $"($p)/**/*" } | flatten | where { |f| ($f | path type) == file } + | where { |f| is-elf $f } | par-each { |file| let versions = (^objdump -T $file | complete | get stdout | parse -r 'GLIBC_(?[0-9.]+)' | get ver) if ($versions | is-empty) { From 7f22b52cb28034864e7a8e0d1bcf2f279a50db32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Sun, 13 Sep 2026 22:59:13 +0300 Subject: [PATCH 10/29] ci: fold file-type check into is-elf, add a return type is-elf now guards its own path-type check, collapsing the two where clauses into one. Also tried adding a `-> list` return type to ver-key, but nushell's static checker rejects `>` on that type even though it works fine at runtime on inferred lists, so left it untyped. Co-Authored-By: Claude Sonnet 5 --- nix/tools/check-glibc-floor.nu | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/nix/tools/check-glibc-floor.nu b/nix/tools/check-glibc-floor.nu index bd20d601c1..766225688b 100644 --- a/nix/tools/check-glibc-floor.nu +++ b/nix/tools/check-glibc-floor.nu @@ -5,8 +5,12 @@ def ver-key [ver: string] { $ver | split row "." | each { into int } } -def is-elf [file: string] { - (open --raw $file | bytes at 0..<4) == 0x[7f454c46] +def is-elf [path: string]: nothing -> bool { + if ($path | path type) != file { + false + } else { + (open --raw $path | bytes at 0..<4) == 0x[7f454c46] + } } def main [max_allowed: string, ...paths: string] { @@ -16,7 +20,6 @@ def main [max_allowed: string, ...paths: string] { $paths | each { |p| glob $"($p)/**/*" } | flatten - | where { |f| ($f | path type) == file } | where { |f| is-elf $f } | par-each { |file| let versions = (^objdump -T $file | complete | get stdout | parse -r 'GLIBC_(?[0-9.]+)' | get ver) From 9ba877941ebad821d58c8c87007467c008a8bae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Sun, 13 Sep 2026 23:00:12 +0300 Subject: [PATCH 11/29] ci: simplify is-elf to a single short-circuit expression Co-Authored-By: Claude Sonnet 5 --- nix/tools/check-glibc-floor.nu | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/nix/tools/check-glibc-floor.nu b/nix/tools/check-glibc-floor.nu index 766225688b..3d5ecba57e 100644 --- a/nix/tools/check-glibc-floor.nu +++ b/nix/tools/check-glibc-floor.nu @@ -6,11 +6,7 @@ def ver-key [ver: string] { } def is-elf [path: string]: nothing -> bool { - if ($path | path type) != file { - false - } else { - (open --raw $path | bytes at 0..<4) == 0x[7f454c46] - } + ($path | path type) == file and (open --raw $path | bytes at 0..<4) == 0x[7f454c46] } def main [max_allowed: string, ...paths: string] { From 2c45b748ae276b5327c5b42707329e383e37e6dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Sun, 13 Sep 2026 23:03:49 +0300 Subject: [PATCH 12/29] ci: make is-elf consume \$in for point-free where usage Co-Authored-By: Claude Sonnet 5 --- nix/tools/check-glibc-floor.nu | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nix/tools/check-glibc-floor.nu b/nix/tools/check-glibc-floor.nu index 3d5ecba57e..299d7d3bed 100644 --- a/nix/tools/check-glibc-floor.nu +++ b/nix/tools/check-glibc-floor.nu @@ -5,8 +5,8 @@ def ver-key [ver: string] { $ver | split row "." | each { into int } } -def is-elf [path: string]: nothing -> bool { - ($path | path type) == file and (open --raw $path | bytes at 0..<4) == 0x[7f454c46] +def is-elf []: string -> bool { + ($in | path type) == file and (open --raw $in | bytes at 0..<4) == 0x[7f454c46] } def main [max_allowed: string, ...paths: string] { @@ -16,7 +16,7 @@ def main [max_allowed: string, ...paths: string] { $paths | each { |p| glob $"($p)/**/*" } | flatten - | where { |f| is-elf $f } + | where { is-elf } | par-each { |file| let versions = (^objdump -T $file | complete | get stdout | parse -r 'GLIBC_(?[0-9.]+)' | get ver) if ($versions | is-empty) { From 2e2af84f408d1d2919ee236432d81f91312088df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Sun, 13 Sep 2026 23:06:31 +0300 Subject: [PATCH 13/29] ci: drop if/else + compact for a single filter [] | last returns null rather than erroring, so building the record unconditionally and filtering null versions out in one where clause replaces the separate if/else + compact step. Co-Authored-By: Claude Sonnet 5 --- nix/tools/check-glibc-floor.nu | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/nix/tools/check-glibc-floor.nu b/nix/tools/check-glibc-floor.nu index 299d7d3bed..4fde55b689 100644 --- a/nix/tools/check-glibc-floor.nu +++ b/nix/tools/check-glibc-floor.nu @@ -18,15 +18,16 @@ def main [max_allowed: string, ...paths: string] { | flatten | where { is-elf } | par-each { |file| - let versions = (^objdump -T $file | complete | get stdout | parse -r 'GLIBC_(?[0-9.]+)' | get ver) - if ($versions | is-empty) { - null - } else { - { file: $file, version: ($versions | sort-by { |v| ver-key $v } | last) } + { + file: $file, + version: ( + ^objdump -T $file | complete | get stdout + | parse -r 'GLIBC_(?[0-9.]+)' | get ver + | sort-by { |v| ver-key $v } | last + ), } } - | compact - | where { |h| (ver-key $h.version) > $max_key } + | where { |h| $h.version != null and (ver-key $h.version) > $max_key } ) if ($offenders | is-empty) { From 696473b5ade3039a0ac0d3f4ecc089a8c53745ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Mon, 14 Sep 2026 08:49:50 +0300 Subject: [PATCH 14/29] ci: rewrite glibc floor check as glibc-version-check, idiomatic nushell Renamed away from "floor" terminology (the function computes each file's max required glibc version, not a floor) across the nix check attribute, script filename, and its docstring/output text. Also rewrote the check logic itself in idiomatic nushell: is-elf and max-glibc-version as named helpers, ELF detection via magic-byte check (skips non-ELF files instead of shelling out to objdump for every file), version comparison via parsed int lists, and offending files printed as a table. Co-Authored-By: Claude Sonnet 5 --- nix/checks.nix | 8 +++---- nix/tools/check-glibc-floor.nu | 40 -------------------------------- nix/tools/check-glibc-version.nu | 38 ++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 44 deletions(-) delete mode 100644 nix/tools/check-glibc-floor.nu create mode 100755 nix/tools/check-glibc-version.nu diff --git a/nix/checks.nix b/nix/checks.nix index 8ed069bb1f..a6b66eb8a8 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -954,18 +954,18 @@ postgresql_17_src ; psql_orioledb-17_exts_orioledb_debug = self'.legacyPackages.psql_orioledb-17.exts.orioledb.debug; - glibc-floor = + glibc-version-check = let - checkScript = pkgs.writers.writeNuBin "check-glibc-floor" { + checkScript = pkgs.writers.writeNuBin "check-glibc-version" { makeWrapperArgs = [ "--prefix" "PATH" ":" "${lib.makeBinPath [ pkgs.binutils ]}" ]; - } (builtins.readFile ./tools/check-glibc-floor.nu); + } (builtins.readFile ./tools/check-glibc-version.nu); in - pkgs.runCommand "glibc-floor-check" + pkgs.runCommand "glibc-version-check" { paths = lib.collect lib.isDerivation (self'.legacyPackages // self'.packages); } diff --git a/nix/tools/check-glibc-floor.nu b/nix/tools/check-glibc-floor.nu deleted file mode 100644 index 4fde55b689..0000000000 --- a/nix/tools/check-glibc-floor.nu +++ /dev/null @@ -1,40 +0,0 @@ -# Fails if any file under the given paths requires a glibc symbol version -# above the allowed floor. - -def ver-key [ver: string] { - $ver | split row "." | each { into int } -} - -def is-elf []: string -> bool { - ($in | path type) == file and (open --raw $in | bytes at 0..<4) == 0x[7f454c46] -} - -def main [max_allowed: string, ...paths: string] { - let max_key = (ver-key $max_allowed) - - let offenders = ( - $paths - | each { |p| glob $"($p)/**/*" } - | flatten - | where { is-elf } - | par-each { |file| - { - file: $file, - version: ( - ^objdump -T $file | complete | get stdout - | parse -r 'GLIBC_(?[0-9.]+)' | get ver - | sort-by { |v| ver-key $v } | last - ), - } - } - | where { |h| $h.version != null and (ver-key $h.version) > $max_key } - ) - - if ($offenders | is-empty) { - print $"glibc floor OK \(<= ($max_allowed)\)" - exit 0 - } - - print $offenders - exit 1 -} diff --git a/nix/tools/check-glibc-version.nu b/nix/tools/check-glibc-version.nu new file mode 100755 index 0000000000..fb5617e884 --- /dev/null +++ b/nix/tools/check-glibc-version.nu @@ -0,0 +1,38 @@ +#!/usr/bin/env nu + +def parse-version [] { + split row "." | each { into int } +} + +def is-elf []: path -> bool { + (open --raw $in | bytes at 0..<4) == ("\u{7f}ELF" | into binary) +} + +def max-glibc-version [path: path] { + ^objdump -T $path | complete | get stdout + | parse -r 'GLIBC_(?[0-9.]+)' | get ver + | each { parse-version } + | sort + | last +} + +# Fails if any file under the given paths requires a newer glibc than max_version. +def main [max_version: string, ...paths: path] { + let offenders = ( + $paths + | each { glob $"($in)/**/*" } | flatten + | where { ($in | path type) == file } + | where { is-elf } + | par-each { |f| { file: $f, version: (max-glibc-version $f) } } + | compact version + | where version > ($max_version | parse-version) + ) + + if ($offenders | is-empty) { + print $"glibc version OK \(<= ($max_version)\)" + exit 0 + } + + print $offenders + exit 1 +} From 8f850716887f94b599dd73c9d01198168b1f24bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Fri, 18 Sep 2026 04:11:41 +0300 Subject: [PATCH 15/29] ci: drop makeWrapper for glibc-version-check's objdump dependency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit nativeBuildInputs on the outer runCommand already puts objdump on PATH for the wrapped script's process, so wrapping checkScript itself was unnecessary — it has no other callers. Co-Authored-By: Claude Sonnet 5 --- nix/checks.nix | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/nix/checks.nix b/nix/checks.nix index a6b66eb8a8..e9f94decce 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -571,7 +571,7 @@ # running server as pg_regress (--use-existing). These specs target # core/heap + contrib concurrency behaviour. Skipped on: # - CLI variants: portable build runs only a test subset. - # - orioledb ships its own isolation suite for its storage-engine + # - orioledb ships its own isolation suite for its storage-engine # concurrency semantics. #shellcheck disable=SC2193 if ${lib.boolToString isCliVariant}; then @@ -956,17 +956,13 @@ psql_orioledb-17_exts_orioledb_debug = self'.legacyPackages.psql_orioledb-17.exts.orioledb.debug; glibc-version-check = let - checkScript = pkgs.writers.writeNuBin "check-glibc-version" { - makeWrapperArgs = [ - "--prefix" - "PATH" - ":" - "${lib.makeBinPath [ pkgs.binutils ]}" - ]; - } (builtins.readFile ./tools/check-glibc-version.nu); + checkScript = pkgs.writers.writeNuBin "check-glibc-version" ( + builtins.readFile ./tools/check-glibc-version.nu + ); in pkgs.runCommand "glibc-version-check" { + nativeBuildInputs = [ pkgs.binutils ]; paths = lib.collect lib.isDerivation (self'.legacyPackages // self'.packages); } '' From faa658ae8b2958f5b5ba580373b7e696adcfe2ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Thu, 1 Oct 2026 05:22:25 +0300 Subject: [PATCH 16/29] fix: force table width in glibc-version-check for headless nix sandbox print failed with "Couldn't fit table into 0 columns!" since nushell's auto width detection returns 0 with no tty, which is always the case in a nix build sandbox. --- nix/tools/check-glibc-version.nu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/tools/check-glibc-version.nu b/nix/tools/check-glibc-version.nu index fb5617e884..e98ff106ad 100755 --- a/nix/tools/check-glibc-version.nu +++ b/nix/tools/check-glibc-version.nu @@ -33,6 +33,6 @@ def main [max_version: string, ...paths: path] { exit 0 } - print $offenders + print ($offenders | table --width 200) exit 1 } From e35562c50e678c5e1570d37908d1578d948d4a1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Mon, 5 Oct 2026 04:00:18 +0300 Subject: [PATCH 17/29] fix(nix): raise glibc floor target to 2.40 2.40 is the glibc in the oldest AMI build (17.6.1.072) sharing current collation data, the oldest version safely migratable without risking collation-driven data corruption. Supautils targets 2.31 separately. Refs: MPG-126 --- nix/checks.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nix/checks.nix b/nix/checks.nix index ff43db3fe7..5cc5a8e342 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -935,7 +935,8 @@ paths = lib.collect lib.isDerivation (self'.legacyPackages // self'.packages); } '' - ${lib.getExe checkScript} 2.31 $paths + # 2.40: oldest AMI build (17.6.1.072) sharing current collation data. + ${lib.getExe checkScript} 2.40 $paths touch $out ''; }; From de1d022c9ef138cecddf1a0656fd576e4f59e33f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Mon, 5 Oct 2026 04:03:21 +0300 Subject: [PATCH 18/29] fix(nix): split glibc targets, 2.40 postgres core only Extensions and site-env still target 2.31, supautils's compat floor. Refs: MPG-126 --- nix/checks.nix | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/nix/checks.nix b/nix/checks.nix index 5cc5a8e342..ba02174a4a 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -928,15 +928,24 @@ checkScript = pkgs.writers.writeNuBin "check-glibc-version" ( builtins.readFile ./tools/check-glibc-version.nu ); + hasBin = _: v: lib.isAttrs v && v ? bin; + corePaths = lib.collect lib.isDerivation ( + lib.mapAttrsToList (_: v: v.bin) (lib.filterAttrs hasBin self'.legacyPackages) + ); + otherPaths = lib.collect lib.isDerivation ( + (lib.mapAttrs (_: v: if lib.isAttrs v && v ? bin then v.exts else v) self'.legacyPackages) + // self'.packages + ); in pkgs.runCommand "glibc-version-check" { nativeBuildInputs = [ pkgs.binutils ]; - paths = lib.collect lib.isDerivation (self'.legacyPackages // self'.packages); + inherit corePaths otherPaths; } '' # 2.40: oldest AMI build (17.6.1.072) sharing current collation data. - ${lib.getExe checkScript} 2.40 $paths + ${lib.getExe checkScript} 2.40 $corePaths + ${lib.getExe checkScript} 2.31 $otherPaths touch $out ''; }; From 89a2ba95469edc920b2998598e429c2819a9788a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Mon, 5 Oct 2026 17:16:56 +0300 Subject: [PATCH 19/29] fix(nix): classify pg_regress/pg_isolation_regress/psql_17_cli_portable as core They just repackage an already core-targeted postgres build rather than compiling anything themselves, so they belong in the 2.40 bucket, not the 2.31 catch-all. Refs: MPG-126 --- nix/checks.nix | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/nix/checks.nix b/nix/checks.nix index ba02174a4a..6eaa8bfd18 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -929,12 +929,20 @@ builtins.readFile ./tools/check-glibc-version.nu ); hasBin = _: v: lib.isAttrs v && v ? bin; + # These just repackage an already core-targeted postgres build, not an independent compile. + coreNames = [ + "pg_regress" + "pg_isolation_regress" + "psql_17_cli_portable" + ]; + isCore = n: _: lib.hasPrefix "postgresql_" n || builtins.elem n coreNames; corePaths = lib.collect lib.isDerivation ( - lib.mapAttrsToList (_: v: v.bin) (lib.filterAttrs hasBin self'.legacyPackages) + (lib.mapAttrsToList (_: v: v.bin) (lib.filterAttrs hasBin self'.legacyPackages)) + ++ (lib.attrValues (lib.filterAttrs isCore self'.packages)) ); otherPaths = lib.collect lib.isDerivation ( (lib.mapAttrs (_: v: if lib.isAttrs v && v ? bin then v.exts else v) self'.legacyPackages) - // self'.packages + // (lib.filterAttrs (n: v: !(isCore n v)) self'.packages) ); in pkgs.runCommand "glibc-version-check" @@ -944,6 +952,7 @@ } '' # 2.40: oldest AMI build (17.6.1.072) sharing current collation data. + # 2.31: supautils's compat floor, catch-all for everything not core. ${lib.getExe checkScript} 2.40 $corePaths ${lib.getExe checkScript} 2.31 $otherPaths touch $out From e76ad99d4e6947b622cb358781d26d12c2b85312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Tue, 6 Oct 2026 00:39:57 +0300 Subject: [PATCH 20/29] fix(nix): collapse glibc floor to core everywhere except supautils/gatekeeper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extensions only ever activate within the same AMI's own nix closure (via postgres_prestart.sh's switch__version), so they already share core's glibc floor — there's no live path that pushes a newer build onto an older host. The one exception is anything loaded outside that closure: supautils via session_preload_libraries into an already-running legacy postgres, and gatekeeper via the host's system PAM stack. Both keep the deeper 2.31 (Ubuntu 20.04) floor; everything else now checks against core's 2.40. Refs: MPG-126 --- nix/checks.nix | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/nix/checks.nix b/nix/checks.nix index 6eaa8bfd18..243149ebc8 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -935,26 +935,40 @@ "pg_isolation_regress" "psql_17_cli_portable" ]; + # Loaded outside this AMI's own postgres nix closure, so they can reach hosts + # built from an older AMI: supautils via session_preload_libraries into an + # already-running legacy postgres, gatekeeper via the host's system PAM stack. + legacyNames = [ + "supautils" + "gatekeeper" + ]; isCore = n: _: lib.hasPrefix "postgresql_" n || builtins.elem n coreNames; + isLegacy = n: _: builtins.elem n legacyNames; corePaths = lib.collect lib.isDerivation ( (lib.mapAttrsToList (_: v: v.bin) (lib.filterAttrs hasBin self'.legacyPackages)) - ++ (lib.attrValues (lib.filterAttrs isCore self'.packages)) + ++ (lib.attrValues (lib.filterAttrs (n: v: isCore n v && !(isLegacy n v)) self'.packages)) + ); + legacyPaths = lib.collect lib.isDerivation ( + lib.attrValues (lib.filterAttrs isLegacy self'.packages) ); otherPaths = lib.collect lib.isDerivation ( (lib.mapAttrs (_: v: if lib.isAttrs v && v ? bin then v.exts else v) self'.legacyPackages) - // (lib.filterAttrs (n: v: !(isCore n v)) self'.packages) + // (lib.filterAttrs (n: v: !(isCore n v) && !(isLegacy n v)) self'.packages) ); in pkgs.runCommand "glibc-version-check" { nativeBuildInputs = [ pkgs.binutils ]; - inherit corePaths otherPaths; + inherit corePaths otherPaths legacyPaths; } '' - # 2.40: oldest AMI build (17.6.1.072) sharing current collation data. - # 2.31: supautils's compat floor, catch-all for everything not core. - ${lib.getExe checkScript} 2.40 $corePaths - ${lib.getExe checkScript} 2.31 $otherPaths + # 2.40: oldest AMI build (17.6.1.072) sharing current collation data. Everything + # else only ever activates from within that same AMI's own closure, so it shares + # this floor too — there's no live path that pushes a newer build onto an older host. + # 2.31: Ubuntu 20.04's glibc, for the two things loaded outside this AMI's own + # closure and so reachable from older hosts: supautils and gatekeeper (see above). + ${lib.getExe checkScript} 2.40 $corePaths $otherPaths + ${lib.getExe checkScript} 2.31 $legacyPaths touch $out ''; }; From 95175bd562fb6425533450bc104e496e6f6521d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Tue, 6 Oct 2026 01:14:57 +0300 Subject: [PATCH 21/29] fix(nix): drop core glibc floor, add declared-collation-drift check Core and extensions always share one glibc from the same build closure, so glibc-version-check now only covers supautils and gatekeeper (loaded outside that closure, reachable from older hosts). collation-version-check pins glibc's version against collation-version-baseline.txt and fails CI on drift, requiring a deliberate, reviewed baseline bump instead of a silent collation change riding in on a nixpkgs input bump. Refs: MPG-126 --- nix/checks.nix | 52 ++++++++++++++---------------- nix/collation-version-baseline.txt | 1 + 2 files changed, 25 insertions(+), 28 deletions(-) create mode 100644 nix/collation-version-baseline.txt diff --git a/nix/checks.nix b/nix/checks.nix index 243149ebc8..af8ee418ac 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -928,49 +928,45 @@ checkScript = pkgs.writers.writeNuBin "check-glibc-version" ( builtins.readFile ./tools/check-glibc-version.nu ); - hasBin = _: v: lib.isAttrs v && v ? bin; - # These just repackage an already core-targeted postgres build, not an independent compile. - coreNames = [ - "pg_regress" - "pg_isolation_regress" - "psql_17_cli_portable" - ]; - # Loaded outside this AMI's own postgres nix closure, so they can reach hosts - # built from an older AMI: supautils via session_preload_libraries into an - # already-running legacy postgres, gatekeeper via the host's system PAM stack. + # Postgres core and every extension are built in the same nix closure as whatever + # runs them, so they always share one glibc — no floor needed there. The exception + # is anything loaded outside that closure, and so reachable from an older host: + # supautils via session_preload_libraries into an already-running legacy postgres, + # and gatekeeper via the host's system PAM stack. legacyNames = [ "supautils" "gatekeeper" ]; - isCore = n: _: lib.hasPrefix "postgresql_" n || builtins.elem n coreNames; - isLegacy = n: _: builtins.elem n legacyNames; - corePaths = lib.collect lib.isDerivation ( - (lib.mapAttrsToList (_: v: v.bin) (lib.filterAttrs hasBin self'.legacyPackages)) - ++ (lib.attrValues (lib.filterAttrs (n: v: isCore n v && !(isLegacy n v)) self'.packages)) - ); legacyPaths = lib.collect lib.isDerivation ( - lib.attrValues (lib.filterAttrs isLegacy self'.packages) - ); - otherPaths = lib.collect lib.isDerivation ( - (lib.mapAttrs (_: v: if lib.isAttrs v && v ? bin then v.exts else v) self'.legacyPackages) - // (lib.filterAttrs (n: v: !(isCore n v) && !(isLegacy n v)) self'.packages) + lib.attrValues (lib.filterAttrs (n: _: builtins.elem n legacyNames) self'.packages) ); in pkgs.runCommand "glibc-version-check" { nativeBuildInputs = [ pkgs.binutils ]; - inherit corePaths otherPaths legacyPaths; + inherit legacyPaths; } '' - # 2.40: oldest AMI build (17.6.1.072) sharing current collation data. Everything - # else only ever activates from within that same AMI's own closure, so it shares - # this floor too — there's no live path that pushes a newer build onto an older host. - # 2.31: Ubuntu 20.04's glibc, for the two things loaded outside this AMI's own - # closure and so reachable from older hosts: supautils and gatekeeper (see above). - ${lib.getExe checkScript} 2.40 $corePaths $otherPaths + # 2.31: Ubuntu 20.04's glibc. ${lib.getExe checkScript} 2.31 $legacyPaths touch $out ''; + collation-version-check = + let + baseline = lib.strings.trim (builtins.readFile ./collation-version-baseline.txt); + actual = pkgs.glibc.name; + in + pkgs.runCommand "collation-version-check" { } ( + if actual == baseline then + "touch $out" + else + '' + echo "glibc collation data changed: baseline is ${baseline}, build has ${actual}." + echo "This can silently reorder existing indexes without a version-string change." + echo "If this bump has been reviewed for collation safety, update collation-version-baseline.txt to match." + exit 1 + '' + ); }; }; } diff --git a/nix/collation-version-baseline.txt b/nix/collation-version-baseline.txt new file mode 100644 index 0000000000..1d24ce965b --- /dev/null +++ b/nix/collation-version-baseline.txt @@ -0,0 +1 @@ +glibc-2.40-66 From 16c799eee5e24d9007c759b156fcf1ee0fa588f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Tue, 6 Oct 2026 01:54:47 +0300 Subject: [PATCH 22/29] fix(nix): shorten glibc-version-check comment Refs: MPG-126 --- nix/checks.nix | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/nix/checks.nix b/nix/checks.nix index af8ee418ac..d4db1b8a5c 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -928,11 +928,7 @@ checkScript = pkgs.writers.writeNuBin "check-glibc-version" ( builtins.readFile ./tools/check-glibc-version.nu ); - # Postgres core and every extension are built in the same nix closure as whatever - # runs them, so they always share one glibc — no floor needed there. The exception - # is anything loaded outside that closure, and so reachable from an older host: - # supautils via session_preload_libraries into an already-running legacy postgres, - # and gatekeeper via the host's system PAM stack. + # supautils/gatekeeper load outside this build's own nix closure, so they alone can reach older hosts. legacyNames = [ "supautils" "gatekeeper" From 3ec76bbed06a264e00e3625e86dd945f9c938093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Tue, 6 Oct 2026 02:20:57 +0300 Subject: [PATCH 23/29] fix(nix): format glibc version as dotted string in check output version was still the parsed int list used for comparison, printing as "[list 2 items]" instead of e.g. "2.34". Refs: MPG-126 --- nix/tools/check-glibc-version.nu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/tools/check-glibc-version.nu b/nix/tools/check-glibc-version.nu index e98ff106ad..d785dc2bd3 100755 --- a/nix/tools/check-glibc-version.nu +++ b/nix/tools/check-glibc-version.nu @@ -33,6 +33,6 @@ def main [max_version: string, ...paths: path] { exit 0 } - print ($offenders | table --width 200) + print ($offenders | update version { str join "." } | table --width 200) exit 1 } From e1bc6a1350f3e2ca9a4d825bb1852d3cfcdd4357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Tue, 6 Oct 2026 02:30:37 +0300 Subject: [PATCH 24/29] fix(nix): clarify glibc-version-check comment Refs: MPG-126 --- nix/checks.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nix/checks.nix b/nix/checks.nix index 8b2704161b..e9f92bcf7f 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -929,7 +929,7 @@ checkScript = pkgs.writers.writeNuBin "check-glibc-version" ( builtins.readFile ./tools/check-glibc-version.nu ); - # supautils/gatekeeper load outside this build's own nix closure, so they alone can reach older hosts. + # these packages are dlopened into processes with old glibcs so need a lower version floor legacyNames = [ "supautils" "gatekeeper" From 3d96f647103f399d073ca6ca4d3d5d8cdb72ef51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Tue, 6 Oct 2026 04:37:09 +0300 Subject: [PATCH 25/29] fix(nix): inline collation-version-check baseline Refs: MPG-126 --- nix/checks.nix | 4 ++-- nix/collation-version-baseline.txt | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) delete mode 100644 nix/collation-version-baseline.txt diff --git a/nix/checks.nix b/nix/checks.nix index e9f92bcf7f..2476d1051d 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -950,7 +950,7 @@ ''; collation-version-check = let - baseline = lib.strings.trim (builtins.readFile ./collation-version-baseline.txt); + baseline = "glibc-2.40-66"; actual = pkgs.glibc.name; in pkgs.runCommand "collation-version-check" { } ( @@ -960,7 +960,7 @@ '' echo "glibc collation data changed: baseline is ${baseline}, build has ${actual}." echo "This can silently reorder existing indexes without a version-string change." - echo "If this bump has been reviewed for collation safety, update collation-version-baseline.txt to match." + echo "If this bump has been reviewed for collation safety, update the baseline in nix/checks.nix to match." exit 1 '' ); diff --git a/nix/collation-version-baseline.txt b/nix/collation-version-baseline.txt deleted file mode 100644 index 1d24ce965b..0000000000 --- a/nix/collation-version-baseline.txt +++ /dev/null @@ -1 +0,0 @@ -glibc-2.40-66 From 47dfc5bfdf83b2b8b4a18139b4d1cfb3f0fa758c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Tue, 6 Oct 2026 04:40:47 +0300 Subject: [PATCH 26/29] fix(nix): make collation-version-check an eval-time assertion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pure string comparison, no build needed — fails nix eval/flake check immediately with the message, instead of a derivation you have to build and open logs for. Refs: MPG-126 --- nix/checks.nix | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/nix/checks.nix b/nix/checks.nix index 2476d1051d..df7655577f 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -953,17 +953,12 @@ baseline = "glibc-2.40-66"; actual = pkgs.glibc.name; in - pkgs.runCommand "collation-version-check" { } ( - if actual == baseline then - "touch $out" - else - '' - echo "glibc collation data changed: baseline is ${baseline}, build has ${actual}." - echo "This can silently reorder existing indexes without a version-string change." - echo "If this bump has been reviewed for collation safety, update the baseline in nix/checks.nix to match." - exit 1 - '' - ); + assert lib.assertMsg (actual == baseline) '' + glibc collation data changed: baseline is ${baseline}, build has ${actual}. + This can change collation versions and require a reindex. + If this bump has been reviewed for collation safety, update the baseline in nix/checks.nix to match. + ''; + pkgs.runCommand "collation-version-check" { } "touch $out"; }; }; } From 18fa03fe06e0b61254fabb8719a88f93e8ffe708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Tue, 6 Oct 2026 15:29:20 +0300 Subject: [PATCH 27/29] fix(nix): lib.collect doesn't traverse plain lists, legacyPaths was empty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit lib.collect only recurses into attrsets (see lib/attrsets.nix), so wrapping an already-flat list in it silently returns []. supautils wasn't even in self'.packages (it only exists per-PG-version under legacyPackages.psql_X.exts.supautils), so the check was vacuously passing for both exceptions. Now genuinely checks gatekeeper and supautils (all 3 PG majors) against 2.31 — both currently fail (tracked in MPG-1326). Refs: MPG-126 --- nix/checks.nix | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/nix/checks.nix b/nix/checks.nix index df7655577f..59f0ab676c 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -930,13 +930,12 @@ builtins.readFile ./tools/check-glibc-version.nu ); # these packages are dlopened into processes with old glibcs so need a lower version floor - legacyNames = [ - "supautils" - "gatekeeper" + legacyPaths = [ + self'.packages.gatekeeper + self'.legacyPackages.psql_15.exts.supautils + self'.legacyPackages.psql_17.exts.supautils + self'.legacyPackages."psql_orioledb-17".exts.supautils ]; - legacyPaths = lib.collect lib.isDerivation ( - lib.attrValues (lib.filterAttrs (n: _: builtins.elem n legacyNames) self'.packages) - ); in pkgs.runCommand "glibc-version-check" { From 8e15e5a6e0dcb8451e0fc4f645ae503709bc896a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Tue, 6 Oct 2026 15:45:18 +0300 Subject: [PATCH 28/29] fix(nix): drop gatekeeper from the legacy glibc floor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit gatekeeper's first commit (880813867, "custom pam" #1772) already pinned postgres_release 17.6.1.072 — the same AMI generation postgres core's own 2.40 floor is set to. It's installed at AMI-build time (ansible/tasks/stage2-setup-postgres.yml, a Packer/ebssurrogate provisioning task), never retrofitted onto an older running host, so it's never actually reached anything below 2.40. Only supautils (dlopened into already-running legacy pre-nix postgres) still needs the lower floor. Refs: MPG-126 --- nix/checks.nix | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nix/checks.nix b/nix/checks.nix index 59f0ab676c..e709afaa9d 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -929,9 +929,8 @@ checkScript = pkgs.writers.writeNuBin "check-glibc-version" ( builtins.readFile ./tools/check-glibc-version.nu ); - # these packages are dlopened into processes with old glibcs so need a lower version floor + # supautils is dlopened into already-running legacy pre-nix postgres, so it needs a lower floor than an AMI build. legacyPaths = [ - self'.packages.gatekeeper self'.legacyPackages.psql_15.exts.supautils self'.legacyPackages.psql_17.exts.supautils self'.legacyPackages."psql_orioledb-17".exts.supautils From 97e789cf5b1e73bd1795d191802c14fbb370aa33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A1rton=20Boros?= Date: Tue, 6 Oct 2026 16:02:00 +0300 Subject: [PATCH 29/29] fix(nix): pin gatekeeper to the 2.40 floor explicitly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's dlopened by the base AMI's own system PAM stack, not postgres's nix closure, so it isn't covered by "core and extensions share one glibc" — a future nixpkgs glibc bump could silently outrun the base image's own system glibc within the same AMI build. Pin it explicitly rather than relying on that happening to hold. Refs: MPG-126 --- nix/checks.nix | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/nix/checks.nix b/nix/checks.nix index e709afaa9d..f94a6e653a 100644 --- a/nix/checks.nix +++ b/nix/checks.nix @@ -935,15 +935,21 @@ self'.legacyPackages.psql_17.exts.supautils self'.legacyPackages."psql_orioledb-17".exts.supautils ]; + # gatekeeper is dlopened by the base AMI's own system PAM stack, not postgres's nix + # closure, so it isn't covered by "core and extensions share one glibc" — pin it + # explicitly so a future nixpkgs glibc bump can't silently outrun the base image. + pamPaths = [ self'.packages.gatekeeper ]; in pkgs.runCommand "glibc-version-check" { nativeBuildInputs = [ pkgs.binutils ]; - inherit legacyPaths; + inherit legacyPaths pamPaths; } '' # 2.31: Ubuntu 20.04's glibc. ${lib.getExe checkScript} 2.31 $legacyPaths + # 2.40: current core floor. + ${lib.getExe checkScript} 2.40 $pamPaths touch $out ''; collation-version-check =