ops(fips): fleet LAN fast-path pairing script (dev nodes only)

scripts/fleet-fips-pair.sh writes a deterministic /etc/fips/fips.yaml
on each of our 4 dev fleet nodes (.116/.198/.228/.253), listing the
other three as static FIPS peers over their LAN IPs (UDP 2121 / TCP
8443). Also flips `node.identity.persistent: true` so the npub stays
stable across restarts — without this the daemon rolls a new keypair
on every restart and federation invites that carried the previous
npub go stale.

The script is NOT the general deployment mechanism:
- Every archipelago install already ships fips.v0l.io as an anchor
  peer, so any node can DHT-route to any npub that has ever announced
  on the public mesh.
- Federation invites (v1.4+) carry the peer's fips_npub, so accepting
  an invite is enough for crate::fips::dial::peer_base_url(npub) to
  reach the peer through the anchor network.
- This script is a LAN fast-path optimization so intra-fleet traffic
  stays on the wire instead of bouncing through fips.v0l.io.

Usage:
  scripts/fleet-fips-pair.sh           # apply to all nodes
  scripts/fleet-fips-pair.sh --verify  # print current peer state

Verified: all 4 fleet nodes now report 3 authenticated peers each
(their 3 fleet siblings), plus fips.v0l.io in the identity cache.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-04-19 01:50:20 -04:00
parent 683553dfde
commit bcd9b9aa56

176
scripts/fleet-fips-pair.sh Executable file
View File

