Skip to content

feat: add portable CLI binaries for multi-platform Supabase CLI distribution#2033

Open
LGUG2Z wants to merge 1 commit intodevelopfrom
feature/sb-cli-binaries
Open

feat: add portable CLI binaries for multi-platform Supabase CLI distribution#2033
LGUG2Z wants to merge 1 commit intodevelopfrom
feature/sb-cli-binaries

Conversation

@LGUG2Z
Copy link

@LGUG2Z LGUG2Z commented Feb 2, 2026

What kind of change does this PR introduce?

Adds a new, portable psql_17_cli_portable target.

What is the current behavior?

All current builds are not portable (i.e. they have hard dependencies on the /nix/store), and they all include the full list of extensions.

What is the new behavior?

The psql_17_cli_portable target can run without a /nix/store present, and only includes the supautils extension.

Additional context

This commit implements portable, self-contained PostgreSQL binaries for the
Supabase CLI across macOS (ARM), Linux (x64), and Linux (ARM64), along with
automated CI/CD workflows for building and releasing these artifacts.

The Supabase CLI needs to ship PostgreSQL binaries that work on user machines
without requiring Nix or other system dependencies. This means extracting the
actual binaries from Nix's wrapper scripts, bundling all necessary shared
libraries, and patching them to use relative paths instead of hardcoded Nix
store paths.

A `variant` parameter was added to the postgres build pipeline to distinguish
between "full" (all extensions) and "cli" (minimal extensions for Supabase CLI).
The `cliExtensions` list contains 6 extensions required for running Supabase
migrations: supautils, pg_graphql, pgsodium, supabase_vault, pg_net, and pg_cron.
Built-in extensions (uuid-ossp, pgcrypto, pg_stat_statements) are included
automatically with PostgreSQL. `makeOurPostgresPkgs`/`makePostgresBin` were
modified to accept this parameter. A new `psql_17_cli` package is created using
`variant = "cli"`, while the full extension set is preserved for base packages
(`psql_15`, `psql_17`, `psql_orioledb-17`).

The portable CLI variant (`psql_17_cli_portable`) includes 6 extensions for
migration support while maintaining a significantly smaller size than the full
build. The implementation in `nix/packages/postgres-portable.nix` extracts
binaries from `psql_17_cli` using a `resolve_binary()` function that follows
wrapper layers to find the actual ELF/Mach-O binaries behind Nix's environment
setup scripts.

All Nix-provided libraries (ICU, readline, zlib, etc.) are bundled while
excluding system libraries (`libc`, `libpthread`, `libm`, `glibc`, `libdl`) that
must come from the host. This distinction is critical: Linux bundles must
exclude glibc due to kernel ABI dependencies, while macOS can include more libs
due to its different linking model. Dependency resolution runs multiple passes
to catch transitive deps (e.g., ICU → charset → etc.).

Platform-specific patching is applied: Linux binaries use the system interpreter
(`/lib64/ld-linux-*.so.2`) and `$ORIGIN`-based RPATHs, while macOS binaries use
`@rpath` with `@executable_path`. Wrapper scripts set `LD_LIBRARY_PATH` (Linux)
or `DYLD_LIBRARY_PATH` (macOS) to find bundled libraries. The bundle includes
PostgreSQL config templates (`postgresql.conf`, `pg_hba.conf`, `pg_ident.conf`)
tailored for CLI usage with minimal local dev settings, plus the complete
Supabase migration script (`migrate.sh`) with all init-scripts and migration
SQL files (55 files, 236KB).

A Docker-style initialization script (`supabase-postgres-init.sh`) provides
one-command database setup via environment variables (`POSTGRES_USER`,
`POSTGRES_PASSWORD`, `POSTGRES_DB`, `PGDATA`).

To achieve true portability, a `portable` parameter was added to the PostgreSQL
build in `nix/postgresql/generic.nix`. When `portable = true`, three critical
hardcoded paths are excluded from the build:

