Skip to content

fix: bootstrap PostgreSQL without a working service manager (#823) - #824

Open
GG-O-BP wants to merge 1 commit into
mendixlabs:mainfrom
GG-O-BP:fix/823-ensure-db-initdb-fallback
Open

fix: bootstrap PostgreSQL without a working service manager (#823)#824
GG-O-BP wants to merge 1 commit into
mendixlabs:mainfrom
GG-O-BP:fix/823-ensure-db-initdb-fallback

Conversation

@GG-O-BP

@GG-O-BP GG-O-BP commented Aug 3, 2026

Copy link
Copy Markdown

What & why

Closes #823.

mxcli run --ensure-db only knew service and a non-working Debian
pg_ctlcluster placeholder. On Arch neither produced a server, so bootstrap
failed even though the portable initdb, pg_ctl, and psql tools were
available.

Fix

  • Try the real service postgresql start path and confirm readiness with a
    short probe. Remove the unusable pg_ctlcluster -- start placeholder so
    service attempts no longer multiply the overall timeout.
  • If no service becomes ready, initialize/start a user-owned cluster under
    ~/.mxcli/postgres with initdb / pg_ctl.
  • Keep socket authentication password-free only inside the private 0700
    socket directory (--auth-local=trust), while loopback TCP uses
    --auth-host=scram-sha-256. Provision the role/database as postgres over
    that socket; retain sudo -u postgres psql for system clusters.
  • Reuse an initialized data directory. A running cluster is reused only when
    line 4 of postmaster.pid confirms the requested port; a mismatched or
    unreadable port fails immediately with an actionable error.
  • Normalize an empty host (--db-host ":5432"), include the server-log path in
    startup failures, and preserve service-manager diagnostics when fallback
    also fails.
  • Remote database hosts remain check-only and are never provisioned.

Tests

Command-stub tests cover:

  • ready and ineffective service paths
  • absence of the removed pg_ctlcluster placeholder
  • missing portable tools and first initialization
  • initialized/stopped, already-running, mismatched-port, and unknown-port cases
  • hardened initdb auth and socket-based superuser selection
  • direct and sudo superuser paths
  • empty-host normalization, service diagnostics, and log-named startup errors

Validation:

  • go build ./...
  • make vet
  • go test ./cmd/mxcli/docker -count=1
  • real PostgreSQL 18.4 bootstrap on an Arch-shaped PATH (no service or
    pg_ctlcluster), role/database creation, repeated EnsureDatabase, and a
    verified 0700 socket directory

Docs

Updated the run-local skill, docs-site page, CHANGELOG, and appended the
fix-issue symptom entry at the end of its table.

@GG-O-BP GG-O-BP changed the title fix: start PostgreSQL via initdb/pg_ctl when no service manager exists (#823) fix: fall back to initdb/pg_ctl when PostgreSQL service is unavailable (#823) Aug 3, 2026
@GG-O-BP
GG-O-BP force-pushed the fix/823-ensure-db-initdb-fallback branch from b3c8c3f to 1b66266 Compare August 3, 2026 20:45
@GG-O-BP GG-O-BP changed the title fix: fall back to initdb/pg_ctl when PostgreSQL service is unavailable (#823) fix: start PostgreSQL via initdb/pg_ctl when no service manager exists (#823) Aug 3, 2026
@GG-O-BP
GG-O-BP force-pushed the fix/823-ensure-db-initdb-fallback branch from 1b66266 to e2ea586 Compare August 3, 2026 21:12
@GG-O-BP GG-O-BP changed the title fix: start PostgreSQL via initdb/pg_ctl when no service manager exists (#823) fix: bootstrap PostgreSQL without a working service manager (#823) Aug 3, 2026
@ako

ako commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

Review

Sound fix, no blockers. Three things I'd want changed before merge, all localized to startLocalPostgres/startUserCluster.

What I verified — applied the two Go files to current main in a throwaway worktree (they apply cleanly):

  • go vet ./cmd/mxcli/docker clean; all 12 new subtests pass in 0.5 s.
  • The reported Arch path is genuinely fixed: neither service nor pg_ctlcluster is on PATH, both LookPath calls skip, and the fallback runs immediately.

Moderate

1. The fallback multiplies the timeout by the number of service attempts — confirmed, not theoretical.

Moving waitPGReady inside the attempts loop means every attempt pays a full 20 s. Instrumented with a scaled-down serviceReadyTimeout, counting probe invocations:

service → isready → pg_ctlcluster → isready → initdb → pg_ctl_start
ready-waits paid = 2   (at the real timeout: 40 s)

Worst case is now 20 + 20 (services) + 30 (pg_ctl -w -t 30) + 20 (EnsureDatabase's own waitPGReady) ≈ 90 s before an error surfaces, against ~20 s before. Worse, 20 s of that is spent on {"pg_ctlcluster", "--", "start"} — the entry whose own comment admits the args are a placeholder and cannot work.

Fix: drop the pg_ctlcluster placeholder, and probe inside the loop with a short timeout (2–3 s), keeping the single long waitPGReady where it already is in EnsureDatabase.

2. --auth-host=trust disables authentication on 127.0.0.1:5432.

Confirmed via the initdb args: -U postgres --auth-local=trust --auth-host=trust. The comment justifies it with "the server binds only the local interface", but loopback is not an access control on a multi-user host: any local account becomes the postgres superuser, and PostgreSQL superuser gets COPY … PROGRAM, i.e. shell as the developer's OS user.

Fix: the socket directory is already 0700 (verified — drwx------), so trust over the socket is genuinely safe. Keep --auth-local=trust, set --auth-host=scram-sha-256, and point superuser.psql's non-sudo branch at -h <sockDir> instead of -h 127.0.0.1. The app role already has a password, so canConnectDB over TCP still works unchanged.

3. The fix-issue.md row lands mid-table.

The hunk inserts at line ~322; on current main that table ends at line 429. .gitattributes sets merge=union on that file — which keeps both sides of a conflicting hunk, so a mid-table insert against a moved table interleaves and merges "cleanly" while being wrong. The branch predates the append-at-the-end rule.

Fix: rebase and move the row to the end of the table.

Minor

  • --db-host ":5432" produces a malformed server option. isLocalHost("") is true, so an empty host reaches -o "-h -p 5432 -k …"; pg_ctl splits on whitespace and postmaster reads -p as the value of -h. Confirmed in a probe. Obscure (the default fills in 127.0.0.1:5432), but a one-line guard in startUserCluster would close it.
  • pg_ctl status == 0 returns early without checking the port. A cluster left running from an earlier --ensure-db --db-port 5433 satisfies status, then EnsureDatabase burns 20 s on waitPGReady against the wrong port and reports a confusing failure. Reading the port from line 4 of postmaster.pid would make it precise.
  • The server log path isn't in the error. startUserCluster writes -l ~/.mxcli/postgres/server.log, but the failure message carries only pg_ctl's stdout. Naming the log file makes the error self-service.
  • Service-manager output is now fully discarded (_ = exec.Command(…).Run()). If service postgresql start explains itself, the user instead sees an initdb error from two steps later.

Checklist

Bug-fix requirements met: symptom row added, tests accompany the fix, .claude/skills/mendix/run-local.md (the sync source, not the regenerated cmd/mxcli/skills/ embed dir — correct choice), the docs-site page, and the CHANGELOG all updated. Single concern, single commit.

The mdl-examples/bug-tests/ requirement is the only apparent checklist gap and is correctly N/A — there's no MDL surface here and nothing for Studio Pro to validate. The backend-abstraction, grammar, and syntax/features_*.go sections don't apply either.

Thanks for the thorough stub-test coverage — the ready/non-ready service split and the idempotency cases are exactly the right seams to pin down.

🤖 Generated with Claude Code

@GG-O-BP

GG-O-BP commented Aug 10, 2026

Copy link
Copy Markdown
Author

Thanks for the thorough review, @ako — all seven points are addressed. Rebased onto main (the branch was CONFLICTING; it's now a clean single commit) and everything stays localized to startLocalPostgres/startUserCluster/resolveSuperuser.

Moderate

  1. Timeout multiplication + dead pg_ctlcluster. Dropped the pg_ctlcluster placeholder entirely, and each service attempt now pays only a short serviceReadyTimeout (3 s, down from 20 s) — the single authoritative 20 s wait stays in EnsureDatabase. New TestStartLocalPostgres_NeverRunsPgCtlCluster asserts the entry can never run even when it's on PATH.
  2. --auth-host=trust on loopback TCP. initdb now uses --auth-local=trust --auth-host=scram-sha-256, and the direct superuser connects over the 0700 socket dir (-h <sockDir>) instead of -h 127.0.0.1. So the passwordless superuser can only reach itself over the private socket; loopback TCP is no longer an authentication bypass. canConnectDB (app role, has a password) still works over TCP unchanged. Covered by TestStartUserCluster_InitdbAuthArgs and TestResolveSuperuser_PrefersClusterSocket.
  3. fix-issue.md row mid-table. Rebased; the row is now appended at the very end of the symptom table (one-line diff).

Minor

  • Empty --db-host (":5432"). Normalised to 127.0.0.1 in both EnsureDatabase and startUserCluster, so the server option is well-formed. TestStartUserCluster_EmptyHostNormalised reproduces the old malformed -h -p 5432 and asserts the fix.
  • pg_ctl status==0 ignoring the port. New clusterStatus reads the port from line 4 of postmaster.pid; a running cluster is reused only when its port matches, otherwise it fails fast with an actionable message instead of burning the readiness wait on the wrong port. TestStartUserCluster_RunningPortGuard covers both branches.
  • Server log path missing from the error. The start-failure error now names ~/.mxcli/postgres/server.log (TestStartUserCluster_StartErrorNamesLog).
  • Discarded service-manager output. Captured and appended to the fallback error when the fallback then fails (TestStartLocalPostgres_SurfacesServiceOutput).

Each new test was proven to fail on the un-fixed code (reverted each change in a throwaway worktree and watched the matching test fail with the reported symptom). make vet, go build ./..., and go test ./cmd/mxcli/docker -count=1 are green.

…endixlabs#823)

`mxcli run --ensure-db` only tried `service` and Debian's `pg_ctlcluster`
to start a local PostgreSQL server. On Arch neither exists, so bootstrap
failed even though `initdb`, `pg_ctl`, and `psql` were available.

Try a real service manager (`service postgresql start`), confirming it
actually makes PostgreSQL ready with a short readiness probe. If none is
present or ready, start a user-owned cluster under `~/.mxcli/postgres`
with `initdb`/`pg_ctl`. Reuse an initialized data directory and leave an
already-running server alone so repeated runs are safe.

Provision the application role/database as the cluster's own `postgres`
superuser over its private 0700 Unix socket, without requiring a
`postgres` OS account or passwordless sudo. Retain the existing
`sudo -u postgres psql` path for system clusters.

Review hardening (thanks @ako):
- Drop the `pg_ctlcluster` placeholder (its args could never start a
  cluster) and probe each service attempt with a short timeout, so N
  managers no longer multiply the overall deadline; the single long
  readiness wait stays in EnsureDatabase.
- initdb now uses `--auth-host=scram-sha-256` (only the private socket is
  trust): loopback TCP is not an access control on a multi-user host, so
  trust there would let any local account become the postgres superuser.
  The superuser connection goes over the socket accordingly.
- Normalise an empty `--db-host` (":5432") to loopback so the server's
  `-o "-h ..."` option is well-formed.
- Reuse a running cluster only when its `postmaster.pid` port matches the
  requested port; otherwise fail fast instead of waiting on the wrong one.
- Name the server log file in the start-failure error, and surface the
  service manager's own output when the fallback then fails.

Add focused command-stub tests for the dropped `pg_ctlcluster`, the auth
args, socket-preferred superuser resolution, empty-host normalisation,
the running-port guard, and the log-named start error. Update the docs,
changelog, and fix-issue symptom entry (appended at the table end).
@GG-O-BP
GG-O-BP force-pushed the fix/823-ensure-db-initdb-fallback branch from 79dae2b to a99673e Compare August 10, 2026 05:05
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.

mxcli run --ensure-db can't start PostgreSQL on Arch Linux

2 participants