mirror of
https://github.com/warmbly/warmbly.git
synced 2026-08-26 00:00:41 +00:00
5463e3986e
Hetzner CX32 + 16 Primary IPs becomes 16 sending identities with one
install command, without expanding ops complexity.
cmd/worker/main.go: WORKER_ID now resolves via 4-tier precedence:
1. WORKER_ID env (explicit UUID)
2. WORKER_BIND_IP env (derive UUIDv5 from the bound IP)
3. hostname-as-UUID (legacy single-IP VPS)
4. generated UUID (local dev fallback)
Boot also constructs the chosen Codec + EventBus + EncryptedKeyStore
via the FromEnv factories from earlier commits, so a worker process is
fully configured by its envelope env file plus the runtime config it
pulls from the backend on first boot.
scripts/install-worker.sh gains --ips <ipv4,ipv4,...> which:
- writes a warmbly-worker@.service systemd template
- drops a per-instance env file at /etc/warmbly/instances/<dashed-ip>.env
with WORKER_BIND_IP and WORKER_ID
- shares one /etc/warmbly/worker.env for the common config
- --status, --update, --uninstall now multi-IP aware
- single-IP mode preserved when --ips is absent
5 worker tests pin the UUIDv5 derivation against the installer's
uuidgen --sha1 output so the two never drift.
docs/MULTI_IP_WORKERS.md is the operator runbook with the Hetzner
recipe, OS-level IP attachment, rDNS automation, day-2 ops, and the
25%-of-fleet blast-radius rule.
95 lines
2.6 KiB
Go
95 lines
2.6 KiB
Go
// Verifies that the worker derives the same UUIDv5 from an IP as the install
|
|
// script does. If these ever drift, every worker installed on a multi-IP box
|
|
// would come up with a different ID than the one the installer registered,
|
|
// silently breaking topic subscription and assignment lookups.
|
|
//
|
|
// The expected UUIDs below were generated with the same command the installer
|
|
// runs:
|
|
//
|
|
// uuidgen --sha1 --namespace 6ba7b811-9dad-11d1-80b4-00c04fd430c8 --name <ip>
|
|
//
|
|
// Equivalently:
|
|
//
|
|
// python3 -c "import uuid; print(uuid.uuid5(uuid.UUID('6ba7b811-9dad-11d1-80b4-00c04fd430c8'), '<ip>'))"
|
|
|
|
package main
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
func TestWorkerIDFromIP_Deterministic(t *testing.T) {
|
|
cases := []struct {
|
|
ip string
|
|
want string
|
|
}{
|
|
// Verified via `uuidgen --sha1 --namespace 6ba7b811-9dad-11d1-80b4-00c04fd430c8 --name <ip>`.
|
|
{"1.2.3.4", "47ffd7ee-a509-5b1e-a3d2-993b6a56fe65"},
|
|
{"127.0.0.1", "9a4a6d7c-f348-5828-8dfc-db6d3d417ef0"},
|
|
{"203.0.113.10", "016a4ab6-01b5-59ed-9fe5-3f4e8254099b"},
|
|
{"192.168.1.1", "4f98548b-3e6a-5a9d-bc92-47359ed6e2d5"},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.ip, func(t *testing.T) {
|
|
got := workerIDFromIP(tc.ip).String()
|
|
if got != tc.want {
|
|
t.Fatalf("workerIDFromIP(%q) = %s, want %s", tc.ip, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWorkerIDFromIP_StableAcrossCalls(t *testing.T) {
|
|
a := workerIDFromIP("10.0.0.1")
|
|
b := workerIDFromIP("10.0.0.1")
|
|
if a != b {
|
|
t.Fatalf("non-deterministic: %s != %s", a, b)
|
|
}
|
|
}
|
|
|
|
func TestResolveWorkerID_ExplicitID(t *testing.T) {
|
|
want := uuid.MustParse("11111111-2222-3333-4444-555555555555")
|
|
t.Setenv("WORKER_ID", want.String())
|
|
t.Setenv("WORKER_BIND_IP", "")
|
|
|
|
got, bind := resolveWorkerID()
|
|
if got != want {
|
|
t.Fatalf("got id %s, want %s", got, want)
|
|
}
|
|
if bind != "default route" {
|
|
t.Fatalf("got bind %q, want %q", bind, "default route")
|
|
}
|
|
}
|
|
|
|
func TestResolveWorkerID_ExplicitIDWithBind(t *testing.T) {
|
|
want := uuid.MustParse("11111111-2222-3333-4444-555555555555")
|
|
t.Setenv("WORKER_ID", want.String())
|
|
t.Setenv("WORKER_BIND_IP", "1.2.3.4")
|
|
|
|
got, bind := resolveWorkerID()
|
|
if got != want {
|
|
t.Fatalf("got id %s, want %s", got, want)
|
|
}
|
|
if bind != "1.2.3.4" {
|
|
t.Fatalf("got bind %q, want %q", bind, "1.2.3.4")
|
|
}
|
|
}
|
|
|
|
func TestResolveWorkerID_FromBindIP(t *testing.T) {
|
|
// Unset WORKER_ID so the bind-IP branch runs.
|
|
os.Unsetenv("WORKER_ID")
|
|
t.Setenv("WORKER_BIND_IP", "1.2.3.4")
|
|
|
|
got, bind := resolveWorkerID()
|
|
want := uuid.MustParse("47ffd7ee-a509-5b1e-a3d2-993b6a56fe65")
|
|
if got != want {
|
|
t.Fatalf("got id %s, want %s", got, want)
|
|
}
|
|
if bind != "1.2.3.4" {
|
|
t.Fatalf("got bind %q, want %q", bind, "1.2.3.4")
|
|
}
|
|
}
|