Configure Jetson Orin with Tailscale client connecting to Headscale coordination server at tailscale.vayrette.com:8180. Device registers as 'saltylab-orin' with persistent auth key for unattended login. Features: - systemd auto-start and restart on WiFi drops - Persistent auth key storage at /opt/saltybot/tailscale-auth.key - SSH + HTTP access over Tailscale tailnet (encrypted WireGuard) - IP forwarding enabled for relay/exit node capability - WiFi resilience with aggressive restart policy - MQTT reporting of VPN status, IP, and connection type Components added: - jetson/scripts/setup-tailscale.sh: Tailscale package installation - jetson/scripts/headscale-auth-helper.sh: Auth key management utility - jetson/systemd/tailscale-vpn.service: systemd service unit - jetson/docs/headscale-vpn-setup.md: Comprehensive setup documentation - saltybot_cellular/vpn_status_node.py: ROS2 node for MQTT reporting Updated: - jetson/systemd/install_systemd.sh: Include tailscale-vpn.service - jetson/scripts/setup-jetson.sh: Add Tailscale setup steps Access patterns: - SSH: ssh user@saltylab-orin.tail12345.ts.net - HTTP: http://saltylab-orin.tail12345.ts.net:port - Direct IP: 100.x.x.x (Tailscale allocated address) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
7.4 KiB
Headscale VPN Auto-Connect Setup — Jetson Orin
This document describes the auto-connect VPN setup for the Jetson Orin using Tailscale client connecting to the Headscale server at tailscale.vayrette.com:8180.
Overview
Device Name: saltylab-orin
Headscale Server: https://tailscale.vayrette.com:8180
Primary Features:
- Auto-connect on system boot
- Persistent auth key for unattended login
- SSH + HTTP over Tailscale (tailnet)
- WiFi resilience and fallback
- systemd auto-restart on failure
Architecture
Components
-
Tailscale Client (
/usr/sbin/tailscaled)- VPN daemon running on Jetson
- Manages WireGuard tunnels
- Connects to Headscale coordination server
-
systemd Service (
tailscale-vpn.service)- Auto-starts on boot
- Restarts on failure
- Manages lifecycle of tailscaled daemon
- Logs to journald
-
Auth Key Manager (
headscale-auth-helper.sh)- Generates and validates auth keys
- Stores keys securely at
/opt/saltybot/tailscale-auth.key - Manages revocation
-
Setup Script (
setup-tailscale.sh)- One-time installation of Tailscale package
- Configures IP forwarding
- Sets up persistent state directories
Installation
1. Run Jetson Setup
sudo bash jetson/scripts/setup-jetson.sh
This configures the base system (Docker, NVMe, power mode, etc.).
2. Install Tailscale
sudo bash jetson/scripts/setup-tailscale.sh
This:
- Installs Tailscale from official Ubuntu repository
- Creates state/cache directories at
/var/lib/tailscale - Enables IP forwarding
- Creates environment config at
/etc/tailscale/tailscale-env
3. Generate and Store Auth Key
sudo bash jetson/scripts/headscale-auth-helper.sh generate
This prompts you to enter a pre-authorized key (preauthkey) from the Headscale server.
To get a preauthkey from Headscale:
# On Headscale server
sudo headscale preauthkeys create --expiration 2160h --user default
# Copy the generated key (tskey_...)
Then paste into the helper script when prompted.
The key is stored at: /opt/saltybot/tailscale-auth.key
4. Install systemd Services
sudo bash jetson/systemd/install_systemd.sh
This:
- Deploys repo to
/opt/saltybot/jetson - Installs
tailscale-vpn.serviceto/etc/systemd/system/ - Enables service for auto-start
5. Start the VPN Service
sudo systemctl start tailscale-vpn
Check status:
sudo systemctl status tailscale-vpn
sudo journalctl -fu tailscale-vpn
sudo tailscale status
Usage
Check VPN Status
sudo tailscale status
Example output:
saltylab-orin 100.x.x.x juno linux -
100.x.x.x is local IP address
100.x.x.x is remote IP address
Access via SSH over Tailnet
From any machine on the tailnet:
ssh <username>@saltylab-orin.tail12345.ts.net
Or use the IP directly:
ssh <username>@100.x.x.x
Access via HTTP
If running a web service (e.g., ROS2 visualization):
http://saltylab-orin.tail12345.ts.net:8080
# or
http://100.x.x.x:8080
View Logs
# Real-time logs
sudo journalctl -fu tailscale-vpn
# Last 50 lines
sudo journalctl -n 50 -u tailscale-vpn
# Since last boot
sudo journalctl -b -u tailscale-vpn
WiFi Resilience
The systemd service is configured with aggressive restart policies to handle WiFi drops:
Restart=always
RestartSec=5s
StartLimitInterval=60s
StartLimitBurst=10
This means:
- Automatically restarts after WiFi drops
- Waits 5 seconds between restart attempts
- Allows up to 10 restarts per 60-second window
The daemon will continuously attempt to reconnect when the primary network is unavailable.
Persistent Storage
Auth Key
Location: /opt/saltybot/tailscale-auth.key
Permissions: 600 (readable only by root)
Ownership: root:root
If the key file is missing on boot:
- The systemd service will not auto-authenticate
- Manual login is required:
sudo tailscale login --login-server=https://tailscale.vayrette.com:8180 - Re-run
headscale-auth-helper.sh generateto store the key
State Directory
Location: /var/lib/tailscale/
Contents:
- Machine state and private key
- WireGuard configuration
- Connection state
These are persisted so the device retains its identity and tailnet IP across reboots.
Troubleshooting
Service Won't Start
sudo systemctl status tailscale-vpn
sudo journalctl -u tailscale-vpn -n 30
Check for:
- Missing auth key:
ls -la /opt/saltybot/tailscale-auth.key - Tailscale package:
which tailscale - Permissions:
ls -la /var/lib/tailscale
Can't Connect to Headscale Server
sudo tailscale debug derp
curl -v https://tailscale.vayrette.com:8180
Check:
- Network connectivity:
ping 8.8.8.8 - DNS:
nslookup tailscale.vayrette.com - Firewall: Port 443 (HTTPS) must be open
Auth Key Expired
If the preauthkey expires:
# Get new key from Headscale server
# Then update:
sudo bash jetson/scripts/headscale-auth-helper.sh revoke
sudo bash jetson/scripts/headscale-auth-helper.sh generate
sudo systemctl restart tailscale-vpn
Can't SSH Over Tailnet
# Verify device is in tailnet:
sudo tailscale status
# Check tailnet connectivity from another machine:
ping <device-ip>
# SSH with verbose output:
ssh -vv <username>@saltylab-orin.tail12345.ts.net
Memory/Resource Issues
Monitor memory with Tailscale:
ps aux | grep tailscaled
sudo systemctl show -p MemoryUsage tailscale-vpn
Tailscale typically uses 30-80 MB of RAM depending on network size.
Integration with Other Services
ROS2 Services
Expose ROS2 services over the tailnet by ensuring your nodes bind to:
0.0.0.0for accepting all interfaces- Or explicitly bind to the Tailscale IP (
100.x.x.x)
Docker Services
If running Docker services that need tailnet access:
# In docker-compose.yml
services:
app:
network_mode: host # Access host's Tailscale interface
Security Considerations
-
Auth Key: Stored in plaintext at
/opt/saltybot/tailscale-auth.key- Use file permissions (
600) to restrict access - Consider encryption if sensitive environment
- Use file permissions (
-
State Directory:
/var/lib/tailscale/contains private keys- Restricted permissions (700)
- Only readable by root
-
SSH Over Tailnet: No ACL restrictions by default
- Configure Headscale ACL rules if needed:
headscale acl
- Configure Headscale ACL rules if needed:
-
Key Rotation: Rotate preauthkeys periodically
- Headscale supports key expiration (set to 2160h = 90 days default)
Maintenance
Update Tailscale
sudo apt-get update
sudo apt-get install --only-upgrade tailscale
sudo systemctl restart tailscale-vpn
Backup
Backup the auth key:
sudo cp /opt/saltybot/tailscale-auth.key ~/tailscale-auth.key.backup
Monitor
Set up log rotation to prevent journal bloat:
# journald auto-rotates, but you can check:
journalctl --disk-usage
MQTT Reporting
The Jetson reports VPN status to the MQTT broker:
saltylab/jetson/vpn/status -> online|offline|connecting
saltylab/jetson/vpn/ip -> 100.x.x.x
saltylab/jetson/vpn/hostname -> saltylab-orin.tail12345.ts.net
This is reported by the cellular/MQTT bridge node on boot and after reconnection.