feat(@scripts): add phone bootstrap and peer setup scripts

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-05-10 05:33:32 -07:00
parent 8a222d547d
commit 33094b9c05
2 changed files with 204 additions and 0 deletions

60
bin/quinn-phone-bootstrap Executable file
View file

@ -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)."

144
bin/wg-phone-add Executable file
View file

@ -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/<device>/{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" <<EOF
# WireGuard config for $device
# Generated by session-tools/bin/wg-phone-add at $(date -u +%Y-%m-%dT%H:%M:%SZ)
# Hub: $hub ($hub_endpoint)
[Interface]
PrivateKey = $privkey
Address = $address/32
DNS = $mesh_dns
[Peer]
PublicKey = $hub_pubkey
Endpoint = $hub_endpoint
AllowedIPs = $mesh_subnet, $lan_subnet
PersistentKeepalive = 25
EOF
echo
echo "Client config written: $client_conf"
echo
echo "===== QR (scan with WireGuard iOS) ====="
qrencode -t ansiutf8 < "$client_conf"
echo "========================================"
echo
echo "Device: $device"
echo "Address: $address"
echo "Pubkey: $pubkey"
echo "Hub: $hub_endpoint"
echo
echo "After scanning: enable the tunnel in WireGuard iOS, then test from phone:"
echo " ping 10.0.0.116 (apricot LAN)"
echo " open https://quinn.apricot.local/"