Skip to content

feat(setup): kontext setup / --uninstall for self-serve managed observe#277

Open
michiosw wants to merge 1 commit into
selfserve-paths-foundationsfrom
selfserve-setup-command
Open

feat(setup): kontext setup / --uninstall for self-serve managed observe#277
michiosw wants to merge 1 commit into
selfserve-paths-foundationsfrom
selfserve-setup-command

Conversation

@michiosw

@michiosw michiosw commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

What & why

kontext setup / kontext setup --uninstall (ENG-442) — the self-serve install path that replaces pkg+MDM. Same managed-observe daemon, same Authorization: Bearer ingest as enterprise; only the provisioning differs (the user pastes a dashboard key instead of MDM injecting it). Builds on #276.

setup does, in order: darwin-only gate -> refuse if any KONTEXT_MANAGED_CONFIG is set or a system /Library config exists ("managed by your IT admin") -> validate the token via GET /ping (learns the org id; fails fast on a bad/revoked key) -> write the keychain item via /usr/bin/security -i stdin (never argv) and read it back through the daemon's own path so a write/read mismatch fails here, not silently under launchd -> write user managed.json (org id from ping) -> ensure installation identity -> merge ~/.claude/settings.json hooks -> install + bootstrap + kickstart the LaunchAgent -> probe the socket. --uninstall reverses it (bootout by plist path, remove hooks keeping foreign + Guard hooks, delete keychain item, remove managed.json) and keeps installation.json so re-running setup reuses the same device identity.

The token is validated raw (no trimming): leading/trailing whitespace or control chars fail loudly rather than being silently rewritten, so the stored credential is byte-identical to the dashboard's and can never smuggle a second line into the security -i stream.

How to review / test — without brew (no CLI release needed)

The logic is fully unit-tested behind seams (execCommand/readPassword/isTerminal/keychain/launchctl/systemConfigPath/goos are function vars), so you can verify the whole flow with no real keychain, launchctl, or network:

go test ./internal/setup/...    # ping httptest, refusal cases (env + MDM), full artifact
                                 # assertions, idempotent re-run, uninstall inverse + identity kept,
                                 # plist golden + XML escaping, token-shape rejection

To exercise the real daemon path against the local stack — build from the branch and run the binary directly (this is the brew-less equivalent of the published flow):

go build -o /tmp/kontext-review ./cmd/kontext     # do NOT use /tmp/kontext (collides with runtimehost)
KONTEXT_MANAGED_ALLOW_HTTP_LOCALHOST=1 /tmp/kontext-review setup \
  --cloud-url http://localhost:4000 --token <key minted in the dashboard>
# assert: keychain read-back ok, ~/.claude/settings.json hooks merged, LaunchAgent running,
# a batch lands in the dashboard with device label = ComputerName and version cli-<v>.
# Run it twice (idempotent), then: /tmp/kontext-review setup --uninstall  (clean; installation.json kept)

Note: a machine with an existing /Library managed.json will (correctly) refuse — move it aside to test the happy path.

Security notes for the reviewer

  • Token never reaches argv/logs/plist; the security -i quoting is injection-safe for everything that passes the shape check.
  • managed.json is 0600; settings.json writes preserve mode; the keychain delete loop purges only same-service items before writing exactly one.
  • Refuses every env override and every MDM-managed Mac, so self-serve state can never shadow an org-managed install.

Part of the ENG-442 kontext-cli stack: #276 -> #277 -> #278.

michiosw commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

@greptile-apps

greptile-apps Bot commented Jun 13, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a self-serve macOS setup and uninstall flow for managed observe. The main changes are:

  • Adds the top-level kontext setup command and setup flags.
  • Validates install tokens, stores them in the login keychain, and writes user managed config.
  • Installs Claude Code hooks and a user LaunchAgent for the managed observe daemon.
  • Adds uninstall cleanup for LaunchAgent, hooks, keychain token, and user managed config while keeping device identity.
  • Adds daemon scope checking so a self-serve agent parks when a higher-priority managed config appears.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Reviews (3): Last reviewed commit: "feat(setup): kontext setup / --uninstall..." | Re-trigger Greptile

Comment thread internal/setup/launchagent.go Outdated
Comment thread internal/setup/uninstall.go Outdated
Comment thread internal/setup/keychain.go Outdated
Comment thread internal/setup/launchagent.go
@michiosw michiosw force-pushed the selfserve-paths-foundations branch from 1ba7ba8 to 55ae7c5 Compare June 13, 2026 09:34
@michiosw michiosw force-pushed the selfserve-setup-command branch from 3acdc06 to 4c34bc7 Compare June 13, 2026 09:34

Copy link
Copy Markdown
Contributor Author

@greptileai

@michiosw michiosw requested a review from hasandemirkiran June 13, 2026 11:21
Comment thread internal/setup/launchagent.go Outdated
brew-installed Macs connect to an org without MDM:

- validates the dashboard-minted install token against
  GET /api/v1/authorization-ledger/ping before writing any state
- stores the raw token in the login keychain via 'security -i' stdin
  (never argv; go-keyring unusable — its darwin Set base64-prefixes the
  value the daemon reads raw), deletes stale same-service items first,
  and verifies via the daemon's actual read path
- writes user-scope managed.json (round-tripped through the daemon's
  parser) + installation identity; device label from scutil ComputerName
- merges the five managed hooks into ~/.claude/settings.json (backup,
  foreign content preserved)
- renders a user LaunchAgent (KeepAlive, 30s throttle, logs under
  ~/Library/Logs/Kontext) and bootout/bootstrap/kickstarts it in the
  gui domain — no sudo anywhere
- refuses MDM-managed Macs (system /Library config always outranks user
  scope, so setup there would produce ignored artifacts)
- --uninstall reverses everything but keeps installation.json so a
  re-setup reports the same endpoint

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@michiosw michiosw force-pushed the selfserve-paths-foundations branch from 55ae7c5 to 8d585d0 Compare June 13, 2026 11:30
@michiosw michiosw force-pushed the selfserve-setup-command branch from 4c34bc7 to 706dd46 Compare June 13, 2026 11:30
@michiosw

Copy link
Copy Markdown
Contributor Author

@greptileai

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.

1 participant