Skip to content

Provisioning Devices

Provisioning adds a device to your Watchgrid fleet. The device gets a WireGuard VPN connection, the Watchgrid agent, Magic DNS resolution, and SSH CA trust — all in one step.


Supported Platforms

  • Raspberry Pi (all models, including Pi 5)
  • Ubuntu Server 20.04+
  • Debian 11+
  • Any Linux system with systemd and bash

Supported architectures: amd64, arm64, arm (armhf/armv7).


One-Line Provisioning

Run this command on the device you want to add:

curl -s https://YOUR_SERVER/provision.sh | sudo bash -s https://YOUR_SERVER

Replace YOUR_SERVER with your Watchgrid server's domain name or IP. HTTPS is required — the provisioning script refuses http:// URLs to prevent binary tampering during download. For local-only development use WATCHGRID_ALLOW_HTTP=1 as an explicit override.

Options

Flag Description
--k3s Also install K3s (lightweight Kubernetes)
--update Only update the agent binary (skip VPN setup)
--force Force reprovisioning (clear existing WireGuard config and keys)
--token TOKEN Onboarding token for tenant assignment
--siteid ID Assign the device directly to a site during first onboarding

Examples:

# Basic provisioning
curl -s https://watchgrid.example.com/provision.sh | sudo bash -s https://watchgrid.example.com

# With K3s installation
curl -s https://watchgrid.example.com/provision.sh | sudo bash -s https://watchgrid.example.com --k3s

# Update agent only
curl -s https://watchgrid.example.com/provision.sh | sudo bash -s https://watchgrid.example.com --update

# With onboarding token (assigns to specific tenant)
curl -s https://watchgrid.example.com/provision.sh | sudo bash -s https://watchgrid.example.com --token abc123

# Assign to site 5 during onboarding
curl -s https://watchgrid.example.com/provision.sh | sudo bash -s https://watchgrid.example.com --token abc123 --siteid 5

# Force a clean reprovision (wipes WireGuard config)
curl -s https://watchgrid.example.com/provision.sh | sudo bash -s https://watchgrid.example.com --force

# Local dev only — bypass HTTPS check
WATCHGRID_ALLOW_HTTP=1 bash provision.sh http://localhost:8080

What Provisioning Does

The provisioning script performs these steps automatically:

  1. Detects architecture — amd64, arm64, or arm
  2. Downloads the agent binary from your server (correct architecture)
  3. Generates WireGuard keys (or reuses existing ones if reprovisioning)
  4. Registers with the server — sends device info and public key
  5. Configures WireGuard — sets up the VPN tunnel to the server
  6. Installs the agent as a systemd service (watchgrid-agent)
  7. Configures SSH CA trust — downloads the CA public key for certificate-based SSH
  8. Optionally installs K3s with the --k3s flag
  9. Runs provisioning profiles — executes any tag-matched scripts (see Provisioning Profiles)

Approving Devices

After a device is provisioned, it appears in the Dashboard as a pending authorization:

  1. Go to the Dashboard
  2. Look for the Pending Agent Authorizations section at the top
  3. You'll see the device ID and its WireGuard public key
  4. Click Approve & Assign IP to admit the device to the VPN
  5. The device gets a VPN IP (e.g., 100.64.1.x) and becomes reachable

Denied devices are rejected and cannot connect to the VPN.


Onboarding via the Web UI

You can also onboard devices through the web interface:

  1. Go to Devices → click Onboard Device
  2. The modal shows a provisioning command with your server URL
  3. Copy the command and run it on the target device
  4. Onboarding tokens can lock a device to a specific tenant

Reprovisioning

If your server is reinitialized or you need to refresh a device's connection:

curl -s https://YOUR_SERVER/provision.sh | sudo bash -s https://YOUR_SERVER

Running the same provisioning command again:

  • Reuses existing WireGuard keys (does not generate new ones)
  • Updates the agent binary to the latest version
  • Re-registers with the server
  • Preserves the device's identity

