installer: fix empty -email flag breaking the default install, and make re-runs safe - #43
Merged
Merged
Conversation
…ke re-runs safe Two launch-blocking defects found in container testing. F1 — the default non-interactive install produced a malformed systemd unit. `curl -fsSL https://pilotprotocol.network/install.sh | sh` sets no email, and the unit rendered `-email` with an EMPTY value: -identity /home/u/.pilot/identity.json \ -email \ -encrypt Go's flag parser then consumed the next flag as the email value, so the daemon failed validation every 5s forever ("daemon start: invalid email: email address must contain @") and `-encrypt` was silently swallowed. The node never registered. Every optional argument is now emitted as a complete flag+value pair or not at all. Omitting `-email` is the well-defined path: the daemon falls back to ~/.pilot/account.json and, failing that, synthesises <fingerprint>@nodes.pilotprotocol.network — exactly as the installer header already documents. The macOS LaunchAgent plist had the same empty-argument shape and is fixed identically; -hostname/-public were already guarded. F2 — re-running the installer died on a systemd host, blocking the upgrade path. pilot-updater is now auto-enabled and RUNNING after install, so `cp` over its live binary failed with ETXTBSY ("Text file busy"), aborting mid-install under `set -e` with binaries partially replaced. Binaries are now installed via a same-directory temp file + rename(2), which is immune to ETXTBSY and cannot leave a truncated binary at the real path. Managed services are stopped before the swap and restarted after, so the new image is actually picked up. The whole staged payload is resolved and validated before anything is replaced, so an incomplete download leaves the existing install untouched. Coupled fix: the update path used to `exit 0` before the unit was ever rewritten, so re-running could never repair a host installed by a buggy installer — which is the only remedy available to every existing install (historically pinned at its install-time version). Unit/plist generation, the service restart and PATH setup now run on BOTH paths; only the first-run onboarding text is skipped on upgrade. config.json and auto-update.json remain fresh-install-only so a re-run never clobbers operator settings, and -hostname/-public already baked into an existing unit/plist are preserved when the matching env var is not supplied. A crashlooping unit reports "activating"/"failed" rather than "active", so the restart set includes an enabled-but-failed unit — that is precisely the state the -email bug produced, and repairing it must bring it back up. Verified in an ubuntu:24.04 container with systemd as PID 1 (arm64): - reproduced both defects against the current main installer - fresh non-interactive install, no email: unit has no -email, daemon starts, stays up across three checks over ~50s with NRestarts=0, and registers - PILOT_EMAIL set: renders `-encrypt -email <addr>`, daemon healthy, registers - installer re-run 5x over running services: exit 0 every time, binaries swapped, daemon + updater healthy after - broken install repaired by a single re-run, with -hostname preserved - systemctl is-enabled/is-active pilot-updater: enabled + active throughout - bash -n, sh -n, shellcheck -s sh all clean All test nodes were deregistered and confirmed absent from the registry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
TeoSlayer
added a commit
to pilot-protocol/website
that referenced
this pull request
Jul 27, 2026
…un fixes) (#176) The sync-install-sh workflow updated R2 (the live path) from pilot-protocol/release#43 but flagged public/install.sh — the Cloudflare Pages fallback — as drifted. It was still serving the pre-fix script, which renders a systemd unit with an empty `-email` value and crashloops the daemon, and which aborts with ETXTBSY when re-run over a running pilot-updater. Copies the canonical script over the fallback. Byte-identical to pilot-protocol/release@main install.sh (sha256 906deea1...). Co-authored-by: Teodor Calin <teodor@vulturelabs.io> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
TeoSlayer
added a commit
to pilot-protocol/pilotprotocol
that referenced
this pull request
Jul 28, 2026
The canonical-drift job added in #432 went red on main immediately after that PR merged, which is the guard doing exactly its job on its first real run. pilot-protocol/release#43 landed overnight — "fix empty -email flag breaking the default install, and make re-runs safe" — so canonical moved from 988ddeb to 906deea while this repo's copy stayed at 988ddeb. That follow-up is a direct consequence of #432/release#42: once the installer actually started the services instead of only writing the unit files, a unit generated with an empty `-email ` flag became a real failure rather than dead config. release#43 adds a privilege gate (CAN_PRIV / PILOT_SUDO, never prompting), recovers an existing email from account.json/config.json on every run so re-runs don't drop the operator's address, and skips synthesised @nodes.pilotprotocol.network placeholders so they can't harden into an override that defeats a later `pilotctl set-email`. install.sh here is now byte-identical to release@51894f1 (sha256 906deea1…), which is also what the live URL serves. Co-authored-by: Teodor Calin <teodor@vulturelabs.io> Co-authored-by: Claude Opus 5 <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.
Two launch-blocking defects found in container testing.
F1 — malformed systemd unit on the default install (CRITICAL)
The advertised non-interactive
curl -fsSL https://pilotprotocol.network/install.sh | shsets no email, and the unit rendered-emailwith an empty value:Go's flag parser consumed the next flag (
-encrypt) as the email value, so the daemon failed validation every 5s forever —daemon start: invalid email: email address must contain @→pilot-daemon.service: Failed with result 'exit-code'. The node never registered, and-encryptwas silently swallowed.Fix: every optional argument is emitted as a complete flag+value pair or not at all. Omitting
-emailis the well-defined path — the daemon falls back to~/.pilot/account.jsonand, failing that, synthesises<fingerprint>@nodes.pilotprotocol.network, exactly as the installer header already documents. The macOS LaunchAgent plist had the same empty-argument shape and is fixed identically.-hostname/-publicwere already guarded against this.F2 — re-running the installer failed on a systemd host
pilot-updateris now auto-enabled and RUNNING after install, socpover its live binary failed withETXTBSY("Text file busy"), aborting mid-install underset -ewith binaries partially replaced.Fix: binaries install via a same-directory temp file +
rename(2)— immune to ETXTBSY, and it cannot leave a truncated binary at the real path. Managed services are stopped before the swap and restarted after, so the new image is actually picked up. The whole staged payload is resolved and validated before anything is replaced, so an incomplete download leaves the existing install untouched.Coupled fix — re-run is now a real repair path
The update path used to
exit 0before the unit was ever rewritten, so re-running could never repair a host installed by a buggy installer — which is the only remedy available to every existing install (historically pinned at its install-time version).Unit/plist generation, service restart and PATH setup now run on both paths; only the first-run onboarding text is skipped on upgrade.
config.jsonandauto-update.jsonstay fresh-install-only so a re-run never clobbers operator settings, and-hostname/-publicalready baked into an existing unit/plist are preserved when the matching env var is not supplied.A crashlooping unit reports
activating/failedrather thanactive, so the restart set includes an enabled-but-failed unit — precisely the state the-emailbug produced.Container proof
ubuntu:24.04+ systemd as PID 1, arm64, non-root user with passwordless sudo:main-email \+invalid emailcrashloop; re-runREAL_EXIT=1on ETXTBSY-email-encrypt, zero-emailoccurrencesactive,NRestarts=0, stable MainPIDdaemon registered node_id=251638 addr=0:0000.0003.D6F6, synthetic emailPILOT_EMAILset → renders correctly and works-encrypt -email teodor+ctest@vulturelabs.io,active,NRestarts=0, registeredactiveaftersystemctl is-enabled pilot-updaterenabledandactivethroughout-hostname pilot-ctest-repairpreserved, daemonactive+ registeredbash -n/sh -n/shellcheck -s shAll test nodes (251638–251642) were
pilotctl deregister'd and confirmed absent —pilotctl lookupreturnsnode not found,pilotctl find pilot-ctest-repairreturnshostname not found.🤖 Generated with Claude Code