About join-tailnet
Join Tailnet is a helper skill for joining an exe.dev VM to the IV Tailscale tailnet. It temporarily attaches the required integration for the join and detaches it afterward, with checks for an existing attachment.
Key features
- 01Joins an exe.dev VM to the tailnet
- 02Checks for an existing integration attachment
- 03Attaches and detaches join credentials
- 04Starts tailscaled when needed
- 05Uses an ephemeral preauthorized key
Use cases
- 01Adding a VM to the IV Tailscale tailnet
- 02Joining a VM without a standing grant
- 03Retrying a join after an ephemeral node is removed
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ kylelundstedt/ dotfiles --skill join-tailnetAfter installing, type/join-tailnetin Claude Code to confirm it loaded.
Bundled filesjoin-tailnet.sh
Details
From the project READMEJoin Tailnet
Joins an exe.dev VM to the IV Tailscale tailnet.
Usage
Run the helper. It does everything, including attaching and detaching the credential:
~/.agents/skills/join-tailnet/join-tailnet.sh <vm-name>
Then verify the VM appears in tailscale status. After it joins, use
ssh <vm> (Tailscale SSH) for everything else.
Why the script, and not the raw commands
api-tailscale (formerly tailscale-api) is attached in two lanes (settled
2026-09-15; the contract lives in iv-provision tailnet.md):
- Private dev VMs carry a standing grant through the exe.dev tag
tailnet, because the browser-drivencreate-vmskill can only fix things atnewtime (its token cannot attach or detach). A dev VM is private, so this is the same exposure as the VM itself. - Anything internet-facing, and every prod-lane deployment target, gets a
time-boxed per-VM grant —
ssh exe.dev integrations attach api-tailscale vm:<vm> --for 30m— never the tag. This is what the 2026-07-28 remediation was about:auto:allhad put key-minting on the public-facingrss-feedandtelnyx-vm(token exchange fromrss-feedreturned HTTP 200), and thetailnettag had quietly put it back on them between 2026-08-19 and 2026-09-15. Removed again 2026-09-15 (tag -d <vm> tailneton the four public VMs).
This script is the third path — a one-off join from the mini for a VM that has neither: it attaches for the duration of the join and detaches after.
This is the control-plane variant: it needs exe.dev access to attach and
detach, which a VM does not have. The VM-side variant — for a VM that already
carries api-tailscale through the tailnet tag — is iv-provision's
skills-local/join-tailnet, which also covers the preflight check and why the
vendored tailscale skill's join advice does not apply on this fleet. Same
name, different callers; keep them separate rather than syncing one over the
other.
The script now:
- Checks whether
api-tailscaleis already attached to the VM. If so, it leaves the attachment exactly as found and skips step 4. - Attaches
api-tailscaletovm:<name>. - SSHes in over
*.exe.xyz, ensurestailscaledis running, exchanges an OAuth token through the proxy, mints a one-use ephemeral preauthorized key against the public API, and runstailscale up. - Detaches
api-tailscaleon exit — via a trap that fires on error and interrupt, not only on success.
So the authority exists only while it is being used. If a detach ever fails the
script warns loudly; ssh exe.dev integrations list will show the stray
vm: attachment.
Do not hand-run the old inline curl commands. Without an attachment the
token exchange returns no access_token, and the natural next move — attaching
api-tailscale by hand and forgetting to detach — is exactly the standing
authority this change removed.
Prerequisites
- The VM exists and is reachable at
<vm>.exe.xyz. curlandjqare present on the VM (stock exeuntu and iv-image both have them; a slim/exeslim base may not — check before relying on this skill there).tailscaledis enabled and started by the script. Stock exeuntu ships it disabled, so don't assume it is already running.- You are running from
klundstedt-mini, which owns exe.dev control-plane mutations.
Notes
- Keys are minted
ephemeral:true, so a node that goes offline long enough is removed from the tailnet and must be re-joined by re-running this script. For a long-lived appliance that must survive outages, run withIV_TAILSCALE_EPHEMERAL=false(added 2026-09-15 for the AgentsView collector rebuild): the node then persists, and retiring the VM must delete the node in the admin console (iv-provisionretiring.md§5). --tag=ivat VM creation is no longer sufficient or required for tailnet joining. It still governs othertag:ivintegrations.- Prod-lane images self-join at boot (since exeslim 2026-08-23). The
exeslim(notexeslim-dev) image shipsiv-tailnet-join.service: at first boot it probes theapi-tailscaleproxy and, if attached, mints atag:prodkey and joins.api-tailscaleis attached to the exe.dev tagtailnet, sonew --tag=tailneton an exeslim VM means it is on the tailnet astag:prodseconds after boot, and this helper then exits early ("already on the tailnet"). To gettag:devon such a VM:sudo tailscale logouton it, then run this helper. Seen oniv-llm-relay2026-09-15. - Prod-lane nodes are minted non-ephemeral since exeslim 2026-09-15, so an
appliance never needs the API after its first boot; an older ephemeral prod
node that gets reaped after a long outage needs a 30-minute attach and
systemctl start iv-tailnet-joinover the.exe.xyzedge (or this script, which would make ittag:dev).
SSH discipline
- One SSH attempt at a time. Never launch parallel SSH to
*.exe.xyz. - If SSH fails, wait 30–60s before one more attempt.