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>
341 lines
7.4 KiB
Markdown
341 lines
7.4 KiB
Markdown
# 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
|
|
|
|
1. **Tailscale Client** (`/usr/sbin/tailscaled`)
|
|
- VPN daemon running on Jetson
|
|
- Manages WireGuard tunnels
|
|
- Connects to Headscale coordination server
|
|
|
|
2. **systemd Service** (`tailscale-vpn.service`)
|
|
- Auto-starts on boot
|
|
- Restarts on failure
|
|
- Manages lifecycle of tailscaled daemon
|
|
- Logs to journald
|
|
|
|
3. **Auth Key Manager** (`headscale-auth-helper.sh`)
|
|
- Generates and validates auth keys
|
|
- Stores keys securely at `/opt/saltybot/tailscale-auth.key`
|
|
- Manages revocation
|
|
|
|
4. **Setup Script** (`setup-tailscale.sh`)
|
|
- One-time installation of Tailscale package
|
|
- Configures IP forwarding
|
|
- Sets up persistent state directories
|
|
|
|
## Installation
|
|
|
|
### 1. Run Jetson Setup
|
|
|
|
```bash
|
|
sudo bash jetson/scripts/setup-jetson.sh
|
|
```
|
|
|
|
This configures the base system (Docker, NVMe, power mode, etc.).
|
|
|
|
### 2. Install Tailscale
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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**:
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
sudo bash jetson/systemd/install_systemd.sh
|
|
```
|
|
|
|
This:
|
|
- Deploys repo to `/opt/saltybot/jetson`
|
|
- Installs `tailscale-vpn.service` to `/etc/systemd/system/`
|
|
- Enables service for auto-start
|
|
|
|
### 5. Start the VPN Service
|
|
|
|
```bash
|
|
sudo systemctl start tailscale-vpn
|
|
```
|
|
|
|
Check status:
|
|
|
|
```bash
|
|
sudo systemctl status tailscale-vpn
|
|
sudo journalctl -fu tailscale-vpn
|
|
sudo tailscale status
|
|
```
|
|
|
|
## Usage
|
|
|
|
### Check VPN Status
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
ssh <username>@saltylab-orin.tail12345.ts.net
|
|
```
|
|
|
|
Or use the IP directly:
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```ini
|
|
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:
|
|
1. The systemd service will not auto-authenticate
|
|
2. Manual login is required: `sudo tailscale login --login-server=https://tailscale.vayrette.com:8180`
|
|
3. Re-run `headscale-auth-helper.sh generate` to 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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```bash
|
|
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.0` for accepting all interfaces
|
|
- Or explicitly bind to the Tailscale IP (`100.x.x.x`)
|
|
|
|
### Docker Services
|
|
|
|
If running Docker services that need tailnet access:
|
|
```dockerfile
|
|
# In docker-compose.yml
|
|
services:
|
|
app:
|
|
network_mode: host # Access host's Tailscale interface
|
|
```
|
|
|
|
## Security Considerations
|
|
|
|
1. **Auth Key**: Stored in plaintext at `/opt/saltybot/tailscale-auth.key`
|
|
- Use file permissions (`600`) to restrict access
|
|
- Consider encryption if sensitive environment
|
|
|
|
2. **State Directory**: `/var/lib/tailscale/` contains private keys
|
|
- Restricted permissions (700)
|
|
- Only readable by root
|
|
|
|
3. **SSH Over Tailnet**: No ACL restrictions by default
|
|
- Configure Headscale ACL rules if needed: `headscale acl`
|
|
|
|
4. **Key Rotation**: Rotate preauthkeys periodically
|
|
- Headscale supports key expiration (set to 2160h = 90 days default)
|
|
|
|
## Maintenance
|
|
|
|
### Update Tailscale
|
|
|
|
```bash
|
|
sudo apt-get update
|
|
sudo apt-get install --only-upgrade tailscale
|
|
sudo systemctl restart tailscale-vpn
|
|
```
|
|
|
|
### Backup
|
|
|
|
Backup the auth key:
|
|
|
|
```bash
|
|
sudo cp /opt/saltybot/tailscale-auth.key ~/tailscale-auth.key.backup
|
|
```
|
|
|
|
### Monitor
|
|
|
|
Set up log rotation to prevent journal bloat:
|
|
|
|
```bash
|
|
# 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.
|
|
|
|
## References
|
|
|
|
- [Headscale Documentation](https://headscale.net/)
|
|
- [Tailscale Documentation](https://tailscale.com/kb)
|
|
- [Tailscale CLI Reference](https://tailscale.com/kb/1080/cli)
|