1. `--with-system-tzdata` is removed from configure flags, allowing PostgreSQL
to use bundled timezone data from the `share/` directory instead of a hardcoded
`/nix/store/.../tzdata` path;

2. the `locale-binary-path.patch` is excluded, so PostgreSQL calls `locale -a`
from system PATH rather than using an absolute path to glibc's locale command;

3. the `postFixup` initdb wrapper is disabled to avoid hardcoding glibc paths.
The `portable` parameter defaults to `false` in `nix/postgresql/default.nix` but
is overridden to `true` for the CLI variant in `nix/packages/postgres.nix` using
`.override { portable = true; }`.

This ensures standard PostgreSQL builds remain unchanged while the CLI variant
produces truly portable binaries without any `/nix/store` references.

A GitHub Actions workflow builds portable binaries across all three platforms
using a matrix strategy. Each build runs automated portability checks that
verify no `/nix/store` references remain, validate RPATH configuration, confirm
transitive dependencies are bundled, ensure system libraries are NOT bundled,
and check wrapper scripts contain proper library path setup. Post-build testing
validates binaries work without Nix (`postgres --version`, `psql --version`). On
tagged releases (`v*-cli`), the workflow creates GitHub releases with tarball
artifacts and checksums.

The test infrastructure needed significant changes to support variants with
different extension sets. An `isCliVariant` parameter was added to
`makeCheckHarness`, and the hardcoded `shared_preload_libraries` list in
`postgresql.conf.in` was replaced with a `@PRELOAD_LIBRARIES@` placeholder. A
`generatePreloadLibs` script now parses `receipt.json` at test time and
dynamically builds the preload list based on available extensions, removing the
previous timescaledb removal hack for OrioleDB.

Summary by CodeRabbit

  • New Features
    • Portable, multi-architecture PostgreSQL CLI bundles and a CLI runtime variant; lightweight local Postgres config/auth templates, CLI init SQL, and a pgsodium key-generation helper.
  • Tests
    • CLI-specific init/test flow, dynamic preload library handling, and an extensive portability test harness validating binaries, libraries, tzdata, and extension/migration behavior.
  • Infrastructure
    • CI workflow to build per-architecture bundles, run portability checks, package tarballs with checksums, and publish GitHub Releases.

