Join the tailnet from a cidata tailscale_authkey - #97
Conversation
A tailscale_authkey file on the autoinstall drive gets the installed machine onto the tailnet on its own: the tailscale package is bundled in the offline mirror and installed only when the key is present, the key is staged root-only at /etc/tailscale/authkey, and a oneshot first-boot unit runs tailscale up once the network is really there. On success the key is deleted and the unit disables itself; on failure both survive to retry on the next boot, so an offline install joins whenever it first gets connectivity. ufw allows traffic in on tailscale0 via the same write-the-rule-then-ignore-the-chroot-exit-code dance as the SSH phase. Bundling rather than fetching at first boot is deliberate: pacman -Sy tailscale on a fresh machine is a partial upgrade, and pacman -Syu means an unattended full system upgrade racing the user's first login. The offline mirror is a coherent snapshot; installing from it keeps the target consistent and the install offline-capable. See plans/tailscale.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xc74h4WSYpuYPGuYbFAR1q
|
@copilot Review. |
The implementation is clean and well-structured. Here's the review: Correctness
Retry logic in the unit Hostname Tests No issues to address — the PR is ready. |
Review caught that the join unit was Type=oneshot wanted by multi-user.target: target units implicitly gain After= for their Wants=, so an offline machine's boot -- SDDM included -- stalled for the full 10-minute timeout, and once timed out the join never retried within that boot. Type=simple counts as started the moment it forks, so boot proceeds while the join retries in the background for as long as the boot lasts. Cleanup moves inside the script, sequenced after tailscale up succeeds, which preserves the retry-across-boots behavior: key and unit survive a boot with no connectivity, and the key never outlives a successful join. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xc74h4WSYpuYPGuYbFAR1q
There was a problem hiding this comment.
Pull request overview
Adds optional Tailscale autoinstall support: if a tailscale_authkey file is present on the cidata drive, the installer stages a first-boot join (including UFW allowance) and installs the tailscale package from the ISO’s offline mirror, keeping stock installs unchanged.
Changes:
- Add
tailscale_authkeyas an optionalcidatainput, plumb it through the install wrapper/env/context, and installtailscaleonly when the key is present. - Stage
/etc/tailscale/authkey, enabletailscaled, install a first-boot join unit, and add a UFW rule fortailscale0. - Add unit tests for
configure_tailscale, extendcidataload tests, and document the feature (README + plan).
Reviewed changes
Copilot reviewed 9 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
configs/airootfs/usr/share/omarchy-iso/orchestrator/phases_impl.py |
Installs tailscale conditionally and implements configure_tailscale (key staging, unit install, UFW rule validation). |
configs/airootfs/usr/share/omarchy-iso/orchestrator/main.py |
Registers the new “Configuring Tailscale” phase in the orchestrator sequence. |
configs/airootfs/usr/share/omarchy-iso/orchestrator/context.py |
Adds tailscale_authkey_path sourced from OMARCHY_INSTALL_TAILSCALE_AUTHKEY_FILE via _optional_path. |
configs/airootfs/usr/local/bin/omarchy-iso-install |
Adds --tailscale-authkey-file flag mapping to env for the orchestrator. |
configs/airootfs/usr/local/bin/omarchy-cidata-load |
Copies tailscale_authkey from cidata when present. |
configs/airootfs/root/.automated_script.sh |
Always passes --tailscale-authkey-file /root/tailscale_authkey (no-op when absent). |
builder/archinstall.packages |
Adds tailscale to the bundled/offline mirror package set. |
test/test_configure_tailscale.py |
New unit tests for the configure_tailscale phase behavior and failure modes. |
test/cidata-load-test.sh |
Extends cidata load coverage to include the optional tailscale_authkey file. |
README.md |
Documents the new optional tailscale_authkey autoinstall input and behavior. |
plans/tailscale.md |
Adds the design/acceptance criteria write-up for the feature. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # `tailscale up` needs a running tailscaled and there is no systemd in the | ||
| # chroot, so the install only stages: the key, the enabled services, and a | ||
| # oneshot first-boot unit that performs the join once the network is really | ||
| # there. The package itself was installed from the offline mirror during | ||
| # arch_install_system -- nothing is fetched at boot. |
| | `user_email_address.txt` | No | Git email | | ||
| | `user_encrypt_installation.txt` | No | `true` when `user_configuration.json` carries a `disk_encryption` block; defaults to false | | ||
| | `authorized_keys` | No | SSH public keys in sshd's own format, one per line | | ||
| | `tailscale_authkey` | No | Tailscale auth key; the machine joins your tailnet on first boot | |
The consumer-secure-boot plan now reflects the current repo: installs already boot mkinitcpio-built UKIs via Limine with pacman hooks regenerating them, so the remaining work is signing, not UKI plumbing. The Microsoft shim submission is called out as the calendar-bound critical path, and DKMS/NVIDIA module signing is promoted to a v1 blocker. BitLocker guidance matches the shipped decrypt-not-suspend policy from #105. Removed plans that have shipped: tailscale (#97), autoinstall, oem-install (#98/#107), dual-boot, and protected-partition-install (dual-boot half shipped, restore half superseded by factory snapshots). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N1HofkzWQwCMZSYAxm1tGe
What
A
tailscale_authkeyfile on the autoinstall drive gets the installed machine onto the tailnet on its own. Combined withauthorized_keys, this completes the disposable-VM story: create VM, boot, walk away,ssh user@machineover the tailnet from anywhere.How
tailscalerides in the offline mirror (~10MB on the ISO, added viabuilder/archinstall.packages) and is installed during the ordinary package phase — but only when the key file is present, so a stock install stays stock. Fetching at first boot was rejected:pacman -Sy tailscaleis a partial upgrade, andpacman -Syu tailscalemeans an unattended full system upgrade racing the user's first login. The offline mirror is a coherent snapshot; installing from it keeps the target consistent and the install offline-capable.tailscale upneeds a running tailscaled and there is no systemd in the chroot, so the installer only stages: the key at/etc/tailscale/authkey(root,0600),tailscaled.serviceenabled, and a backgroundomarchy-tailscale-join.service(Type=simple— a oneshot wanted by multi-user.target would hold the whole boot hostage, since target units implicitly gainAfter=for theirWants=) that retriestailscale upuntil the network is really there (network-online.targetcan be reached before real connectivity exists). Cleanup is sequenced inside the script aftertailscale upsucceeds: the key is deleted and the unit disables itself; on a boot with no connectivity both survive, so a machine installed offline joins on the first boot that has connectivity.ufw allow in on tailscale0, the same write-the-rule-then-ignore-the-chroot-exit-code dance asconfigure_ssh_access— without it the node joins and is then unreachable over the tailnet.user_configuration.json; nothing extra to configure. An empty or ambiguous key file fails the install loudly, as does a target without the tailscale binary (an ISO built before the package was bundled).Full rationale in
plans/tailscale.md.Testing
./test/allpasses: newtest_configure_tailscale.pymirrors the SSH phase tests (staging, permissions, unit content, ufw chroot behavior, loud failures), andcidata-load-test.shcovers the new optional file.🤖 Generated with Claude Code
https://claude.ai/code/session_01Xc74h4WSYpuYPGuYbFAR1q