Files
ai-app/scripts/wg-setup-host.sh
T
irisandClaude Opus 5 4ccfda6b8e Delete the decisions and design logs; scripts, rigs and xtask off the root
Iris: "remove both decisions and iris.md. I've decided to instead make
decisions when planning with agents rather than after they do things, and
they're both too long for me to wanna read, + don't cover all the
decisions I'll wanna make about the code anyways. I'll just naturally run
into things for now. Todo is important though."

So docs/DECISIONS.md (850 lines) and docs/IRIS.md (1,986) are gone, and
AGENTS.md now says not to start another: raise a choice while planning it
with her, otherwise decide it and put the reasoning at the code it
governs. The TODO lists stay. docs/SUBAGENTS_DECISIONS.md went with them
-- same artefact, same reasoning, and she did not name it, so its six
decisions were folded into docs/SUBAGENTS.md rather than deleted.

Deleting the logs left ~30 citations dangling in code comments and docs.
Each states its reason inline and cited the file only for provenance, so
they now read "decided 2026-09-07" or name the module doc that carries
the reasoning.

The root had six things that were not a program or a document. Moved,
per "I only meant top level sh files":

  run-tests.sh, test-wg-tunnel.sh, wg-setup-host.sh  -> scripts/
  rigs/                                              -> scripts/rigs/
  xtask/                                             -> scripts/xtask/

A project's own scripts stayed with the project: app/*.sh, app-rust/*.sh,
iris/*.sh and server/enroll-link.sh did not move.

`target/` at the root is deleted and cannot come back: there was never a
workspace there, and the 29 MB was only xtask's scratch space, now in
scripts/xtask/target/. `cargo xtask apk` still runs from the repo root
and now publishes to scripts/build/outputs/apk/<mode>/ -- one directory
deep, because that is what Dev Updater's `*/build/outputs/apk/*/*.apk`
discovery pattern needs, and scripts/xtask/build would have been two.

Verified: ./scripts/run-tests.sh and `cd iris && cargo test` green, clippy
and fmt clean everywhere, `cargo xtask apk debug --abi x86_64` builds and
signs an APK carrying lib/x86_64/libai_app.so at the new publish path, and
the repo root is now eleven entries with no build output among them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 00:16:24 -04:00

155 lines
6.1 KiB
Bash
Executable File

#!/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 scripts/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" <<EOF
# Generated by ai-app/scripts/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
if wg show wg0 >/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 "$(dirname "$(readlink -f "$0")")") && ./server/target/release/ai-server"
echo " Add --rotate-token once to print a fresh enrollment QR for the app."