@LGUG2Z LGUG2Z force-pushed the feature/sb-cli-binaries branch 30 times, most recently from cebd363 to 57718b4 Compare February 3, 2026 20:13
@LGUG2Z LGUG2Z force-pushed the feature/sb-cli-binaries branch from 9bc3af3 to 345b8ff Compare February 4, 2026 20:24
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@nix/packages/postgres.nix`:
- Around line 194-201: The current assignment to postFixup is replacing upstream
fixup steps; instead, preserve upstream behavior and only remove the initdb
wrapper on Linux by appending a conditional snippet to the existing postFixup
(e.g., set postFixup = (old: old + lib.optionalString
pkgs.stdenv.hostPlatform.isLinux '' ... '') so you only inject the removal of
the initdb wrapper and do not discard other upstream fixups), reference
postFixup, lib.optionalString, pkgs.stdenv.hostPlatform.isLinux and the initdb
wrapper; if you truly intend to disable postFixup entirely for the CLI variant,
add an explicit comment documenting that decision.

Comment on lines 194 to 273
# CRITICAL FIX FOR LINUX: Override postFixup to remove initdb wrapper
# The upstream nixpkgs adds a wrapper on Linux that embeds glibc.bin path
# We remove this wrapper because 'locale' should be found from system PATH
postFixup = lib.optionalString pkgs.stdenv.hostPlatform.isLinux ''
# Do NOT wrap initdb with glibc.bin path
# The system's locale command will be used from PATH
# This is acceptable for portable CLI distribution
'';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's see the full context around the lines in question
head -210 nix/packages/postgres.nix | tail -40

Repository: supabase/postgres

Length of output: 2545


🏁 Script executed:

# Check the structure of the entire postgres.nix file to understand the override pattern
wc -l nix/packages/postgres.nix

Repository: supabase/postgres

Length of output: 90


🏁 Script executed:

# Look for the override block containing this code
grep -n "override\|postFixup" nix/packages/postgres.nix | head -20

Repository: supabase/postgres

Length of output: 468


🏁 Script executed:

# Check the basePostgresql definition to see its postFixup
grep -n "basePostgresql" nix/packages/postgres.nix | head -10

Repository: supabase/postgres

Length of output: 204


🏁 Script executed:

# Look for any upstream postFixup references or check what basePostgresql pulls from
grep -n "inherit\|postgresql\|pkgs\." nix/packages/postgres.nix | head -30

Repository: supabase/postgres

Length of output: 1701


🏁 Script executed:

# Search for where basePostgresql is defined
grep -B 5 "basePostgresql\s*=" nix/packages/postgres.nix

Repository: supabase/postgres

Length of output: 195


🏁 Script executed:

# Let's understand what upstream nixpkgs postgres does with postFixup
# Search for what basePostgresql (pkgs.postgresql_*) might contain
# Check if there are any references to postFixup behavior in comments or documentation

grep -n "postFixup\|preConfigure\|previousAttrs" nix/packages/postgres.nix

Repository: supabase/postgres

Length of output: 484


🏁 Script executed:

# Let's check the git history or similar patterns to see if postFixup was intentionally appended before
grep -B 3 -A 3 "previousAttrs\." nix/packages/postgres.nix

Repository: supabase/postgres

Length of output: 1484


🏁 Script executed:

# Let's check if there's any actual content that should be in postFixup
# The block appears empty - let's verify this is intentional
sed -n '194,203p' nix/packages/postgres.nix

Repository: supabase/postgres

Length of output: 745


🏁 Script executed:

# Let's see if there are any other patterns or comments explaining the postFixup intent
sed -n '190,210p' nix/packages/postgres.nix

Repository: supabase/postgres

Length of output: 1443


Clarify intent: does the CLI variant need postFixup disabled entirely, or just the initdb wrapper removed?

The current assignment postFixup = lib.optionalString pkgs.stdenv.hostPlatform.isLinux ''...'' replaces the entire upstream postFixup with an empty string on all platforms, discarding any upstream fixup steps that may be critical for portability. This differs from how preConfigure is handled (appended, not replaced).

If the goal is simply to avoid the initdb wrapper while preserving other fixups, use the safer pattern:

Recommended fix
-                  postFixup = lib.optionalString pkgs.stdenv.hostPlatform.isLinux ''
+                  postFixup = (previousAttrs.postFixup or "") + lib.optionalString pkgs.stdenv.hostPlatform.isLinux ''
                     # Do NOT wrap initdb with glibc.bin path
                     # The system's locale command will be used from PATH
                     # This is acceptable for portable CLI distribution
                   '';

If disabling postFixup entirely for the CLI variant is intentional and safe, document this explicitly.

🤖 Prompt for AI Agents
In `@nix/packages/postgres.nix` around lines 194 - 201, The current assignment to
postFixup is replacing upstream fixup steps; instead, preserve upstream behavior
and only remove the initdb wrapper on Linux by appending a conditional snippet
to the existing postFixup (e.g., set postFixup = (old: old + lib.optionalString
pkgs.stdenv.hostPlatform.isLinux '' ... '') so you only inject the removal of
the initdb wrapper and do not discard other upstream fixups), reference
postFixup, lib.optionalString, pkgs.stdenv.hostPlatform.isLinux and the initdb
wrapper; if you truly intend to disable postFixup entirely for the CLI variant,
add an explicit comment documenting that decision.

@LGUG2Z LGUG2Z force-pushed the feature/sb-cli-binaries branch 5 times, most recently from 3b7417d to d9cb921 Compare February 4, 2026 21:34
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@nix/checks.nix`:
- Around line 215-256: The cliSkipTests array in nix/checks.nix includes "plv8"
but there is no corresponding test file; remove the "plv8" entry from the
cliSkipTests list (or alternatively add a matching test file under
nix/tests/sql/ if the test is required) so filteredTestList and the test
basename mapping no longer reference a non-existent test; update the
cliSkipTests array (symbol: cliSkipTests) and re-run the filter logic (symbol:
filteredTestList) to ensure no missing basenames remain.