Raspberry Pi 5 Note

On a Raspberry Pi 5 with K3s (--k3s flag), the provisioning script detects if cgroups need to be enabled. If so, it updates /boot/firmware/cmdline.txt and reboots the device automatically. After reboot, run the provisioning command again to complete the K3s installation.


Verifying a Provisioned Device

On the device, check the agent service:

sudo systemctl status watchgrid-agent
sudo journalctl -u watchgrid-agent -f

Verify VPN connectivity:

# Check WireGuard interface
sudo wg show

# Ping the server through the VPN
ping 100.64.1.254

Verify Magic DNS:

# Resolve another device by hostname
nslookup other-device.wg 100.64.1.254

Troubleshooting

Problem Solution
Device stuck on "pending" Check that WG_SERVER_ENDPOINT is reachable from the device
WireGuard handshake fails Verify port 51820/udp is open on the server
Agent won't start Check logs: journalctl -u watchgrid-agent -f
DNS not resolving .wg Ensure DNS = 100.64.1.254 is in the device's WireGuard config
Architecture mismatch The script auto-detects arch; verify with uname -m
Update fails with "checksum mismatch" Rebuild agent binaries on the server — checksums are generated by scripts/build-agent.sh and must match the served binary

Security

HTTPS requirement

The provisioning script refuses http:// server URLs to prevent man-in-the-middle attacks during binary download. Always run your Watchgrid server behind a TLS-terminating reverse proxy (nginx, Caddy, etc.) in production. The script also runs automatically after a K3s reboot — the stored server URL must be HTTPS for this continuation step to succeed.

Agent self-update integrity

When the server triggers a remote agent update:

  1. SHA-256 checksum — the agent downloads watchgrid-agent-{arch}.sha256 and compares it against the binary hashed in-flight; mismatches abort the update.
  2. Ed25519 signature — if a public key is configured (via WATCHGRID_UPDATE_PUBKEY env var or the build-time -ldflags "-X main.agentUpdatePublicKeyHex=..." flag), the agent also downloads watchgrid-agent-{arch}.sig and verifies an Ed25519 signature over the binary. If verification fails, the binary is deleted and the update is aborted.

To enable signature verification in production:

# Generate a signing key pair (run once, store private key securely)
openssl genpkey -algorithm ed25519 -out agent-signing.pem
openssl pkey -in agent-signing.pem -pubout -outform DER | tail -c 32 | xxd -p -c 32 > agent-signing.pub.hex

# Sign each binary in the build pipeline
openssl pkeyutl -sign -inkey agent-signing.pem -rawin -in watchgrid-agent-amd64 \
  -out watchgrid-agent-amd64.sig

# Embed the public key at build time
go build -ldflags="-X main.agentUpdatePublicKeyHex=$(cat agent-signing.pub.hex)" ./...

# Or set at runtime on the device
export WATCHGRID_UPDATE_PUBKEY=$(cat agent-signing.pub.hex)

If no public key is configured, the agent logs a warning and falls back to checksum-only verification.

TLS certificate pinning (SPKI)

The agent supports pinning the server's TLS certificate by its SPKI (Subject Public Key Info) SHA-256 hash. Set WATCHGRID_SERVER_SPKI to a comma-separated list of hex-encoded SHA-256 SPKI hashes. The agent rejects any server whose certificate does not match.

# Get the SPKI hash of your server's certificate
openssl s_client -connect watchgrid.example.com:443 </dev/null 2>/dev/null \
  | openssl x509 -noout -pubkey \
  | openssl pkey -pubin -outform DER \
  | openssl dgst -sha256 -hex | awk '{print $2}'

export WATCHGRID_SERVER_SPKI=<hash-from-above>

K3s container registry

Devices running K3s pull container images from registry.wg:5000 via the WireGuard-encrypted tunnel. The containerd registry configuration uses plain http:// endpoints; WireGuard provides transport-layer encryption. No TLS certificate bypass flags are set in the registry config.