Skip to content

installer: actually enable the auto-updater on Linux and macOS - #42

Merged
TeoSlayer merged 1 commit into
mainfrom
fix/activate-auto-updater
Jul 27, 2026
Merged

installer: actually enable the auto-updater on Linux and macOS#42
TeoSlayer merged 1 commit into
mainfrom
fix/activate-auto-updater

Conversation

@TeoSlayer

Copy link
Copy Markdown
Contributor

The installer promises auto-updates, then doesn't deliver them

install.sh writes /etc/systemd/system/pilot-updater.service (Linux) and ~/Library/LaunchAgents/network.pilotprotocol.pilot-updater.plist (macOS). It writes ~/.pilot/auto-update.json with {"enabled": true} and prints "Auto-updates ENABLED (opt-out)". The consent block goes further:

AUTO-UPDATES (on by default)
The pilot-updater service checks GitHub for new stable releases and installs them automatically.

None of that was true. The unit and the plist are written but never activated. On a fresh install:

$ systemctl is-enabled pilot-updater
disabled
$ systemctl status pilot-updater
○ pilot-updater.service - Pilot Protocol Auto-Updater
     Loaded: loaded (/etc/systemd/system/pilot-updater.service; disabled; preset: enabled)
     Active: inactive (dead)

macOS is the same — launchctl print gui/$UID/network.pilotprotocol.pilot-updater finds no such job.

Impact: every install from https://pilotprotocol.network/install.sh stays pinned on whatever release shipped at install time and never receives a security or perf fix — silently, while telling the operator the opposite.

This was never a regression here

git log -S 'systemctl enable --now pilot-updater' -- install.sh returns nothing in this repo. The activation has never existed in the canonical script.

The fix does exist in pilot-protocol/pilotprotocol's copy of install.sh — added specifically to close this gap, and guarded there by install-test.yml, whose header comment documents the exact failure mode:

install.sh historically wrote the systemd unit / LaunchAgent plist but did NOT activate it. Operators read past the echoed instructions and their installs stayed pinned on the release shipping at install time.

The two scripts had forked. The copy that got the fix is the one no user runs; the copy served to users is this one. CI stayed green the whole time because it only ever guarded the wrong file.

The fix

Ported both branches verbatim from the working copy:

  • Linuxsudo systemctl enable --now pilot-updater, non-fatal on failure.
  • macOS — unload-then-launchctl load -w on the updater plist, which keeps re-running install.sh (the upgrade path) idempotent.

The daemon is deliberately left opt-in on both platforms — it has operator-tunable flags (-public, -hostname, registry overrides) an operator may want to set before first start. Only the updater is auto-started.

Verification

Proven on real GitHub-hosted ubuntu-latest and macos-latest runners via pilot-protocol/pilotprotocol#432, which asserts the updater is enabled+active (Linux) and loaded (macOS) after a non-interactive install. Those jobs failed against the current canonical script and pass with this one.

Deploy impact

⚠️ Merging this changes the live installer. sync-install-sh fires on install.sh changes and uploads this script to R2, which is what https://pilotprotocol.network/install.sh serves. That is the point — but it is a live change, not a no-op.

🤖 Generated with Claude Code

The installer writes /etc/systemd/system/pilot-updater.service (Linux) and
~/Library/LaunchAgents/network.pilotprotocol.pilot-updater.plist (macOS), and
it writes ~/.pilot/auto-update.json with {"enabled": true} while printing
"Auto-updates ENABLED (opt-out)". The consent block goes further and promises:

    AUTO-UPDATES (on by default)
    The pilot-updater service checks GitHub for new stable releases and
    installs them automatically.

None of that was true. The unit and the plist were written but never
activated, so on a fresh install `systemctl is-enabled pilot-updater` reports
`disabled` / `inactive (dead)` and launchctl has no such job. Every install
from https://pilotprotocol.network/install.sh stays pinned on whatever release
shipped at install time and never receives a security or perf fix — silently,
while telling the operator the opposite.