In `@nix/packages/cli-config/pg_hba.conf.template`:
- Around line 5-9: The pg_hba.conf.template currently requires scram-sha-256 for
loopback TCP ("host all all 127.0.0.1/32 scram-sha-256" and "host all all
::1/128 scram-sha-256"), which will reject initdb-created passwordless superuser
connections; update the CLI-friendly template to use "trust" for those loopback
host rules or alternatively make the init wrapper set a password before enabling
SCRAM—modify the entries in pg_hba.conf.template for 127.0.0.1/32 and ::1/128 to
"trust" (or implement password-setting in the init script that runs before
switching these entries to scram-sha-256).

Comment on lines +215 to +256
# Tests to skip for CLI variants (require extensions not in CLI)
cliSkipTests = [
# Extension-specific tests
"evtrigs"
"http"
"hypopg"
"index_advisor"
"pg_hashids"
"pg_jsonschema"
"pg_partman"
"pg_repack"
"pg_tle"
"pgtap"
"pgmq"
"pgroonga"
"pgrouting"
"plpgsql-check"
"plv8"
"postgis"
"postgres_fdw"
# Tests that depend on extensions not in CLI
"security" # depends on various extensions
"extensions_schema" # tests extension loading
"roles" # includes roles/schemas from extensions not in CLI (pgtle, pgmq, repack, topology)
# Version-specific extension tests
"z_17_ext_interface"
"z_17_pg_stat_monitor"
"z_17_pgvector"
"z_17_rum"
"z_17_roles" # version-specific roles test, includes pgtle_admin
];

# Convert filtered tests to a sorted list of basenames (without extension)
testList = pkgs.lib.mapAttrsToList (
name: _: builtins.substring 0 (pkgs.lib.stringLength name - 4) name
) filteredSqlTests;
sortedTestList = builtins.sort (a: b: a < b) testList;

# Filter out CLI-incompatible tests if this is a CLI variant
filteredTestList =
if isCliVariant then
builtins.filter (test: !(builtins.elem test cliSkipTests)) testList
else
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Compare cliSkipTests entries against nix/tests/sql/*.sql basenames.
python - <<'PY'
import re, pathlib
text = pathlib.Path("nix/checks.nix").read_text()
m = re.search(r"cliSkipTests\s*=\s*\[(.*?)\];", text, re.S)
skip = re.findall(r'"([^"]+)"', m.group(1)) if m else []
tests = [p.stem for p in pathlib.Path("nix/tests/sql").glob("*.sql")]
missing = [s for s in skip if s not in tests]
extra = [t for t in tests if t in skip]
print("Skip entries without matching test file:", missing)
print("Skip entries that match:", extra)
PY

Repository: supabase/postgres

Length of output: 467


Remove plv8 from cliSkipTests—it has no corresponding test file.
Verification shows plv8 is listed in the skip list but lacks a matching test file in nix/tests/sql/. Most other entries (including plpgsql-check) correctly match their test basenames. Remove plv8 or create the missing test file.

🤖 Prompt for AI Agents
In `@nix/checks.nix` around lines 215 - 256, The cliSkipTests array in
nix/checks.nix includes "plv8" but there is no corresponding test file; remove
the "plv8" entry from the cliSkipTests list (or alternatively add a matching
test file under nix/tests/sql/ if the test is required) so filteredTestList and
the test basename mapping no longer reference a non-existent test; update the
cliSkipTests array (symbol: cliSkipTests) and re-run the filter logic (symbol:
filteredTestList) to ensure no missing basenames remain.

@LGUG2Z LGUG2Z force-pushed the feature/sb-cli-binaries branch 3 times, most recently from 00bdb0f to d3de8d8 Compare February 4, 2026 22:07
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@nix/packages/postgres-portable.nix`:
- Around line 50-61: Uncomment and set the getkey_script paths in the
postgresql.conf.template so the portable bundle points to the installed script
path (installed by configBundle) — update the pgsodium.getkey_script and
vault.getkey_script entries in postgresql.conf.template to reference the bundle
location (share/supabase-cli/config/pgsodium_getkey.sh) or implement a startup
hook in the CLI to rewrite those config keys at launch; ensure references align
with the install path created by configBundle (the directory created in
installPhase) and document the behavior so pgsodium and vault can locate the
getkey script when the portable bundle is used.

Comment on lines 50 to 65
configBundle = stdenv.mkDerivation {
name = "cli-config-bundle";
src = configDir;
dontPatchShebangs = true;
installPhase = ''
mkdir -p $out/share/supabase-cli/config
cp postgresql.conf.template $out/share/supabase-cli/config/
cp pg_hba.conf.template $out/share/supabase-cli/config/
cp pg_ident.conf.template $out/share/supabase-cli/config/
cp pgsodium_getkey.sh $out/share/supabase-cli/config/
chmod +x $out/share/supabase-cli/config/pgsodium_getkey.sh
'';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd -t f "\.(template|sh)$" nix/packages/

Repository: supabase/postgres

Length of output: 244


🏁 Script executed:

echo "=== pg_hba.conf.template ===" && cat nix/packages/cli-config/pg_hba.conf.template && \
echo -e "\n=== pgsodium_getkey.sh ===" && cat nix/packages/cli-config/pgsodium_getkey.sh && \
echo -e "\n=== postgresql.conf.template ===" && head -50 nix/packages/cli-config/postgresql.conf.template

Repository: supabase/postgres

Length of output: 2208


🏁 Script executed:

cat -n nix/packages/postgres-portable.nix | head -80

Repository: supabase/postgres

Length of output: 3062


🏁 Script executed:

rg -i "getkey_script|pgsodium.*path|postgresql\.conf" --type sh --type rs --type ts --type go -A 3 -B 1 | head -100

Repository: supabase/postgres

Length of output: 87


🏁 Script executed:

rg -i "getkey_script|pgsodium.*path" -A 2 -B 1 | head -80

Repository: supabase/postgres

Length of output: 5047


🏁 Script executed:

grep -r "run-server\|launcher\|wrapper" nix/packages/postgres-portable.nix || echo "Not found in postgres-portable.nix"

Repository: supabase/postgres

Length of output: 345


🏁 Script executed:

rg "configBundle|postgresql\.conf\.template" nix/packages/ -A 5 -B 5 | head -60

Repository: supabase/postgres

Length of output: 2061


🏁 Script executed:

cd nix/packages && grep -A 30 "buildPhase\|installPhase" postgres-portable.nix | grep -E "run-server|launcher|bin/" | head -20

Repository: supabase/postgres

Length of output: 261


🏁 Script executed:

# Check what's actually included in the final output
grep -A 50 "stdenv.mkDerivation {" nix/packages/postgres-portable.nix | grep -E "mkdir|cp|bin/|share/" | head -30

Repository: supabase/postgres

Length of output: 1025


🏁 Script executed:

# Look for documentation or CLI integration
find . -name "README*" -o -name "*.md" | head -10

Repository: supabase/postgres

Length of output: 362


🏁 Script executed:

# Search for references to share/supabase-cli/config
rg "share/supabase-cli/config|cli-config" --type md --type txt -A 2 -B 1

Repository: supabase/postgres

Length of output: 43


🏁 Script executed:

cat README.md | grep -A 20 -i "portable\|cli\|config" | head -50

Repository: supabase/postgres

Length of output: 3259


🏁 Script executed:

# Search for where CLI might process these templates
rg "share/supabase-cli" -l

Repository: supabase/postgres

Length of output: 144


🏁 Script executed:

# Look for any comments or issues about template configuration in the codebase
rg "placeholder|path.*template|getkey.*path" --type nix -B 2 -A 2 | head -80

Repository: supabase/postgres

Length of output: 379


🏁 Script executed:

# Check if there's a mechanism in the nix package to handle path substitution
tail -100 nix/packages/postgres-portable.nix

Repository: supabase/postgres

Length of output: 4291


Uncomment and configure getkey_script paths in postgresql.conf.template for portable bundle usage.

The template has pgsodium.getkey_script and vault.getkey_script commented out with a placeholder path. Since the portable bundle installs the script to share/supabase-cli/config/pgsodium_getkey.sh, these lines should be uncommented and configured to reference that location, or a documented mechanism must exist for the CLI to set these paths at startup. Without this configuration, the pgsodium and vault extensions won't function. The pg_hba.conf configuration (trust for local, scram-sha-256 for network) is appropriate for passwordless init.

🤖 Prompt for AI Agents
In `@nix/packages/postgres-portable.nix` around lines 50 - 61, Uncomment and set
the getkey_script paths in the postgresql.conf.template so the portable bundle
points to the installed script path (installed by configBundle) — update the
pgsodium.getkey_script and vault.getkey_script entries in
postgresql.conf.template to reference the bundle location
(share/supabase-cli/config/pgsodium_getkey.sh) or implement a startup hook in
the CLI to rewrite those config keys at launch; ensure references align with the
install path created by configBundle (the directory created in installPhase) and
document the behavior so pgsodium and vault can locate the getkey script when
the portable bundle is used.

@LGUG2Z LGUG2Z force-pushed the feature/sb-cli-binaries branch 2 times, most recently from 7b41b7c to fc53a9e Compare February 5, 2026 01:26
@LGUG2Z
Copy link
Author

LGUG2Z commented Feb 5, 2026

@jgoux

I've updated the PR to

  • Comprehensively strip out hardcoded paths at compile time and added the integration test we discussed on Slack
  • Added the safeupdate ext
  • Use trust in the hba conf for now until we decide how to move ahead with the init

I have not made any changes to the template - I think it's normal to update a config template at runtime

Adhoc codesigning isn't working and is hard to debug in CI, I think it's ok to continue with client-side codesigning for macOS for now since that is what homebrew kegs do when installing

Let's discuss the init with others on Slack and then we can update this PR with whatever is decided

@LGUG2Z LGUG2Z force-pushed the feature/sb-cli-binaries branch from fc53a9e to a35290e Compare February 5, 2026 01:41
@LGUG2Z
Copy link
Author

LGUG2Z commented Feb 5, 2026

@CodeRabbit pause

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 5, 2026

✅ Actions performed

Reviews paused.

@LGUG2Z LGUG2Z force-pushed the feature/sb-cli-binaries branch 10 times, most recently from 6a1ceb1 to 26c381c Compare February 5, 2026 20:52
…ibution

This commit implements portable, self-contained PostgreSQL binaries for
the Supabase CLI across macOS (ARM), Linux (x64), and Linux (ARM64),
along with automated CI/CD workflows for building and releasing these
artifacts.

The Supabase CLI needs to ship PostgreSQL binaries that work on user
machines without requiring Nix or other system dependencies. This means
extracting the actual binaries from Nix's wrapper scripts, bundling all
necessary shared libraries, and patching them to use relative paths
instead of hardcoded Nix store paths.

A `variant` parameter was added to the postgres build pipeline to
distinguish between "full" (all extensions) and "cli" (minimal
extensions for Supabase CLI). The `cliExtensions` list contains 6
extensions required for running Supabase migrations: supautils,
pg_graphql, pgsodium, supabase_vault, pg_net, and pg_cron. Built-in
extensions (uuid-ossp, pgcrypto, pg_stat_statements) are included
automatically with PostgreSQL. `makeOurPostgresPkgs`/`makePostgresBin`
were modified to accept this parameter. A new `psql_17_cli` package is
created using `variant = "cli"`, while the full extension set is
preserved for base packages (`psql_15`, `psql_17`, `psql_orioledb-17`).

The portable CLI variant (`psql_17_cli_portable`) includes 6 extensions
for migration support while maintaining a significantly smaller size
than the full build. The implementation in
`nix/packages/postgres-portable.nix` extracts binaries from
`psql_17_cli` using a `resolve_binary()` function that follows wrapper
layers to find the actual ELF/Mach-O binaries behind Nix's environment
setup scripts.

All Nix-provided libraries (ICU, readline, zlib, etc.) are bundled while
excluding system libraries (`libc`, `libpthread`, `libm`, `glibc`,
`libdl`) that must come from the host. This distinction is critical:
Linux bundles must exclude glibc due to kernel ABI dependencies, while
macOS can include more libs due to its different linking model.
Dependency resolution runs multiple passes to catch transitive deps
(e.g., ICU → charset → etc.).

Platform-specific patching is applied: Linux binaries use the system
interpreter (`/lib64/ld-linux-*.so.2`) and `$ORIGIN`-based RPATHs, while
macOS binaries use `@rpath` with `@executable_path`. Wrapper scripts set
`LD_LIBRARY_PATH` (Linux) or `DYLD_LIBRARY_PATH` (macOS) to find bundled
libraries. The bundle includes PostgreSQL config templates
(`postgresql.conf`, `pg_hba.conf`, `pg_ident.conf`) tailored for CLI
usage with minimal local dev settings, plus the complete Supabase
migration script (`migrate.sh`) with all init-scripts and migration SQL
files (55 files, 236KB).

A Docker-style initialization script (`supabase-postgres-init.sh`)
provides one-command database setup via environment variables
(`POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`, `PGDATA`).

To achieve true portability, a `portable` parameter was added to the
PostgreSQL build in `nix/postgresql/generic.nix`. When `portable =
true`, three critical hardcoded paths are excluded from the build:

1. `--with-system-tzdata` is removed from configure flags, allowing
   PostgreSQL to use bundled timezone data from the `share/` directory
   instead of a hardcoded `/nix/store/.../tzdata` path;

2. the `locale-binary-path.patch` is excluded, so PostgreSQL calls
   `locale -a` from system PATH rather than using an absolute path to
   glibc's locale command;

3. the `postFixup` initdb wrapper is disabled to avoid hardcoding glibc
   paths. The `portable` parameter defaults to `false` in
   `nix/postgresql/default.nix` but is overridden to `true` for the CLI
   variant in `nix/packages/postgres.nix` using `.override { portable =
   true; }`.

This ensures standard PostgreSQL builds remain unchanged while the CLI
variant produces truly portable binaries without any `/nix/store`
references.

A GitHub Actions workflow builds portable binaries across all three
platforms using a matrix strategy. Each build runs automated portability
checks that verify no `/nix/store` references remain, validate RPATH
configuration, confirm transitive dependencies are bundled, ensure
system libraries are NOT bundled, and check wrapper scripts contain
proper library path setup. Post-build testing validates binaries work
without Nix (`postgres --version`, `psql --version`). On tagged releases
(`v*-cli`), the workflow creates GitHub releases with tarball artifacts
and checksums.

The test infrastructure needed significant changes to support variants
with different extension sets. An `isCliVariant` parameter was added to
`makeCheckHarness`, and the hardcoded `shared_preload_libraries` list in
`postgresql.conf.in` was replaced with a `@PRELOAD_LIBRARIES@`
placeholder. A `generatePreloadLibs` script now parses `receipt.json` at
test time and dynamically builds the preload list based on available
extensions, removing the previous timescaledb removal hack for OrioleDB.
@LGUG2Z LGUG2Z force-pushed the feature/sb-cli-binaries branch from 26c381c to 09c97c8 Compare February 6, 2026 17:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants