WireGuard bring-up: host setup script, in-VM test tunnel, portable repo_root
wg-setup-host.sh sets up the tunnel on the backend host: keys generated there and kept there, wg0.conf, wg-quick enabled, and the phone's config printed as a scannable QR. Split tunnel (AllowedIPs is only the backend subnet), single-address addressing per PLAN.md, and the three things it can't do for you -- router UDP forward, DDNS, hairpin check -- spelled out at the end. test-wg-tunnel.sh stands up a real WireGuard tunnel between two network namespaces inside one machine, so the production posture (bind wg0 and nothing else) is testable with no router, phone, or internet exposure. Verified: real handshake, server listening on 10.66.0.1:8443 only, and an authorized request from inside the tunnel answering 200 over pinned TLS -- the leaf's 10.66.0.1 SAN is what a phone will validate too. repo_root() now resolves from the running executable before falling back to the compiled-in path: the repo is shared host<->VM over virtiofs at different absolute paths with a shared target/, so a binary built on one side and run on the other looked for its config where nothing exists. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017xn8nHw1tw1R6PtiY1eEtw
This commit is contained in:
1 parent
8841effc6a
commit
29f8b31f0b
5 files changed
+324
-4
No files matched your search
Executable
+136
@@ -0,0 +1,136 @@
|
||||
#!/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 enables wg-quick@wg0 so the tunnel comes back after a reboot.
|
||||
#
|
||||
# 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" <<EOF
|
||||
# Generated by ai-app/wg-setup-host.sh. The backend binds this interface's
|
||||
# address and refuses to start without it (see server/src/main.rs).
|
||||
[Interface]
|
||||
Address = $SERVER_IP/24
|
||||
ListenPort = $PORT
|
||||
PrivateKey = $(cat "$WG_DIR/server.key")
|
||||
|
||||
[Peer]
|
||||
# phone
|
||||
PublicKey = $(cat "$PEER_DIR/phone.pub")
|
||||
AllowedIPs = $PHONE_IP/32
|
||||
EOF
|
||||
|
||||
echo "==> Writing $PEER_DIR/phone.conf"
|
||||
cat > "$PEER_DIR/phone.conf" <<EOF
|
||||
[Interface]
|
||||
Address = $PHONE_IP/24
|
||||
PrivateKey = $(cat "$PEER_DIR/phone.key")
|
||||
|
||||
[Peer]
|
||||
PublicKey = $(cat "$WG_DIR/server.pub")
|
||||
Endpoint = $ENDPOINT:$PORT
|
||||
# Split tunnel: only the backend's subnet goes over WireGuard.
|
||||
AllowedIPs = $SUBNET
|
||||
# Keeps the mapping alive through home NAT so the backend can reach the
|
||||
# phone first (needed later for "your turn" push).
|
||||
PersistentKeepalive = 25
|
||||
EOF
|
||||
|
||||
echo "==> Enabling wg-quick@wg0"
|
||||
systemctl enable --now "wg-quick@wg0" >/dev/null 2>&1 || {
|
||||
echo " systemctl failed; bringing it up directly instead"
|
||||
wg-quick down wg0 >/dev/null 2>&1 || true
|
||||
wg-quick up wg0
|
||||
}
|
||||
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 " 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."
|
||||
Reference in new issue
Block a user