This is not a recent regression: `git log -S` finds no commit in this repo that
ever added the activation. The fix exists in pilot-protocol/pilotprotocol's
copy of install.sh (added specifically to close this gap, and guarded there by
install-test.yml), but the two scripts had forked, and the copy served to users
is this one — the one that never got it. The guard never covered the canonical
script, so CI stayed green while the live installer shipped the defect.

Ported both branches verbatim from the working copy:
  - Linux: `sudo systemctl enable --now pilot-updater`, non-fatal on failure.
  - macOS: unload-then-`launchctl load -w` on the updater plist, which keeps
    re-running install.sh (the upgrade path) idempotent.

The daemon is deliberately left opt-in on both platforms — it has
operator-tunable flags (-public, -hostname, registry overrides) that an
operator may want to set before first start. Only the updater is auto-started.

Verified on real GitHub-hosted ubuntu-latest and macos-latest runners via
pilot-protocol/pilotprotocol#432, which asserts the updater is enabled+active
(Linux) and loaded (macOS) after a non-interactive install.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@TeoSlayer
TeoSlayer merged commit 717c020 into main Jul 27, 2026
@TeoSlayer
TeoSlayer deleted the fix/activate-auto-updater branch July 27, 2026 15:12
TeoSlayer pushed a commit to pilot-protocol/website that referenced this pull request Jul 27, 2026
pilot-protocol/release#42 fixed a defect the audit turned up after #174: the
canonical installer wrote the systemd unit and the LaunchAgent plist but never
enabled or loaded them, so every install had auto-updates silently dead while
~/.pilot/auto-update.json and the printed consent block both claimed they were
on. That fix is now live (R2 serves sha256 988ddebd…, 55,527 bytes).

