Addresses the second half of #243 — the pantry start postgres failures on Linux. The RPATH half of that issue is deliberately untouched; see the end.
1. The unit's ExecStart was mangled by systemd
Reported as Ignoring unknown escape sequences followed by status=127. Postgres was the only self-initializing service still inlining a /bin/sh -c '…' command into ExecStart=; mysql and mariadb already write a #!/bin/sh script and point ExecStart at it. systemd parses that line itself — it applies its own quote removal and warns on the \n the command needs for tr — so what /bin/sh received was not what was written.
Postgres now launches from a generated script, scoped per project exactly as mysql's is, with the comment mysql's carries about why the run prefix lives in the positional parameters rather than a string.
2. The library search root pointed somewhere that doesn't exist
pantryRootOf looks for a literal /pantry/ segment in the resolved binary path. A global install has none: binaries are symlinked into <prefix>/bin and packages live under <prefix>/packages, so a resolved /usr/local/bin/initdb fell through to a hardcoded /opt/pantry/pantry. On the reporter's host that directory does not exist, the find matched nothing, the library path came out empty, and postgres could not load libpq.
libraryGlobRootOf derives the root from the binary:
| resolved binary | root |
|---|---|
/usr/local/packages/postgresql.org/v17.10/bin/initdb | /usr/local/packages |
/usr/local/bin/initdb | /usr/local |
/srv/app/pantry/postgresql.org/v17.10/bin/postgres | /srv/app/pantry |
~/.local/share/pantry/global/packages/…/bin/postgres | …/global/packages |
/packages/ is checked before /pantry/ because the user-local global root contains both markers and the deeper one is the tighter search. With neither present, the binary's own parent is the only root we can name that exists by construction. mysql and mariadb shared the same hardcoded fallback and get the same fix.
This may also be why their initdb failed with FileNotFound, independently of the RPATH question — an empty library path and a missing rpath present identically at that point.
3. The missing-user-bus error named neither cause nor fix
systemctl --user without a user bus fails with Failed to connect to bus: No medium found. A start in user scope now checks for the bus's runtime socket first (no process spawned) and reports loginctl enable-linger <user>, with system scope as the alternative.
Tests
libraryGlobRootOfis covered across all four layouts above.- The postgres
ExecStartis pinned as a bare/bin/sh <path>containing no quoting, escapes or shell metacharacters for systemd to misread, and the test then reads the generated script off disk and asserts its shebang,-p 5432, the version guard, theexecline and the real PGDATA path — so the port is verified in the thing that actually launches the server. - Four existing postgres tests asserted the port appears literally in
start_command, which the script makes untrue. They now assert it where it lives, following the convention the mariadb test had already recorded in a comment ("self-init services launch via a generated start script, so the port lives in that script + health_check").
zig build test:services 146/146. Full zig build test 555 pass / 6 skip; the 2 failures (shell.install_route bash + zsh) are pre-existing on stock main on this machine and unrelated — verified by running the suite on both trees earlier.
What is NOT here
The RPATH half of #243 (readelf -d showing [/../lib]). fixLinuxRpaths constructs $ORIGIN/../lib correctly and the Linux global-install path does reach it, so a literal /../lib means either the value is being evaluated somewhere with $ORIGIN empty, or patchelf is absent on that host and the function — deliberately best-effort — returns silently, leaving the upstream tarball's rpath in place. The reporter's note that an older pantry wrote working LD_LIBRARY_PATH shims fits the second. readelf -d <pkg>/bin/initdb and which patchelf on that box separate the two; this is an Apple Silicon Mac and CLAUDE.md is explicit that dispatching a workflow to find out costs 10×.
🤖 Generated with Claude Code