fix(domain): skip remote vhost for cp-http80 apps + 409 idempotent recovery - #190
Merged
Merged
Conversation
The custom-domain vhost script runs as the admin SSH user 'samo', but static release checkouts are owned by the app user, so 'git rev-parse HEAD' aborted with dubious-ownership and the guard falsely reported release drift, blocking 'domain add' for every static app. Scope git safe.directory to the already realpath-validated checkout path so the integrity check runs regardless of checkout ownership. Deploy path is unaffected (it SSHes as the app user). Apply the same fix to src/app/bootstrap.ts:762 (re-bootstrap of a host with an active static deployment runs as root, which also trips dubious-ownership on app-user-owned release dirs). Drop 2>/dev/null in both sites so real git errors reach the operator instead of being masked by the drift message. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
'systemctl reload caddy' can fail on the control plane with systemd status=226/NAMESPACE when the long-running caddy service's PrivateTmp namespace goes stale, which blocked 'domain add' at the control-plane vhost step. Fall back to 'caddy reload --config ... --force' (the admin API, zero-downtime, Caddy's recommended reload) when systemctl reload fails, so control-plane routing changes still apply. Fail-closed on a genuinely invalid config is preserved (both paths validate). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…teTmp namespace failure Records the systemd status=226/NAMESPACE failure mode (stale PrivateTmp staging dir on long-running caddy.service) and the admin-API reload fallback that samohost now uses on the control plane, plus the host-side maintenance remedy. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pins two behaviours that were missing and caused gamechangers.team domain registration to fail: 1. mainListen=cp-http80: remote vhost script must be skipped (the CP reverse_proxy already routes via header_up Host <mainHost>; the guard in the remote script rejects legacy apps without release state). 2. 409 duplicate: createCustomHostname returns 409 on re-run (CF already registered the hostname on a prior attempt that failed later); the command must recover via listCustomHostnames instead of aborting. Both tests are RED against the current implementation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…covery Two root-cause fixes surfaced while connecting gamechangers.team to the game-changers app: Fix 1 — cp-http80 remote vhost skip When app.mainListen === "cp-http80", the control-plane already proxies all traffic to the app VM using `header_up Host <app.mainHost>`, so the app VM's existing mainHost vhost handles the custom domain without any extra Caddy block. Writing a second vhost was also impossible: the guard in buildCustomDomainVhostScript requires .samohost-active-static.json, which does not exist on legacy apps that pre-date the releases-based deployment. The fix: gate the remote SSH step on `app.mainListen !== "cp-http80"`. Fix 2 — 409 idempotent re-run When domain add is interrupted after the CF API call but before local state is written, a re-run hits a 409 (duplicate custom hostname). The fix: catch CloudflareError(409) and recover the existing record via listCustomHostnames instead of aborting. Any other error status remains fatal (fail-closed). Both fixes are unit-tested in the companion RED commit (34e2201). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…p git rev-parse Adds the missing test samorev flagged for bootstrap.ts:762. The change in this PR fixed the dubious-ownership guard in buildHostBootstrapScript (static path) — switching from `rev-parse HEAD 2>/dev/null` (which suppressed the git error AND lacked safe.directory, causing an empty capture that always failed the sha comparison) to the ownership-scoped `-c safe.directory="$SAMOHOST_CHECKOUT_REAL" rev-parse HEAD` form. The test mirrors the pattern already established in env-script.test.ts #ownershipfix (line 3954) for the same root cause in buildCustomDomainVhostScript. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
samo-agent
marked this pull request as draft
July 21, 2026 05:57
samo-agent
marked this pull request as ready for review
July 21, 2026 05:57
…rigger CI Merge origin/main into fix/domain-add-cp-http80-and-409-idempotent to resolve the routing.md doc conflict (both sides added new ##-sections after line 74) and ensure the PR has a clean merge commit for CI to run against. All tests pass (2316 pass; packages/auth/pg.test.ts skipped locally — requires Postgres service, provided by CI). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Root-cause analysis
Triggered by connecting `gamechangers.team` to the `game-changers` app (id `bf003044`, `mainListen: "cp-http80"`). First run registered the CF custom hostname then aborted on the remote vhost guard. Second run hit 409. Both failures are fixed here. The CP vhost was written and Caddy was reloaded successfully; `domains.json` has the record with `hostnameStatus: pending / sslStatus: pending_validation` (correct — awaiting customer DNS).
Test plan
bun test test/domain-command.test.ts— 21 pass (2 new tests cover both fixes)bun test— 2061 pass, 0 fail/etc/caddy/sites.d/10-domain-gamechangers-team.caddyroutes to46.225.115.31:80withheader_up Host game-changers.samo.teamcaddy validatepasses; Caddy reloaded (confirmed via journal)🤖 Generated with Claude Code