Add 'ios tunnel service install/uninstall/status' to run the tunnel agent as a managed OS service - #811
Add 'ios tunnel service install/uninstall/status' to run the tunnel agent as a managed OS service#811danielpaulus wants to merge 2 commits into
Conversation
…ed OS service Register the tunnel agent (ios tunnel start) as a managed OS service the way 'cloudflared service install' does, so onboarding a device host is one command instead of a hand-written unit-file checklist. - New 'ios tunnel service (install | uninstall | status)' command group in package main (cmd_tunnel_service.go) using github.com/kardianos/service; the dependency stays out of the ios library packages. - install captures the current invocation's flags (--pair-record-path resolved to an absolute path, --userspace, --udid, --tunnel-info-host/port) and the ORCHESTRATOR_URL / GO_IOS_AGENT_HOST environment into the service definition, sets Restart=always, enables at boot, and starts the service. Re-running install replaces the existing definition (idempotent). - uninstall stops, disables, and removes the service; uninstalling a missing service is not an error. status reports installed/running state. - systemd-first on Linux: any other init system fails with a clear message instead of writing an untested script. --user installs a systemd --user unit / launchd LaunchAgent for the no-root (--userspace) case; permission errors are decorated with sudo/--user guidance. - Dispatched as a global command (no device/usbmuxd needed) before the device commands so the install/uninstall literals don't collide with app (un)install. - Unit tests: golden-file coverage of the flag+env capture, and fake-injected coverage of the install/uninstall flows (no systemd, insufficient privileges, idempotent reinstall). No service is registered by tests. Fixes #750 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
|
/test-devices |
|
❌ Real-device tests failed — see run. |
… usbmuxd env Windows: the installed service runs `ios tunnel start` directly, but a Windows service must connect back to the service-control dispatcher (svc.Run) on start. The tunnel agent never does, so the SCM would kill it with error 1053. Reject Windows in checkServiceSystem with an actionable message instead of registering a service that can never start; docs/help no longer advertise Windows. Env capture: add GO_IOS_AGENT_PORT (partner of the already-captured GO_IOS_AGENT_HOST; both select the HTTP API bind host:port) and USBMUXD_SOCKET_ADDRESS to the allowlist so a non-default agent port or usbmuxd socket is reproduced by the service instead of silently falling back to defaults. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
Adversarial review (round 2)Reviewed config-capture fidelity, install/uninstall state machine, kardianos platform quirks, and dispatch ordering. Verified behavior at runtime on macOS (launchd) and by inspecting the kardianos v1.3.0 templates. No real services were left installed. Confirmed defects — fixed in 45c8eef
Verified correct (dismissed)
Acceptance criteria
|
Problem
Running the tunnel agent (
ios tunnel start, and the upcoming--cloudfleet-agent mode) persistently on a host means hand-writing an OS service today: a systemd unit, the right flags and env (ORCHESTRATOR_URL,--userspace, pair-record path, working dir), a restart policy, andenable-ing it at boot. That is manual, easy to get wrong (env not inherited, wrong working dir, missing restart), and has to be repeated on every device host. Issue #750 asks for a cloudflared-styleservicegroup so onboarding a host is one command.Design
ios tunnel start …:--pair-record-path(resolved to an absolute path — relative paths are anchored to the install-time cwd, thedefaultliteral is passed through),--userspace,--udid(flag orGO_IOS_UDID),--tunnel-info-host/port;Environment=on systemd /EnvironmentVariableson launchd):ORCHESTRATOR_URL,GO_IOS_AGENT_HOST— a service does not inherit the operator's shell env, so a known-good config must be captured explicitly;Restart=always, enabled at boot, started immediately.--user).--userinstalls a systemd--userunit / launchd LaunchAgent for the no-root--userspacecase; docs note theloginctl enable-lingerrequirement for headless hosts. Installing a user service without--userspacelogs a warning (the kernel tunnel needs root).install/uninstallliterals don't collide with app (un)install.github.com/kardianos/servicedependency lives only in package main; theioslibrary packages stay dependency-clean.Implementation
cmd_tunnel_service.go— config capture (tunnelServiceConfig), install/uninstall flows against a smallserviceControllerinterface (satisfied byservice.Service, faked in tests), init-system gate, privilege-error decoration, command runner and output (JSON by default,--nojsonhuman-readable).cmd_global.go— registerstunnel serviceinglobalCommands.main.go— docopt usage line + detailed help text (sudo requirement,--useralternative, lingering note).internal/clihelp/help.yaml,testdata/help/global.golden,README.md— help catalog entry and docs.go.mod/go.sum—github.com/kardianos/service v1.3.0(root module only).Options considered
github.com/kardianos/service(preferred) — one API over systemd/launchd/Windows SCM, battle-tested (gitlab-runner, telegraf), matches the cloudflared/ngrok precedent cross-platform from day one, and the dependency is confined to package main. We still gate Linux to systemd so we never ship an untested OpenRC/SysV script.Test plan
TestTunnelServiceConfigCapture— golden files for flag+env capture: defaults, cloud/userspace/user, per-device agent,GO_IOS_UDIDfallback,defaultpair-record literal (testdata/tunnelservice/*.golden).TestInstallTunnelServiceNoSystemd), permission errors with sudo/--userhints (TestInstallTunnelService{InsufficientPrivileges,UserServicePrivilegeError}), idempotent reinstall ordering stop→uninstall→install→start (TestInstallTunnelServiceIdempotent), uninstall incl. not-installed (TestUninstallTunnelService*), dispatch matcher (TestIsTunnelServiceCommand).go build ./...,go test ./..., andgofmt -lclean; docopt parsing of the new usage line (incl.--uservs--userspace) verified.ios tunnel service status(reportsnot installedvia launchd) andios help tunnel service.Fixes #750
🤖 Generated with Claude Code
https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk