diff --git a/bin/quinn-phone-bootstrap b/bin/quinn-phone-bootstrap new file mode 100755 index 0000000..190a4c1 --- /dev/null +++ b/bin/quinn-phone-bootstrap @@ -0,0 +1,60 @@ +#!/bin/sh +# quinn-phone-bootstrap — one-shot end-to-end setup for a phone (or tablet) to +# reach the home LAN via the wg1 mesh, with .local resolution. +# +# What it runs (in order): +# 1. wg-dns-sync on apricot — installs/updates dnsmasq wg-mesh.conf, +# so the phone resolves *.apricot.local etc. +# Requires interactive sudo on apricot +# (uses ssh -t to forward your tty). +# 2. wg-phone-add (locally) — generates or reuses the device's keypair, +# adds peer to quinn-vps wg1 hub, prints QR. +# +# Idempotent: re-runs are no-ops where possible. Use --device to onboard a new +# device (default: phone-quinn). +# +# Usage: +# quinn-phone-bootstrap # full setup, default device +# quinn-phone-bootstrap -d ipad-quinn # onboard a new device +# quinn-phone-bootstrap --skip-dns # skip the apricot dnsmasq step +# quinn-phone-bootstrap --show -d phone-quinn # just re-render the QR +# +# Run interactively (so apricot's sudo can prompt): +# ! quinn-phone-bootstrap + +set -eu + +device="phone-quinn" +skip_dns=0 +show_only=0 + +while [ $# -gt 0 ]; do + case $1 in + -d) device=$2; shift 2 ;; + --skip-dns) skip_dns=1; shift ;; + --show) show_only=1; shift ;; + -h|--help) sed -n '2,21p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; + *) echo "unknown arg: $1" >&2; exit 1 ;; + esac +done + +script_dir=$(cd "$(dirname "$0")" && pwd) + +if [ "$show_only" -eq 0 ] && [ "$skip_dns" -eq 0 ]; then + echo "===== step 1/2: sync dnsmasq on apricot =====" + # ssh -t so apricot's sudo can prompt against the user's tty. + ssh -t apricot 'cd /var/home/lilith/Code/@scripts/session-tools && sudo bin/wg-dns-sync' + echo +fi + +echo "===== step $([ "$show_only" -eq 1 ] && echo "1/1" || echo "2/2"): phone WireGuard peer =====" +if [ "$show_only" -eq 1 ]; then + "$script_dir/wg-phone-add" -d "$device" --show +else + "$script_dir/wg-phone-add" -d "$device" +fi + +echo +echo "Bootstrap complete." +echo "If the QR was already imported on the phone before, scanning again is harmless" +echo "(WireGuard iOS will refuse to import a duplicate)." diff --git a/bin/wg-phone-add b/bin/wg-phone-add new file mode 100755 index 0000000..ded156e --- /dev/null +++ b/bin/wg-phone-add @@ -0,0 +1,144 @@ +#!/bin/sh +# wg-phone-add — generate (or reuse) a WireGuard peer for a phone/tablet, +# add it to the quinn-vps wg1 hub, render the client config + QR. +# +# Idempotent: re-runs reuse the stored keypair and just re-display the QR. +# +# Keys live OUTSIDE this repo (private keys must not be committed): +# ~/.config/wg-mesh/clients//{private.key,public.key,address} +# +# Usage: +# wg-phone-add # default device "phone-quinn", auto-pick IP +# wg-phone-add -d phone-rachel # named device, auto-pick IP +# wg-phone-add -d ipad-quinn -i 10.9.0.6 +# wg-phone-add -d phone-quinn --show # just re-render QR for an existing client +# +# Exit codes: +# 0 success +# 1 missing dep / invalid input +# 2 ssh to hub failed +# 3 hub config edit / wg syncconf failed + +set -eu + +device="phone-quinn" +forced_ip="" +show_only=0 + +while [ $# -gt 0 ]; do + case $1 in + -d) device=$2; shift 2 ;; + -i) forced_ip=$2; shift 2 ;; + --show) show_only=1; shift ;; + -h|--help) sed -n '2,18p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; + *) echo "unknown arg: $1" >&2; exit 1 ;; + esac +done + +# Validate device name (used in filenames + comment in hub conf). +case $device in + *[!A-Za-z0-9_-]*|"") echo "invalid device name: $device" >&2; exit 1 ;; +esac + +for cmd in ssh wg qrencode jq; do + command -v $cmd >/dev/null || { echo "$cmd not installed" >&2; exit 1; } +done + +hub=quinn-vps +hub_endpoint=89.127.233.145:51820 +hub_pubkey=t6LT6Ff4AxcGCd9Zug7dxkT7tXhkMV+fd28UCY/h8Xw= +mesh_subnet=10.9.0.0/24 +lan_subnet=10.0.0.0/24 +mesh_dns=10.9.0.2 # apricot, where wg-dns-sync runs + +client_dir="$HOME/.config/wg-mesh/clients/$device" +mkdir -p "$client_dir" +chmod 700 "$client_dir" + +# 1. Reuse or generate keypair. +if [ -s "$client_dir/private.key" ] && [ -s "$client_dir/public.key" ]; then + echo "wg-phone-add: reusing existing keypair at $client_dir" + privkey=$(cat "$client_dir/private.key") + pubkey=$(cat "$client_dir/public.key") +else + [ "$show_only" -eq 1 ] && { echo "wg-phone-add: --show given but no keypair at $client_dir" >&2; exit 1; } + echo "wg-phone-add: generating new keypair" + privkey=$(wg genkey) + pubkey=$(echo "$privkey" | wg pubkey) + umask 077 + printf %s "$privkey" > "$client_dir/private.key" + printf %s "$pubkey" > "$client_dir/public.key" +fi + +# 2. Pick or validate the WG IP slot. +if [ -n "$forced_ip" ]; then + address=$forced_ip +elif [ -s "$client_dir/address" ]; then + address=$(cat "$client_dir/address") +else + # Find lowest free .5..254 in 10.9.0.0/24 by reading hub config over ssh. + used=$(ssh -o BatchMode=yes "$hub" "grep -hoE '10\\.9\\.0\\.[0-9]+' /etc/wireguard/wg1.conf | sort -u") + address="" + for n in $(seq 5 254); do + candidate="10.9.0.$n" + echo "$used" | grep -qx "$candidate" || { address=$candidate; break; } + done + [ -n "$address" ] || { echo "no free 10.9.0.x slot in /24" >&2; exit 1; } +fi +printf %s "$address" > "$client_dir/address" + +# 3. (skipped if --show) ensure peer block is on hub. +if [ "$show_only" -eq 0 ]; then + # Probe whether hub already has this pubkey configured. + if ssh -o BatchMode=yes "$hub" "grep -qxF 'PublicKey = $pubkey' /etc/wireguard/wg1.conf"; then + echo "wg-phone-add: hub already has peer with this pubkey, skipping config edit" + else + echo "wg-phone-add: appending [Peer] block to $hub:/etc/wireguard/wg1.conf" + # Use printf to avoid heredoc quoting headaches over ssh. + # NOTE: assumes ssh user on quinn-vps can write /etc/wireguard/wg1.conf. + # On quinn-vps the user is root (no sudo), per existing topology. + if ! ssh -o BatchMode=yes "$hub" "printf '\n[Peer]\n# %s\nPublicKey = %s\nAllowedIPs = %s/32\nPersistentKeepalive = 25\n' '$device' '$pubkey' '$address' >> /etc/wireguard/wg1.conf"; then + echo "ssh write to hub failed" >&2; exit 3 + fi + # Live-reload without disturbing existing tunnels. + if ! ssh -o BatchMode=yes "$hub" "wg syncconf wg1 <(wg-quick strip wg1)"; then + echo "wg syncconf on hub failed" >&2; exit 3 + fi + fi +fi + +# 4. Render the client config. +client_conf="$client_dir/$device.conf" +umask 077 +cat > "$client_conf" <