thermograph/infra/deploy/swarm/setup-wireguard.sh
emi d138f00a20
Some checks failed
Sync infra to hosts / sync-beta (push) Has been skipped
Sync infra to hosts / sync-prod (push) Has been skipped
Sync infra to hosts / sync-dev (push) Failing after 6s
secrets-guard / encrypted (push) Successful in 6s
shell-lint / shellcheck (push) Successful in 13s
Validate observability stack / validate (push) Successful in 17s
PR build (required check) / changes (pull_request) Successful in 6s
secrets-guard / encrypted (pull_request) Successful in 5s
PR build (required check) / build-backend (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Successful in 6s
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Successful in 18s
PR build (required check) / gate (pull_request) Successful in 2s
infra: split the estate into vps1/vps2 — beta joins prod, dev gets a home (#103)
2026-07-26 06:56:38 +00:00

115 lines
4.3 KiB
Bash
Executable file

#!/usr/bin/env bash
# Sets up this node's side of a full-mesh WireGuard tunnel across all of
# vps2, vps1, and the desktop (three nodes, not two — see
# docs/runbooks/implementation-handoff.md Track B step 2). Docker Swarm's
# control plane (2377/tcp) is TLS-encrypted by default, but the overlay data
# plane (VXLAN, 4789/udp) is NOT — and it should never face the public
# internet. Swarm joins over these private WireGuard IPs instead.
#
# A full mesh means every node peers directly with every other node (not a
# hub-and-spoke through one box) — with three nodes that's 2 [Peer] blocks per
# node. Run this on EACH of the three nodes, in any order, using a small peer
# list file so the script isn't hardcoded to any particular node count.
#
# --- Peer list format --------------------------------------------------
# A text file, one line per OTHER node (not including the one you're running
# on), each line: <wg_ip> <public_ip> <pubkey-or-dash>
# 10.10.0.1 169.58.46.181 <vps2's pubkey, or - if not yet known>
# 10.10.0.2 75.119.132.91 <vps1's pubkey, or ->
# 10.10.0.3 <desktop's public/reachable IP or a DDNS name> <desktop's pubkey, or ->
#
# First pass on any node: peer pubkeys you don't have yet are "-". Run this
# script on all three nodes once (prints each node's own pubkey), fill those
# into everyone's peer-list files, then re-run on all three once more to
# actually establish the tunnels. Re-running is safe (idempotent) at any point.
#
# bash setup-wireguard.sh <my_wg_ip> <peers_file>
set -euo pipefail
MY_WG_IP="${1:?usage: $0 <my_wg_ip> <peers_file>}"
PEERS_FILE="${2:?}"
WG_PORT="${WG_PORT:-51820}"
WG_DIR=/etc/wireguard
if [ "$(id -u)" -ne 0 ]; then
echo "Run as root (or via the agent user's sudo)." >&2
exit 1
fi
if [ ! -f "$PEERS_FILE" ]; then
echo "Peer list not found: $PEERS_FILE (see this script's header for the format)" >&2
exit 1
fi
echo "==> Installing WireGuard if needed"
command -v wg >/dev/null 2>&1 || { apt-get update -y -q && apt-get install -y -q wireguard; }
install -d -m 700 "$WG_DIR"
if [ ! -f "$WG_DIR/privatekey" ]; then
echo "==> Generating this node's WireGuard keypair"
umask 077
wg genkey | tee "$WG_DIR/privatekey" | wg pubkey > "$WG_DIR/publickey"
fi
MY_PRIVKEY="$(cat "$WG_DIR/privatekey")"
MY_PUBKEY="$(cat "$WG_DIR/publickey")"
echo
echo "==> This node's WireGuard public key (put this in the OTHER two nodes'"
echo " peer-list files, replacing the '-' for this node's line):"
echo " $MY_PUBKEY"
echo
missing=0
{
echo "[Interface]"
echo "Address = ${MY_WG_IP}/24"
echo "PrivateKey = ${MY_PRIVKEY}"
echo "ListenPort = ${WG_PORT}"
echo
while read -r peer_wg_ip peer_public_ip peer_pubkey; do
[ -z "${peer_wg_ip:-}" ] && continue
case "$peer_wg_ip" in \#*) continue ;; esac
if [ "$peer_pubkey" = "-" ] || [ -z "$peer_pubkey" ]; then
echo "# SKIPPED (no pubkey yet) — peer at ${peer_public_ip}" >&2
missing=$((missing + 1))
continue
fi
echo "[Peer]"
echo "PublicKey = ${peer_pubkey}"
echo "Endpoint = ${peer_public_ip}:${WG_PORT}"
echo "AllowedIPs = ${peer_wg_ip}/32"
echo "PersistentKeepalive = 25"
echo
done < "$PEERS_FILE"
} > "$WG_DIR/wg0.conf"
chmod 600 "$WG_DIR/wg0.conf"
echo "==> Bringing up wg0"
systemctl enable --now wg-quick@wg0 2>/dev/null || (wg-quick down wg0 2>/dev/null; wg-quick up wg0)
wg syncconf wg0 <(wg-quick strip wg0) 2>/dev/null || true
echo "==> Firewall: only let listed peers' public IPs reach the WireGuard port"
if command -v ufw >/dev/null 2>&1; then
while read -r _wg_ip peer_public_ip _pubkey; do
[ -z "${peer_public_ip:-}" ] && continue
case "$peer_public_ip" in \#*) continue ;; esac
ufw allow from "$peer_public_ip" to any port "$WG_PORT" proto udp comment "wireguard peer"
done < "$PEERS_FILE"
fi
echo
echo "wg0 status:"
wg show wg0 || true
echo
if [ "$missing" -gt 0 ]; then
echo "NOTE: $missing peer(s) skipped (no pubkey yet in $PEERS_FILE). Fill them in"
echo "and re-run on this node once all three nodes have printed their pubkey."
else
echo "==> All peers configured. Verify once every node has run this:"
while read -r peer_wg_ip _peer_public_ip peer_pubkey; do
[ -z "${peer_wg_ip:-}" ] && continue
case "$peer_wg_ip" in \#*) continue ;; esac
[ "$peer_pubkey" = "-" ] && continue
echo " ping -c2 ${peer_wg_ip}"
done < "$PEERS_FILE"
fi