NodeConfig and MasterNodeConfig gain an optional addresses[] field
for fallback addresses tried in order after the primary address.
Provides resilience when Tailscale DNS is down or a node is only
reachable via LAN.
- dialAgentMulti: tries each address with a 3s health check, returns
first success
- forEachNode: uses multi-address dialing
- AgentPool.AddNodeMulti: master tries all addresses when connecting
- AllAddresses(): deduplicates primary + fallback addresses
Config example:
[[nodes]]
name = "rift"
address = "rift.scylla-hammerhead.ts.net:9444"
addresses = ["100.95.252.120:9444", "192.168.88.181:9444"]
Existing configs without addresses[] work unchanged.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Master struct with Run() lifecycle following the agent pattern exactly:
open DB → bootstrap nodes → create agent pool → DNS client → TLS →
auth interceptor → gRPC server → signal handler.
RPC handlers:
- Deploy: place service (tier-aware), forward to agent, register DNS
with Tailnet IP, detect public routes, validate against allowed
domains, coordinate edge routing via SetupEdgeRoute, record placement
and edge routes in master DB, return structured per-step results.
- Undeploy: undeploy on worker first, then remove edge routes, DNS,
and DB records. Best-effort cleanup on failure.
- Status: query agents for service status, aggregate with placements
and edge route info from master DB.
- ListNodes: return all nodes with placement counts.
Placement algorithm: fewest services, ties broken alphabetically.
DNS client: extracted from agent's DNSRegistrar with explicit nodeAddr
parameter (master registers for different nodes).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>