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>
134 lines
5.3 KiB
Bash
Executable File
134 lines
5.3 KiB
Bash
Executable File
#!/bin/sh
|
|
# Stands up a real WireGuard tunnel entirely inside this machine, so the
|
|
# server's production network posture -- "bind wg0 and nothing else, fail
|
|
# closed if it's missing" -- can be exercised without a phone, a router
|
|
# port-forward, or any internet exposure.
|
|
#
|
|
# The shape, all in one kernel:
|
|
#
|
|
# main netns "phone" netns
|
|
# wg0 10.66.0.1 <-- encrypted --> wg1 10.66.0.2
|
|
# | |
|
|
# veth-srv 10.99.0.1 <--- UDP ---> veth-phone 10.99.0.2
|
|
#
|
|
# The veth pair stands in for "the internet" carrying WireGuard's UDP; the
|
|
# wg interfaces are real, with a real handshake and real keys. 10.66.0.1 is
|
|
# deliberately the same address the leaf certificate carries a SAN for
|
|
# (certs.rs covers every local address), so a client inside the tunnel
|
|
# completes the same pinned-TLS handshake a phone will.
|
|
#
|
|
# scripts/test-wg-tunnel.sh up create the tunnel (needs sudo)
|
|
# scripts/test-wg-tunnel.sh test run the server on wg0 and reach it from "phone"
|
|
# scripts/test-wg-tunnel.sh down remove everything it created
|
|
#
|
|
# Everything here is torn down by `down`: the netns (taking wg1 and the veth
|
|
# peer with it), wg0, and the temporary key files.
|
|
|
|
set -eu
|
|
|
|
NS=phone
|
|
WG_SERVER=wg0
|
|
WG_CLIENT=wg1
|
|
SERVER_WG_IP=10.66.0.1
|
|
CLIENT_WG_IP=10.66.0.2
|
|
SERVER_UDP_IP=10.99.0.1
|
|
CLIENT_UDP_IP=10.99.0.2
|
|
LISTEN_PORT=51820
|
|
KEYDIR=/run/ai-app-wg-test
|
|
# This script lives in scripts/; everything it names is under the root.
|
|
REPO=$(cd "$(dirname "$0")/.." && pwd)
|
|
|
|
up() {
|
|
echo "==> Generating ephemeral keypairs in $KEYDIR"
|
|
sudo mkdir -p "$KEYDIR"
|
|
sudo sh -c "umask 077; wg genkey > $KEYDIR/server.key; wg genkey > $KEYDIR/client.key"
|
|
sudo sh -c "wg pubkey < $KEYDIR/server.key > $KEYDIR/server.pub"
|
|
sudo sh -c "wg pubkey < $KEYDIR/client.key > $KEYDIR/client.pub"
|
|
|
|
echo "==> Creating netns '$NS' and the veth pair that carries the UDP"
|
|
sudo ip netns add "$NS"
|
|
sudo ip link add veth-srv type veth peer name veth-phone
|
|
sudo ip link set veth-phone netns "$NS"
|
|
sudo ip addr add "$SERVER_UDP_IP/24" dev veth-srv
|
|
sudo ip link set veth-srv up
|
|
sudo ip -n "$NS" addr add "$CLIENT_UDP_IP/24" dev veth-phone
|
|
sudo ip -n "$NS" link set veth-phone up
|
|
sudo ip -n "$NS" link set lo up
|
|
|
|
echo "==> Creating $WG_SERVER (server side, $SERVER_WG_IP)"
|
|
sudo ip link add "$WG_SERVER" type wireguard
|
|
sudo sh -c "wg set $WG_SERVER listen-port $LISTEN_PORT private-key $KEYDIR/server.key \
|
|
peer \$(cat $KEYDIR/client.pub) allowed-ips $CLIENT_WG_IP/32"
|
|
sudo ip addr add "$SERVER_WG_IP/24" dev "$WG_SERVER"
|
|
sudo ip link set "$WG_SERVER" up
|
|
|
|
# Created in the main namespace, then moved: a wireguard interface keeps
|
|
# its UDP socket in the namespace it was born in, which is exactly what
|
|
# lets the "phone" reach the server's veth address from inside its own.
|
|
echo "==> Creating $WG_CLIENT (phone side, $CLIENT_WG_IP) in netns '$NS'"
|
|
sudo ip link add "$WG_CLIENT" type wireguard
|
|
sudo ip link set "$WG_CLIENT" netns "$NS"
|
|
sudo ip netns exec "$NS" sh -c "wg set $WG_CLIENT private-key $KEYDIR/client.key \
|
|
peer \$(cat $KEYDIR/server.pub) allowed-ips $SERVER_WG_IP/32 \
|
|
endpoint $SERVER_UDP_IP:$LISTEN_PORT persistent-keepalive 5"
|
|
sudo ip -n "$NS" addr add "$CLIENT_WG_IP/24" dev "$WG_CLIENT"
|
|
sudo ip -n "$NS" link set "$WG_CLIENT" up
|
|
|
|
echo "==> Forcing a handshake"
|
|
sudo ip netns exec "$NS" ping -c 2 -W 3 "$SERVER_WG_IP" >/dev/null 2>&1 || true
|
|
sudo wg show "$WG_SERVER" | sed 's/^/ /'
|
|
echo "==> Up. wg0 is $SERVER_WG_IP; run '$0 test' next."
|
|
}
|
|
|
|
test_tunnel() {
|
|
CERTS="${XDG_CONFIG_HOME:-$HOME/.config}/ai-app/certs"
|
|
if [ ! -f "$CERTS/leaf.pem" ]; then
|
|
echo "No certificates in $CERTS -- start ai-server once; it makes them." >&2
|
|
exit 1
|
|
fi
|
|
if [ ! -x "$REPO/server/target/debug/ai-server" ]; then
|
|
echo "Build the server first: (cd server && cargo build)" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "==> Starting ai-server with NO --bind (production path: wg0 only)"
|
|
setsid nohup "$REPO/server/target/debug/ai-server" \
|
|
</dev/null >"$REPO/server/wg-test.log" 2>&1 &
|
|
sleep 2
|
|
|
|
echo "==> Where is it actually listening?"
|
|
ss -tlnp 2>/dev/null | grep 8443 | sed 's/^/ /' || echo " (nothing on 8443)"
|
|
|
|
echo "==> From inside the tunnel: GET /sessions through wg1 -> wg0"
|
|
if [ -z "${AI_TOKEN:-}" ]; then
|
|
echo " (set AI_TOKEN=<the enrollment token> to test an authorized call;"
|
|
echo " without it this only proves reachability + TLS, via a 401)"
|
|
fi
|
|
sudo ip netns exec "$NS" curl -s -o /dev/null -w " HTTP %{http_code} (TLS ok, pinned CA)\n" \
|
|
--cacert "$CERTS/ca.pem" \
|
|
${AI_TOKEN:+-H "Authorization: Bearer $AI_TOKEN"} \
|
|
"https://$SERVER_WG_IP:8443/sessions" || echo " UNREACHABLE"
|
|
|
|
echo "==> Handshake counters (proves the traffic really crossed WireGuard)"
|
|
sudo wg show "$WG_SERVER" transfer | sed 's/^/ /'
|
|
|
|
pkill -f "[a]i-server" || true
|
|
echo "==> Server stopped."
|
|
}
|
|
|
|
down() {
|
|
echo "==> Removing tunnel"
|
|
sudo ip netns del "$NS" 2>/dev/null || true
|
|
sudo ip link del "$WG_SERVER" 2>/dev/null || true
|
|
sudo ip link del veth-srv 2>/dev/null || true
|
|
sudo rm -rf "$KEYDIR"
|
|
echo "==> Down."
|
|
}
|
|
|
|
case "${1:-}" in
|
|
up) up ;;
|
|
test) test_tunnel ;;
|
|
down) down ;;
|
|
*) echo "usage: $0 up|test|down" >&2; exit 1 ;;
|
|
esac
|