Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,23 @@ Detailed per-release notes are on the

## [Unreleased]

### Added
- **Opt out of automatic app-store updates with `PILOT_APP_UPDATE_OPT_OUT`.** The
`pilot-updater` keeps installed apps current by periodically running
`pilotctl appstore upgrade --all`. Set `PILOT_APP_UPDATE_OPT_OUT=true` in the
updater's environment and it stops checking for and installing app updates —
installed apps stay at the version you installed. Unset it or set it to
`false` (the default) to switch app auto-updates back on. Pilot daemon/CLI
binary updates are never affected. Honors the existing
`PILOT_UPDATER_NO_APP_UPGRADE` as a back-compat alias.

### Fixed
- **The `pilotctl skills disable` opt-out now survives updates and explicit
reconciles.** A forced reconcile — `pilotctl skills check`, `pilotctl update`,
or an installer re-run — bypassed the disabled flag and re-injected skills a
user had turned off. The opt-out is now a hard gate on every write path; only
the read-only `pilotctl skills` status still previews. (skillinject)

## [1.12.8] - 2026-07-16

Reliable P2P data transfer across NAT, plus cold-start onboarding fixes: agents
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ Most daemon flags have an environment variable equivalent. Useful for containeri
| `PILOT_REPLY_WHITELIST` | `-reply-whitelist` | Nodes exempt from reply rate limit |
| `PILOT_REKEY_WHITELIST` | `-rekey-whitelist` | Nodes exempt from rekey rate limit |
| `PILOT_FLAG_<NAME>` | — | Feature flag override (`true`/`false`) |
| `PILOT_APP_UPDATE_OPT_OUT` | — | Opt out of automatic **app-store** updates. Set to `true` and the `pilot-updater` stops checking for and installing app updates — installed apps stay at their current version. Unset or `false` (the default) keeps app auto-updates on. Pilot daemon/CLI binary updates are unaffected. Read by `pilot-updater` at startup, so set it in the updater's service environment and restart the updater to change it. (Legacy alias: `PILOT_UPDATER_NO_APP_UPGRADE`.) |

---

Expand Down
23 changes: 21 additions & 2 deletions cmd/updater/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,22 @@ func envBool(name string) bool {
}
}

// appAutoUpgradeEnabled reports whether the updater should run the app
// auto-upgrade loop (the periodic `pilotctl appstore upgrade --all`).
//
// This is the app-update opt-out gate. By default (PILOT_APP_UPDATE_OPT_OUT unset or
// "false") the updater checks for and installs app updates for all installed
// apps. Set PILOT_APP_UPDATE_OPT_OUT=true to stop it — installed apps then stay at
// the version you installed until you opt back in (unset the variable or set it
// to false) and restart the updater. Binary updates to the pilot daemon/CLI are
// never affected by this gate.
//
// PILOT_UPDATER_NO_APP_UPGRADE is honored as a back-compat alias for the same
// opt-out. Either variable being truthy disables the loop.
func appAutoUpgradeEnabled() bool {
return !envBool("PILOT_APP_UPDATE_OPT_OUT") && !envBool("PILOT_UPDATER_NO_APP_UPGRADE")
}