Re-point the Pages fallback at the new canonical so the hard drift check added
in this PR passes, and so the fallback carries the fix too.

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
…commands (#175)

* installer: sync Pages fallback with canonical script, fail on future drift

public/install.sh is the fallback served if the pilot-release Worker/R2
path fails. Its own sync workflow calls it "a shadowed fallback kept
byte-identical" to pilot-protocol/release:install.sh — but it had drifted
12KB and 3 days behind, and still shipped three defects that were already
fixed upstream:

  1. PATH invisible to non-interactive shells — `bash -c 'pilotctl version'`
     failed after a successful install. Fixed upstream by escalating the
     /usr/local/bin symlink through passwordless `sudo -n` and writing PATH
     to ~/.profile, ~/.bash_profile, ~/.bashrc, ~/.zshenv and ~/.zshrc.
  2. The post-install guide told users to pipe replies through `jq`, which
     the installer never installs. Replaced upstream with the native
     `pilotctl inbox --latest`.
  3. Post-install guidance emitted `pilotctl gateway start ...`; gateway is
     extras-only in the core CLI and the bare form no longer exists.

The stale copy also advertised `--version v1.10.5` as its pinned-version
example, five minor versions behind the v1.13.7 that stable now resolves to.

public/install.sh is now byte-identical to release@c8fcaa9 (sha256
4c0babf9…, 53917 bytes), which is also exactly what the live URL serves.

Verified end-to-end by running THIS copy in fresh debian:12 containers as a
non-root user, both with and without passwordless sudo. Install exits 0 in
both; `bash -c 'pilotctl version'` prints v1.13.7 from a new shell in both
(the no-sudo run has no /usr/local/bin symlink and relies purely on the
profile PATH writes); and the printed GET STARTED block contains no jq
invocation.

The drift check that let this happen was a `::warning::` nobody read. It is
now a hard failure — but deliberately moved to the END of the job, after the
R2 upload and live-URL verification. A drifted fallback must never block the
fast path that pushes a security fix to the live installer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* installer: fail on Pages fallback drift, use curl -f in blog install commands

Follow-up to #174, which resynced public/install.sh after it drifted 12KB and
3 days behind canonical while still shipping three already-fixed defects.

The guard that should have caught that was a `::warning::` nobody read. It is
now a hard failure — but deliberately moved to the END of the job, after the
R2 upload and the live-URL verification. A drifted fallback must never block
the fast path that pushes a security fix to the live installer; the failure is
a loud notification that the fallback needs syncing, not a gate on shipping.

Also: five install commands across two blog posts used `curl -sSL` instead of
`curl -fsSL`. Without -f, curl writes an HTTP error body to stdout and exits 0,
so a 404 or a Cloudflare error page gets piped straight into `sh`. Every other
install command on the site already used -fsSL; these five were the outliers.

  src/pages/blog/connect-agents-across-aws-gcp-azure-without-vpn.astro (4)
  src/pages/blog/run-agent-network-without-cloud-dependency.astro (1)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* installer: re-sync Pages fallback with the auto-updater activation fix

pilot-protocol/release#42 fixed a defect the audit turned up after #174: the
canonical installer wrote the systemd unit and the LaunchAgent plist but never
enabled or loaded them, so every install had auto-updates silently dead while
~/.pilot/auto-update.json and the printed consent block both claimed they were
on. That fix is now live (R2 serves sha256 988ddebd…, 55,527 bytes).

Re-point the Pages fallback at the new canonical so the hard drift check added
in this PR passes, and so the fallback carries the fix too.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

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
* installer: sync install.sh with canonical, guard the drift in CI

This repo's install.sh is a COPY. The script users actually run is served
from https://pilotprotocol.network/install.sh by the pilot-release Worker
out of R2, sourced from pilot-protocol/release:install.sh.

This copy had drifted 3 days and ~16KB behind canonical (38,150 vs 53,917
bytes) and still carried three defects already fixed upstream:

  1. PATH invisible to non-interactive shells — `bash -c 'pilotctl version'`
     failed after a successful install.
  2. The post-install guide piped replies through `jq`, which the installer
     never installs. This copy mentioned jq five times, the most of the three
     copies in the org.
  3. It emitted `pilotctl gateway start ...`. Gateway is extras-only in the
     core CLI — cmd/pilotctl/main.go:1737 hard-errors on the bare form with
     "gateway commands are not in the core CLI".

install.sh is now byte-identical to release@c8fcaa9 (sha256 4c0babf9…), which
is also exactly what the live URL serves.

README fixes for the same drift:
  - The demo showed `sudo pilotctl gateway start` — the form that hard-errors.
    Now `pilotctl extras gateway start`.
  - "What the installer does" claimed it installs pilot-gateway. Release
    tarballs ship daemon/pilotctl/updater only; I confirmed against the
    v1.13.7 artifacts. Reworded so nobody re-derives the dependency that broke
    fresh installs for ~6 weeks.

The CI gap: install-test.yml only asserted that the updater got enabled, so it
passed green throughout the drift, while the canonical script had no installer
CI at all. Added a canonical-drift job that diffs this copy against the release
repo and fails on any difference. Testing a copy that no user runs is theatre.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* installer: carry the auto-updater activation fix (release#42)

The two install-test jobs on this PR failed, and correctly so: syncing to
canonical would have regressed this repo's auto-updater activation.

The canonical script writes the systemd unit / LaunchAgent but never enables
or loads it, so `systemctl is-enabled pilot-updater` reported `disabled` and
launchctl had no job — while ~/.pilot/auto-update.json said {"enabled": true}
and the consent block promised auto-updates were on.

That gap is now fixed at the source in pilot-protocol/release#42, and this
commit picks up the fixed canonical script. install.sh here is byte-identical
to that branch, so the canonical-drift job will go green once release#42
merges.

This is exactly the regression install-test.yml was built to catch, and it
caught it. The reason it never fired before is that it only ever guarded this
repo's copy — which no user runs — while the canonical script served at
https://pilotprotocol.network/install.sh had no installer CI at all. The
canonical-drift job added in the previous commit closes that hole permanently.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Teodor Calin <teodor@vulturelabs.io>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.

2 participants