#!/bin/sh # Sets up the WireGuard tunnel on the BACKEND HOST -- the machine that runs # ai-server and that the phone dials in to. Run this on the host, not in the # dev VM (the VM is behind qemu user-mode networking and has no inbound path; # see AGENTS.md). # # sudo WG_ENDPOINT=your-name.duckdns.org ./wg-setup-host.sh # # What it creates: # /etc/wireguard/wg0.conf the backend's tunnel: 10.66.0.1, port 51820 # /etc/wireguard/peers/phone.conf the phone's config, shown as a QR to scan # and brings the interface up with wg-quick. Making it come back after a # reboot is left to you: that is the one step whose commands differ per init # system, and this script would only be guessing (the backend host is Gentoo, # the dev VM is Arch). It prints what to run at the end. # # Addressing matches PLAN.md: the phone reaches the backend at 10.66.0.1 from # everywhere, home or away -- one address in the app, one SAN in the leaf # certificate, no home/away distinction. The phone's AllowedIPs is only # 10.66.0.0/24, so this is a split tunnel: the phone's other traffic does not # route through your house, and nothing here forwards or NATs. # # Re-running is safe: existing keys are reused, so the phone's config stays # valid. Pass WG_NEW_PHONE_KEY=1 to issue a fresh phone keypair, which # invalidates the old one. # # The one thing this cannot do for you: forward UDP 51820 from your router to # this host. That is the only internet-facing hole, and it is silent to # unauthenticated packets -- scanners see a closed port. set -eu WG_DIR=/etc/wireguard PEER_DIR="$WG_DIR/peers" SERVER_IP=10.66.0.1 PHONE_IP=10.66.0.2 SUBNET=10.66.0.0/24 PORT="${WG_PORT:-51820}" ENDPOINT="${WG_ENDPOINT:-}" if [ "$(id -u)" -ne 0 ]; then echo "Run this with sudo -- it writes $WG_DIR and enables a service." >&2 exit 1 fi for tool in wg wg-quick; do command -v "$tool" >/dev/null || { echo "$tool not found: install wireguard-tools." >&2; exit 1; } done if [ -z "$ENDPOINT" ]; then echo "Set WG_ENDPOINT to the hostname the phone should dial from outside," >&2 echo "e.g. WG_ENDPOINT=your-name.duckdns.org (a DDNS name, since a home IP" >&2 echo "can change). Then re-run." >&2 exit 1 fi umask 077 mkdir -p "$PEER_DIR" # Keys are generated here and never leave, except the phone's -- which is # what the QR carries. Regenerating the server key would invalidate every # peer, so it is created once and then reused. if [ ! -f "$WG_DIR/server.key" ]; then echo "==> Generating the backend's keypair" wg genkey > "$WG_DIR/server.key" wg pubkey < "$WG_DIR/server.key" > "$WG_DIR/server.pub" else echo "==> Reusing the backend's existing keypair" fi if [ ! -f "$PEER_DIR/phone.key" ] || [ -n "${WG_NEW_PHONE_KEY:-}" ]; then echo "==> Generating the phone's keypair" wg genkey > "$PEER_DIR/phone.key" wg pubkey < "$PEER_DIR/phone.key" > "$PEER_DIR/phone.pub" else echo "==> Reusing the phone's existing keypair" fi echo "==> Writing $WG_DIR/wg0.conf" cat > "$WG_DIR/wg0.conf" < Writing $PEER_DIR/phone.conf" cat > "$PEER_DIR/phone.conf" </dev/null 2>&1; then # Already up: load the new peers without dropping the interface, so a # re-run doesn't kill a connected phone mid-session. `wg-quick strip` # prints the config with the wg-quick-only keys removed, which is what # `wg syncconf` accepts. echo "==> wg0 is already up -- reloading its peers in place" STRIPPED=$(mktemp) trap 'rm -f "$STRIPPED"' EXIT wg-quick strip wg0 > "$STRIPPED" wg syncconf wg0 "$STRIPPED" else echo "==> Bringing wg0 up" wg-quick up wg0 fi sleep 1 wg show wg0 | sed 's/^/ /' echo echo "==> Phone config -- scan this with the WireGuard app (Add > Scan from QR code):" echo if command -v qrencode >/dev/null; then qrencode -t ansiutf8 < "$PEER_DIR/phone.conf" else echo " (install qrencode to get a scannable QR; the config is below)" sed 's/^/ /' "$PEER_DIR/phone.conf" fi echo echo "Still to do, in order:" echo " 0. Make wg0 come back after a reboot. Left to you rather than" echo " guessed at, since the command depends on your init system:" echo " OpenRC: ln -s /etc/init.d/wg-quick /etc/init.d/wg-quick.wg0" echo " rc-update add wg-quick.wg0 default" echo " systemd: systemctl enable wg-quick@wg0" echo " (Gentoo with netifrc instead of wg-quick: configure net.wg0 in" echo " /etc/conf.d/net -- see the WireGuard page on the Gentoo wiki.)" echo " 1. Forward UDP $PORT on your router to this host. That is the only" echo " internet-facing port; it stays silent to unauthenticated packets." echo " 2. Point $ENDPOINT at your home IP (DDNS client on the router, or a" echo " curl cron here). WireGuard on the phone resolves this once when the" echo " tunnel comes up, so after a rare IP change, toggle the tunnel." echo " 3. Check NAT hairpinning works at home: with the tunnel on and the" echo " phone on your wifi, 'ping $SERVER_IP' from the phone should answer." echo " If it doesn't, your router can't hairpin -- turn the tunnel off at" echo " home, or use a split-DNS entry pointing $ENDPOINT at the LAN IP." echo " 4. Start the backend here (it binds $SERVER_IP only, and refuses to" echo " start if wg0 is down):" echo " cd $(dirname "$(readlink -f "$0")") && ./server/target/release/ai-server" echo " Add --rotate-token once to print a fresh enrollment QR for the app."