#!/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.lan/"
