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
115 lines
4.3 KiB
Bash
Executable file
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
|