Skip to content

feat(appstore): pilotctl install --local for sideloaded apps#240

Merged
TeoSlayer merged 4 commits into
mainfrom
feat/sideload-install-local
Jun 9, 2026
Merged

feat(appstore): pilotctl install --local for sideloaded apps#240
TeoSlayer merged 4 commits into
mainfrom
feat/sideload-install-local

Conversation

@TeoSlayer

Copy link
Copy Markdown
Owner

Summary

Pairs with pilot-protocol/app-store#15. Adds the user-facing path for installing apps that did not come through the signed catalogue.

  • pilotctl appstore install <path>refused with a hint telling the user about --local and what it does/doesn't protect.
  • pilotctl appstore install <path> --local → installs as a sideload: runs manifest.EnforceSideloadPolicy(m) at install time, plants .sideloaded (mode 0o400) in the staged dir before the atomic rename, prints the honest "manifest gate, not OS sandbox" caveat.
  • pilotctl appstore install <id> (catalogue ID) → unchanged.

The internal refactor: resolveInstallTarget now returns an installSource tag (catalogue|local) so the install command can branch on trust regime without re-running the catalogue lookup.

appstore list shows a [sideloaded] badge on the version line and a sideloaded field in JSON output.

Test plan

  • go test ./cmd/pilotctl -count=1 -short — green
  • Existing happy-path/duplicate/text-mode tests updated to pass --local (they install from local bundle dirs); the text-mode test now asserts the SIDELOADED warning is present, so removing that caveat surfaces as a regression
  • Live smoke: synthetic bundle accepted with --local, refused without (caught by os.Exit(1)); policy violation (added net.dial) refused at install time with the offending cap named
  • Reviewer: confirm validManifestJSON move from /tmp/data\$APP/data doesn't break any downstream test I missed (full pilotctl suite passes locally)

Dep bump

Go.mod pin moves to the SHA on app-store#15's feat branch. Will rebase the pin to a tagged release once that PR merges.

Pairs with pilot-protocol/app-store#15. Local-path installs now
require an explicit --local flag and produce a `.sideloaded` marker
in the install dir; the supervisor uses that marker to skip
publisher-signature verification and enforce the sideload allow-list.

Behaviour changes:

  - `pilotctl appstore install <path>`                  → refused
  - `pilotctl appstore install <path> --local`          → sideload
  - `pilotctl appstore install <id>`  (catalogue ID)    → unchanged

When --local is passed:

  - manifest.EnforceSideloadPolicy(m) runs before staging; any
    grant outside the allow-list refuses the install up-front so
    users get a clear error instead of a silent supervisor-skip
    after the next rescan
  - `.sideloaded` is planted in the staging dir as mode 0o400 BEFORE
    the atomic rename, so there is no window where the dir appears
    under InstallRoot without the marker
  - install output prints the honest "manifest gate, not OS sandbox"
    caveat so users know what `--local` does and does not protect

`appstore list` surfaces a `[sideloaded]` badge on the version line
and adds the `sideloaded` field to the JSON output.

A small internal refactor: resolveInstallTarget now returns an
installSource tag (catalogue|local) so the install command can
branch on trust regime without re-doing catalogue lookup.

Test updates:

  - validManifestJSON now uses fs.read $APP/data instead of /tmp/data
    so the helper-built bundles satisfy the sideload policy and can
    be used in both the catalogue and sideload test paths
  - install/duplicate/text-mode tests pass --local since they install
    from a local bundle directory
  - SIDELOADED warning text is asserted in the text-mode install test
    so removing the warning would surface as a regression

Bumps the app-store dep to the feat/sideload-policy SHA. Will rebase
the version pin once that PR merges and a tagged release lands.
@TeoSlayer TeoSlayer requested a review from Alexgodoroja as a code owner June 9, 2026 02:00
teovl added 2 commits June 9, 2026 05:02
Splits the install command into two lines in the appstore help —
catalogue install vs sideload — so users discover --local from
`pilotctl appstore` rather than via the trial-and-error of a
local-path install that gets refused.
Two pre-existing bugs were masking each other in scripts/gen-cli-reference.sh:

1. \`pilotctl --help\` writes its banner to stderr (so that the same
   text shows for \`pilotctl <bad-flag>\`). The script only redirected
   stdout, so the help block was always empty.

2. \`pilotctl --help\` exits with code 2 by Go's flag convention. Under
   \`set -euo pipefail\` that aborted the whole script before the diff
   step ran.

Net effect: the workflow never modified docs/cli-reference.md, so the
\`git diff --exit-code\` step trivially passed. Any PR that touches
\`cmd/pilotctl/**\` triggered the gen step, hit the same exit-2 abort,
and \"passed\" for the wrong reason. PR #237/#238/#239 (catalogue-only
bumps) skipped the gate entirely because they don't match the path
filter, but the run history on main shows the check has been failing
silently on every code change since the workflow was added.

Fix:
  - 2>&1 so stderr lands in the doc
  - `|| true` so a non-zero exit doesn't abort the script

No content change to docs/cli-reference.md — the current committed
version already matches what the fixed script produces.
Bumps github.com/pilot-protocol/app-store from the feat/sideload-policy
SHA to the post-merge main SHA (#15 squash-merged as 8852c785).
No code changes — the manifest API + supervisor scan code that ships
in v1.0.1-beta.1.0.20260609061942-8852c785a264 is byte-equivalent to
what was on the feat branch.
@TeoSlayer TeoSlayer merged commit 8c63d20 into main Jun 9, 2026
6 of 9 checks passed
@TeoSlayer TeoSlayer deleted the feat/sideload-install-local branch June 9, 2026 06:25
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