Skip to content

Tailscale-Based Device Onboarding

The Modern Way: Use Tailscale to bootstrap new devices from any existing device on your private network.


🏗️ Architecture

The onboarding flow uses a temporary local web server to "serve" the encrypted bundles across the Tailnet.

Main Device (Source)          New Device (Target)
--------------------          -------------------
1. Run ./serve-via-tailscale.sh
2. Python Server starts       3. Install Tailscale
3. Serves .age bundles  <---  4. Curl bundle
                              5. Decrypt & Run Setup

🚀 Step-by-Step Setup

1. On Existing Device (Source)

Start the bootstrap server on your primary machine:

cd ~/coder-core/.bootstrap
./serve-via-tailscale.sh

This will print a specialized URL and curl command for you to use.

2. On New Device (Target)

Automatic Discovery (Recommended): If you have coder-core or can copy one script:

# Run the discovery script
./setup-new-device.sh

Manual (No pre-existing files): If the device is completely fresh, manually run:

# 1. Install Tailscale & age
brew install tailscale age
sudo tailscale up

# 2. Pull and execute the bundle
# (Replace IP with the one shown on your source device)
curl http://100.x.y.z:8080/device-bootstrap.age | age -d | bash

✅ What is Automated?

Once the bash command runs, the following is performed automatically:

  • SSH Setup: Installs private/public keys and configures ~/.ssh/config.
  • Infisical CLI: Installs the CLI and logs in using Machine Identity.
  • Repository Setup: Either clones coder-core or restores it from the embedded backup.
  • Verification: Tests connections to bruno, dev-vps, and Infisical.

🔒 Security Advantages

  • No USB Required: Everything happens over the encrypted WireGuard tunnel.
  • Private IP only: The bootstrap server binds ONLY to the Tailscale interface (100.x.y.z).
  • Encrypted Payloads: Even if someone were on your Tailnet, they would still need the age passphrase to read the bundle.

❓ Troubleshooting

Cannot reach IP:

  • Ensure both devices are connected to the same Tailnet.
  • Check if a local firewall (like ufw or macOS Firewall) is blocking port 8080.

"age: command not found":

  • The script attempts to install age via brew, but if you don't have Homebrew, you must install it manually first.