Is your feature request related to a problem? Please describe.
Running the go-ios tunnel agent (ios tunnel start, especially the upcoming --cloud mode) persistently on a host today means hand-writing and maintaining an OS service. On Linux that's a systemd unit in /etc/systemd/system, placing the binary somewhere stable, capturing the right flags + env (ORCHESTRATOR_URL, --userspace, pair-record path, working dir), setting a restart policy, and enable-ing it at boot. This is manual, easy to get wrong (env not inherited, wrong working dir, missing restart), and has to be repeated on every host.
That cost scales badly for the cloud/device-farm use case, where the agent is a fleet agent running on many device hosts. Onboarding a host should be one command, not a unit-file checklist. (We hit exactly this friction bringing up a host by hand — the service didn't inherit the shell env, the working dir/pair-record path mattered, etc.)
Describe the solution you'd like
A service group under the tunnel subcommand, modeled on cloudflared service install (and ngrok service install, gitlab-runner install):
ios tunnel service install [--cloud] [--userspace] [--user] [other tunnel flags]
ios tunnel service uninstall
ios tunnel service status # optional thin wrapper
install registers go-ios as a managed OS service that runs ios tunnel start …, capturing the current invocation's flags and relevant env (ORCHESTRATOR_URL, pair-record path, etc.) into the service definition so it reproduces a known-good config, sets a restart policy, and enables it at boot. Idempotent (re-running overwrites + re-enables).
uninstall stops, disables, and removes it.
Onboarding a cloud host then becomes:
ios login
ios tunnel service install --cloud --userspace
…and the host joins the fleet and stays joined across reboots.
Describe alternatives you've considered
- Document a systemd unit (status quo). Works, but manual and per-host; doesn't scale to a fleet and is the source of the friction above.
- Hand-rolled, systemd-only installer. Lowest dependency cost (we already know the exact unit), covers the Linux device hosts. Good MVP, but doesn't match the cross-platform precedents.
github.com/kardianos/service (used by gitlab-runner, telegraf, etc.) abstracts systemd / launchd / Windows-SCM behind one API. Matches cloudflared/ngrok cross-platform from day one. Dependency lives in the main application package, not the ios library, so the library stays dep-clean.
Recommendation: take the cloudflared route via kardianos/service for cross-platform install/uninstall, or — if we want to stay minimal first — ship systemd-only and add launchd/Windows later.
Additional context
Precedents (the fleet-agent / tunnel-daemon category conventionally self-installs): cloudflared (cloudflared service install), ngrok (ngrok service install), gitlab-runner (gitlab-runner install). Server software (Caddy/Consul/Vault/Prometheus) instead documents a unit — go-ios cloud mode is the former category.
Things to decide during implementation:
- Privilege. A system service needs root/admin. A
--userspace cloud agent doesn't need root to run, so offer --user (systemd --user / LaunchAgent) for the no-root case — noting that user services need lingering enabled to run headless without a login session. Default to the system service (robust for headless farm hosts), document the sudo requirement.
- Env capture. Bake
ORCHESTRATOR_URL (and any other needed env) into the service definition (Environment= on systemd) — a service that doesn't inherit the operator's shell env silently runs with the wrong config.
- Consistency with
ENABLE_GO_IOS_AGENT. go-ios already self-spawns a tunnel child process (RunAgent, "experimental daemon mode"). The service command should complement that managed-process story, not contradict it.
- No-systemd Linux (Alpine/OpenRC) — detect and fail with a clear message rather than writing a broken unit.
- go-ios does not currently install any OS service;
selfIdentity.plist is the only thing the binary writes itself.
Acceptance criteria (MVP):
🤖 Generated with Claude Code
Is your feature request related to a problem? Please describe.
Running the go-ios tunnel agent (
ios tunnel start, especially the upcoming--cloudmode) persistently on a host today means hand-writing and maintaining an OS service. On Linux that's a systemd unit in/etc/systemd/system, placing the binary somewhere stable, capturing the right flags + env (ORCHESTRATOR_URL,--userspace, pair-record path, working dir), setting a restart policy, andenable-ing it at boot. This is manual, easy to get wrong (env not inherited, wrong working dir, missing restart), and has to be repeated on every host.That cost scales badly for the cloud/device-farm use case, where the agent is a fleet agent running on many device hosts. Onboarding a host should be one command, not a unit-file checklist. (We hit exactly this friction bringing up a host by hand — the service didn't inherit the shell env, the working dir/pair-record path mattered, etc.)
Describe the solution you'd like
A
servicegroup under the tunnel subcommand, modeled oncloudflared service install(andngrok service install,gitlab-runner install):installregisters go-ios as a managed OS service that runsios tunnel start …, capturing the current invocation's flags and relevant env (ORCHESTRATOR_URL, pair-record path, etc.) into the service definition so it reproduces a known-good config, sets a restart policy, and enables it at boot. Idempotent (re-running overwrites + re-enables).uninstallstops, disables, and removes it.Onboarding a cloud host then becomes:
…and the host joins the fleet and stays joined across reboots.
Describe alternatives you've considered
github.com/kardianos/service(used by gitlab-runner, telegraf, etc.) abstracts systemd / launchd / Windows-SCM behind one API. Matches cloudflared/ngrok cross-platform from day one. Dependency lives in themainapplication package, not theioslibrary, so the library stays dep-clean.Recommendation: take the cloudflared route via
kardianos/servicefor cross-platform install/uninstall, or — if we want to stay minimal first — ship systemd-only and add launchd/Windows later.Additional context
Precedents (the fleet-agent / tunnel-daemon category conventionally self-installs): cloudflared (
cloudflared service install), ngrok (ngrok service install), gitlab-runner (gitlab-runner install). Server software (Caddy/Consul/Vault/Prometheus) instead documents a unit — go-ios cloud mode is the former category.Things to decide during implementation:
--userspacecloud agent doesn't need root to run, so offer--user(systemd--user/ LaunchAgent) for the no-root case — noting that user services need lingering enabled to run headless without a login session. Default to the system service (robust for headless farm hosts), document the sudo requirement.ORCHESTRATOR_URL(and any other needed env) into the service definition (Environment=on systemd) — a service that doesn't inherit the operator's shell env silently runs with the wrong config.ENABLE_GO_IOS_AGENT. go-ios already self-spawns a tunnel child process (RunAgent, "experimental daemon mode"). The service command should complement that managed-process story, not contradict it.selfIdentity.plistis the only thing the binary writes itself.Acceptance criteria (MVP):
ios tunnel service install [--cloud] [--userspace]creates + enables + starts a service that survives reboot on Linux/systemd.ORCHESTRATOR_URL.ios tunnel service uninstallfully removes it (stop, disable, delete).--useralternative.🤖 Generated with Claude Code