func main() {
installDir := flag.String("install-dir", "", "directory containing pilot binaries (required)")
repo := flag.String("repo", "pilot-protocol/pilotprotocol", "GitHub owner/repo for releases")
Expand Down Expand Up @@ -100,10 +116,13 @@ func main() {
// fleet without anyone running upgrade by hand. Each upgrade re-runs the full
// catalogue-signature + manifest-signature + trust-anchor gate that install
// does, so this adds automation, not trust. Opt out with
// PILOT_UPDATER_NO_APP_UPGRADE for hosts that want binary-only updates.
if !envBool("PILOT_UPDATER_NO_APP_UPGRADE") {
// PILOT_APP_UPDATE_OPT_OUT=true for hosts that want binary-only updates; see
// appAutoUpgradeEnabled. The pilot daemon/CLI binaries keep updating.
if appAutoUpgradeEnabled() {
go appUpgradeLoop(*installDir, *statePath, *interval)
slog.Info("app auto-upgrade loop started", "interval", interval.String())
} else {
slog.Info("app auto-upgrade opted out (PILOT_APP_UPDATE_OPT_OUT); apps stay at their installed versions, pilot binaries still update")
}
if *pin != "" {
slog.Info("version pinned", "tag", *pin)
Expand Down
35 changes: 35 additions & 0 deletions cmd/updater/zz_appupgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,38 @@ func TestAutoUpdateEnabledGate(t *testing.T) {
t.Fatal("enabled:true must read as enabled")
}
}

// TestAppAutoUpgradeOptOut pins the PILOT_APP_UPDATE_OPT_OUT gate: app auto-upgrade is
// ON by default, off when the operator opts out, and back on when they opt in
// again. PILOT_UPDATER_NO_APP_UPGRADE is honored as a back-compat alias.
func TestAppAutoUpgradeOptOut(t *testing.T) {
// Default: both unset → enabled.
t.Setenv("PILOT_APP_UPDATE_OPT_OUT", "")
t.Setenv("PILOT_UPDATER_NO_APP_UPGRADE", "")
if !appAutoUpgradeEnabled() {
t.Fatal("default (both env vars unset) must enable app auto-upgrade")
}

// Opt out via PILOT_APP_UPDATE_OPT_OUT (each truthy spelling).
for _, v := range []string{"true", "1", "yes", "on", "TRUE"} {
t.Setenv("PILOT_APP_UPDATE_OPT_OUT", v)
if appAutoUpgradeEnabled() {
t.Errorf("PILOT_APP_UPDATE_OPT_OUT=%q must disable app auto-upgrade", v)
}
}

// Explicit opt-in values keep it enabled — this is what "switch back on".
for _, v := range []string{"false", "0", "", "no"} {
t.Setenv("PILOT_APP_UPDATE_OPT_OUT", v)
if !appAutoUpgradeEnabled() {
t.Errorf("PILOT_APP_UPDATE_OPT_OUT=%q must keep app auto-upgrade enabled", v)
}
}

// Back-compat alias still disables.
t.Setenv("PILOT_APP_UPDATE_OPT_OUT", "false")
t.Setenv("PILOT_UPDATER_NO_APP_UPGRADE", "1")
if appAutoUpgradeEnabled() {
t.Error("legacy PILOT_UPDATER_NO_APP_UPGRADE=1 must still disable app auto-upgrade")
}
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ require (
github.com/pilot-protocol/policy v0.2.2
github.com/pilot-protocol/rendezvous v0.2.5
github.com/pilot-protocol/runtime v0.3.1
github.com/pilot-protocol/skillinject v0.2.4-0.20260713095857-dc5109d7a33a
github.com/pilot-protocol/skillinject v0.2.4-0.20260717204101-902f745138da
github.com/pilot-protocol/trustedagents v0.2.4
github.com/pilot-protocol/updater v0.2.3
github.com/pilot-protocol/webhook v0.2.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ github.com/pilot-protocol/rendezvous v0.2.5 h1:PvApwKHU2DnvK8pA6K9RAohUomZNu6vX6
github.com/pilot-protocol/rendezvous v0.2.5/go.mod h1:nUPZaM1R1x0iEKbTp1TuYqro2wgBArwT0E4wauR8I2E=
github.com/pilot-protocol/runtime v0.3.1 h1:+W9ww0dZY/FgOBtCmIOV3w5L5Z4Upt/RIsrYElXZ1zs=
github.com/pilot-protocol/runtime v0.3.1/go.mod h1:GfFEIji0w7H9SSNR9Wl2q72pd2OYN3PHY9Qhcbvyrqk=
github.com/pilot-protocol/skillinject v0.2.4-0.20260713095857-dc5109d7a33a h1:MDblMoSuIXZ84um+AJt4vW3ebfBH2SG08b5o1SdGb1g=
github.com/pilot-protocol/skillinject v0.2.4-0.20260713095857-dc5109d7a33a/go.mod h1:+jEW+uCFkA6TnmZc41Ev5oczXCbOHD0NqVWG8RzjwXQ=
github.com/pilot-protocol/skillinject v0.2.4-0.20260717204101-902f745138da h1:supBO6UzykRn5Ta5pCLXjTvAPqTKOgfr2ZpZQugBNuI=
github.com/pilot-protocol/skillinject v0.2.4-0.20260717204101-902f745138da/go.mod h1:+jEW+uCFkA6TnmZc41Ev5oczXCbOHD0NqVWG8RzjwXQ=
github.com/pilot-protocol/trustedagents v0.2.4 h1:NqYjU3eoxBzyzzlhTQY2vV4q0l5+4eaADAhrOD1OkQU=
github.com/pilot-protocol/trustedagents v0.2.4/go.mod h1:Y3Eq/IOZqAUIbtzVBcxW4epkciaFAFAYkEyu/E7DXe4=
github.com/pilot-protocol/updater v0.2.3 h1:9+Y2XvyEnfxjbguho8AIKLdEK+Gzj2I7lkad2qIu6JY=
Expand Down
Loading