@@ -0,0 +1,176 @@
#!/bin/bash
# LAN fast-path pairing for our 4 dev fleet nodes.
#
# ── Is this needed for every archipelago install? No. ────────────────
# For nodes deployed anywhere in the world, FIPS-to-FIPS routing by
# npub works via the anchor peer network (fips.v0l.io ships by default
# in /etc/fips/fips.yaml on every install — that anchor bootstraps DHT
# routing for any npub the node has ever heard about). The peer's
# fips_npub is advertised in our federation invite codes (since v1.4),
# so accepting an invite is enough for `dial::peer_base_url(npub)` to
# reach the peer through the anchor mesh.
#
# ── Why this script exists ───────────────────────────────────────────
# Our 4 fleet nodes are all on 192.168.1.0/24. Hopping through the
# fips.v0l.io anchor for intra-LAN traffic is wasteful when the peers
# are on the same wire. This script writes per-node fips.yaml with:
# 1. The public anchor (fips.v0l.io) so internet peers still route.
# 2. The other 3 fleet nodes as static LAN peers (UDP 2121 / TCP
# 8443) so LAN traffic stays on LAN.
# 3. `persistent: true` so the npub is stable across restarts —
# without this the daemon rolls a new keypair on every restart
# and any federation invite we advertised goes stale.
#
# Idempotent: re-running picks up any newly-added or removed nodes.
#
# For a production install on an unknown LAN, this script isn't the
# mechanism — the ISO install writes the anchor-only fips.yaml and
# identity comes from the archipelago seed; peer discovery is purely
# through the DHT + federation invites.
#
# Usage:
# scripts/fleet-fips-pair.sh # apply to all nodes
# scripts/fleet-fips-pair.sh --verify # just print the peer state
set -eo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/lib/common.sh"
# Fleet roster: "<ip-last-octet> <nic-name> <fips-npub>"
NODES=(
"116 enp0s25 npub1mxavs6scfgl056k6lm4mk73ddnrhjewg78zlyzfn2lmr0rfyrs5qhcr03g"
"198 enp2s0 npub13cy4lml94cj4rdu8runrr945z2muszuvr5tql8mr9m063d7xzpqqu3k8se"
"228 enp2s0 npub1a0xxcqce2tsv8ulwastep23jtf3h4wvvry8r8nklnl36jtrdnefqh5qn6h"
"253 enx9cbf0d0129f9 npub1dl0m0yfzfw6467c3z6q63s7ggzd77yg97j90ptfrheprxeypt3msj0mq4g"
)
LAN_PREFIX="192.168.1"
UDP_PORT=2121
TCP_PORT=8443
if [ "${1:-}" = "--verify" ]; then
for row in "${NODES[@]}"; do
read -r node _nic _npub <<< "$row"
echo "=== .$node ==="
ssh_cmd "$LAN_PREFIX.$node" "sudo fipsctl show peers 2>/dev/null | python3 -c 'import sys,json; d=json.load(sys.stdin); print(f\"{len(d[\"peers\"])} authenticated peers\"); [print(\" npub=\", p.get(\"npub\",\"?\"), \"alias=\", p.get(\"alias\",\"?\")) for p in d[\"peers\"]]' || echo ' fipsctl show peers failed'"
done
exit 0
fi
TMP_ROOT=$(mktemp -d)
trap 'rm -rf "$TMP_ROOT"' EXIT
generate_yaml() {
# $1 = self node octet, $2 = self nic
local self_node="$1"
local self_nic="$2"
local out="$TMP_ROOT/fips.yaml.$self_node"
cat > "$out" <<YAML
# FIPS Node Configuration — managed by scripts/fleet-fips-pair.sh
# DO NOT hand-edit: re-run the script to regenerate.
node:
identity:
# Persistent identity so the npub stays stable across restarts.
# Without this, every restart rolls a new keypair and federation
# peer lists go stale.
persistent: true
tun:
enabled: true
name: fips0
mtu: 1280
dns:
enabled: true
bind_addr: "127.0.0.1"
port: 5354
transports:
udp:
bind_addr: "0.0.0.0:$UDP_PORT"
tcp:
bind_addr: "0.0.0.0:$TCP_PORT"
ethernet:
interface: "$self_nic"
discovery: true
announce: true
auto_connect: true
accept_connections: true
peers:
# Public anchor — bootstraps DHT routing for any npub heard via
# federation invites. Every archipelago install ships this peer.
- npub: "npub1zv58cn7v83mxvttl70w5fwjwuclfmntv9cnmv5wmz2nzz88u5urqvdx96n"
alias: "fips.v0l.io"
addresses:
- transport: tcp
addr: "fips.v0l.io:8443"
- transport: udp
addr: "fips.v0l.io:2121"
connect_policy: auto_connect
# Fleet LAN fast-path — other archipelago nodes on this subnet.
YAML
for other_row in "${NODES[@]}"; do
read -r o_node _o_nic o_npub <<< "$other_row"
[ "$o_node" = "$self_node" ] && continue
cat >> "$out" <<YAML
- npub: "$o_npub"
alias: "archi-$o_node"
addresses:
- transport: udp
addr: "$LAN_PREFIX.$o_node:$UDP_PORT"
- transport: tcp
addr: "$LAN_PREFIX.$o_node:$TCP_PORT"
connect_policy: auto_connect
YAML
done
echo "$out"
}
deploy_to() {
local node="$1"
local nic="$2"
local ip="$LAN_PREFIX.$node"
local yaml
yaml=$(generate_yaml "$node" "$nic")
log_info "[.${node}] uploading fips.yaml"
scp_cmd "$yaml" "archipelago@${ip}:/tmp/fips.yaml.new"
log_info "[.${node}] installing + restarting fips.service"
ssh_cmd "$ip" '
set -e
sudo install -o root -g root -m 0600 /tmp/fips.yaml.new /etc/fips/fips.yaml
rm -f /tmp/fips.yaml.new
sudo systemctl restart fips.service
# Give the daemon a beat to come up before we ask about peers
for i in $(seq 1 10); do
if sudo systemctl is-active fips.service >/dev/null 2>&1; then break; fi
sleep 0.5
done
sudo systemctl is-active fips.service
'
}
for row in "${NODES[@]}"; do
read -r node nic _npub <<< "$row"
deploy_to "$node" "$nic"
done
echo
log_info "Waiting 10s for peer handshakes to settle…"
sleep 10
echo
log_info "Post-pair peer state:"
for row in "${NODES[@]}"; do
read -r node _nic _npub <<< "$row"
count=$(ssh_cmd "$LAN_PREFIX.$node" "sudo fipsctl show peers 2>/dev/null | grep -c '\"npub\"' || echo 0")
log_info " .$node: $count authenticated peers"
done