Parning: lådorna hittar varandra själva — design 2026-09-24
Ägarens beslut 2026-09-24: xtasken ska INTE para. Den startar maskinerna med tillräckligt med information för att de ska bilda en tunnel (WireGuard som standard, Tailscale, Mullvad eller ingen) och sedan para sig med varandra. Biblioteket ligger i edda och används av både gunnar och holger. Testas under draupnir.
1. Vad som är fel idag (MÄTT i private-gunnar-ops, 2026-09-23/24)
Laptopens xtask är äktenskapsmäklaren. Tre lådor bootar utan att veta något om varandra; xtasken gör varje presentation över ssh till fronten: bootstrap av primären med frontens konsolnyckel, tre principaler, tvillingens BecomeTwin, certifikat, speglingslista. Varje presentation var ett ställe där kedjan brast (apparat som inte lyssnade än, tvilling som panikade som PID 1, dödad körning). Laptopen är en enda felkälla mitt i en installation som borde vara lådornas egen sak.
2. Idén, i en mening
Parning = gå med i tunneln. Adresserna inne i tunneln är våra (fasta), inte molnets. Bara EN låda behöver en känd adress: fronten (publik IP ur terraform, eller namnet gunnar.rs). De andra ringer fronten; WireGuard lär sig deras endpoints ur handskakningen. Inga förberedda IP:n, inga MAC-adresser.
| låda | overlay-adress (design, fast) | roll |
| front | 10.44.0.1 | rendezvous + join-dörr + konsol |
| main | 10.44.0.2 | primär |
| twin | 10.44.0.3 | tvilling |
Tunnel = none: samma design, adresserna är då UpClouds utility-IP:n som terraform vet före installationen.
3. Flödet
- xtask (eller holgers motsvarighet) lägger maskinerna och skriver ett frö per låda (virtio-fröet som korp-installer redan läser): tunneltyp, frontens tunnelpublika nyckel och endpoint, lådans overlay-adress och roll, frontens konsol-PUBLIKA nyckel (för-inskriven admin — bootstrapfönstret är stängt från första boot), och en engångs-jointoken (slumpad, 32 byte, skriven i fröet och i frontens lista).
- Första boot: lådan präglar sina egna nycklar (tunnel + gunnar-identitet) och anropar frontens join-dörr:
{tunnelpubkey, overlay, roll, kapacitet}signerat med token. Fronten verifierar token, lägger till peer, svarar med peer-listan och den beslutade rollen. Ingen privat nyckel lämnar någonsin sin låda. - Inne i tunneln parar lådorna sig själva: primären har admin (konsolnyckeln) från start; tvillingen kör BecomeTwin + certify mot 10.44.0.2 i en loop tills det går; följaren är loopen som redan finns. Kontrollplanet binds till overlay-adressen; mTLS som idag.
- Valet (svagast = tvilling) förblir "beslutat en gång": båda gunnar-lådorna skickar kapacitet vid join; fronten beslutar en gång och lämnar rollen i svaret. Lika = lägsta overlay-adress blir tvilling. Beslutet skrivs i frontens journal och ändras aldrig.
- Signalen: varje låda rapporterar sitt parningsläge (tunnel: uppe/nere, join: gjord, roll: given, primär: nådd, följer: n av m) på sin statusdörr. xtasken LÄSER bara.
4. Biblioteket: Skidbladnir/crates/installer/nordisk-pairing (flyttad från edda 2026-09-26)
Ren Rust, inga shell-anrop (lagen). Moduler:
seed— fröformatet (TOML på virtio-fröet):tunnel = "wireguard"|"tailscale"|"mullvad"|"none",front = { pubkey, endpoint },me = { overlay, role_hint },admin_pubkey,join_token,peers_expected = 3. Läses av installern via korp-installer, skrivs av xtask/holger-ops.tunnel—trait Tunnel { fn up(&self, me: &Keys, peers: &[Peer]) -> Result<Iface>; fn add_peer(..); fn status(&self) -> TunnelStatus; }Drivrutiner:wireguard(kernelmodul via netlink —wireguard-control/boringtuni user space som reserv),tailscale(auth key i fröet, tailscaled-API),mullvad(kontonummer i fröet, WireGuard-konfig ur deras API),none(utility-IP:n rakt av). Valet är en build-thing-inställning: mediet komposeras med rätt del (kernelmodul/drivrutin).join— dörren på fronten (HTTPS på publik IP + inne i tunneln):POST /joinmed token-signatur; svar = peer-lista + roll. Klienten på lådorna: loop med backoff tills svar. Engångstoken: förbrukad efter första lyckade join, per låda.pair— tillståndsmaskinen på lådan:Booted → TunnelUp → Joined → RoleKnown → Paired(primary|twin). Primär: admin för-inskriven, väntar på tvillingen. Tvilling: BecomeTwin(10.44.0.2) → certify → följ. Varje steg idempotent och omkörbart.signal— läget som en rad + som strukturerat svar (samma sakestate pairingvisar idag, men rapporterat av lådan själv).elect— svagast-är-tvilling, beslutat på fronten av två kapacitetsrapporter.
gunnar länkar allt; holger länkar seed, tunnel, join, signal (holger har ingen tvilling idag men samma front/apparat-par).
5. Vad som blir kvar i xtask/ops
lay (terraform), frö per låda, media/installation, DNS-växling, down sist, och att LÄSA signalen tills alla tre säger Paired. Stegen elect/pair/twin försvinner.
6. Tester under draupnir
draupnir::boot_fleet_and_await_parallel med tre gäster på ett virtuellt nät (CloudInit/ seed.rs för fröna). Testet skriver tre frön med samma bibliotek, bootar, och läser signalen: PASS = tunneln uppe hos alla tre, join gjord, roller givna, tvillingen följer. Samma test för tunnel = none (utility-adresser) och för varje drivrutin som finns lokalt (WireGuard i kärnan finns på t14s; Tailscale/Mullvad-testerna kräver konto och märks så). Detta ersätter huvuddelen av mock-stegen i private-gunnar-ops.
7. Ordning
- gunnar.rs upp med dagens kedja (pågår 2026-09-24).
nordisk-pairingi edda: seed + tunnel(none, wireguard) + join + pair + signal, med draupnir-test.- gunnar: installer läser fröet, PID-1-tjänsten kör
pair; xtask krymper till §5. - holger: samma.
- Tailscale- och Mullvad-drivrutiner.
- Riv och bygg om (regeln: nya maskiner först, DNS, gamla sist).
8. Nästa version: installerarens tillfälliga mTLS (ägarens idé 2026-09-24)
Idén: installeraren har ett tillfälligt mTLS-certifikat som de installerade maskinerna känner, så att de kan kontakta installeraren, som matar dem med det de behöver.
Vad det löser
- Dagens join är ensidig: dörren prövar lådan (HMAC med estate-token), men lådan frågar över vanlig HTTP och tror på svaret. Med en tillfällig CA pinnar lådan dörrens certifikat — båda sidor är bevisade.
- Allt estate-specifikt flyttar från mediet till dörrens svar: monetize-nyckeln, klocknyckeln, konsolens admin-nyckel, overlay-adress, roll, peers, tunnelkonfig. Ett medium tjänar då varje estate och komponeras en gång; felklassen
monetize-pubkey-mismatchförsvinner och ISO-cachen träffar alltid.
Formen
- xtask präglar per ombyggnad en installer-CA (ed25519), giltig i timmar. CA:ns privata nyckel ligger på fronten och lämnar den aldrig.
- Mediet bär CA-certifikatet (publikt) och ett bootstrap-klientcert + nyckel med kort livstid och ett enda syfte: anropa join-dörren.
- Vid första join skickar lådan en CSR för en nyckel den själv präglat; dörren signerar ett riktigt certifikat per låda (samma utfärdare som redan signerar tvillingens cert). Därefter vägras bootstrap-certet.
- Slot-per-adress gäller fortfarande: ett kopierat medium på en annan adress vägras.
Förbehållet: en nyckel på mediet är bara så hemlig som mediet — ISO:n laddas upp till UpCloud-lagringen och kan läsas av den som har kontoåtkomst. Därför kort livstid, ett syfte, bunden till adress, och utbytt direkt vid första join. Aldrig en långlivad nyckel på mediet.
Var installeraren kör: på fronten, som dörren. Maskinerna når inte laptopen, men de når fronten över utility-nätet.
BYGGT 2026-09-24 (edda nordisk-pairing, mtls.rs/join.rs/door.rs): installer-CA, dörrens cert för sin utility-adress, bootstrap-cert på mediet, lådans eget cert från CSR vid första join, bootstrap spent för joinad slot och withdrawn när alla joinat; dörren matar estate-värden (feed). Fleet-testet (draupnir, tre containrar, WireGuard) grönt på mTLS på 33 s.
Ägarens precisering 2026-09-24: installerarens mTLS gäller ENDAST under bootstrap. När lådan har joinat och fått sitt eget certifikat är installerarens cert dött för den lådan, och när estatet är komplett (alla slots joinade) dras installer-CA:n tillbaka helt.
9. Tunneldrivrutinerna bakom trait Tunnel (BYGGT 2026-09-24)
Ägarens ord: "it must provide for more than wireguard; if possible use a trait, for mullvad, tailscale".
- Traiten är sömmen.
trait Tunnel { kind, public_key (default tom), up, add_peer, status }.public_keyär lådans mesh-nyckel, känd FÖREup(lådan joinar först).driver(kind)utan konfiguration,driver_for(seed, feed)med fröets publika val och dörrens matning. En typ som mediet inte byggts med vägras[tunnel-not-built]. - Fröet har valfria publika sektioner
[mullvad] { location, port }och[tailscale] { control_url }. Hemligheterna (kontonummer, auth key) står ALDRIG på mediet — dörren matar dem över mTLS vid join:feed.mullvad_account,feed.tailscale_authkey. - Mullvad (feature
mullvad, fungerar): Mullvad är en EXIT-VPN, reläerna bär inte trafik mellan klienter — den kan inte vara meshen. Därför: meshen förblir WireGuard via fronten (wg0), och varje låda är dessutom Mullvad-klient påwg1— egen nyckel registrerad på kontot (tunnr-mullvadformer + ureq-klient), relä efterlocation,tunnel_for_relay→ samma dataplan (wg-appliance-core). Luckor: standardrutten skrivs inte (ingenRTM_NEWROUTE-skrivare i sviten, F15), ingen PQ-PSK, registreringen är oprövad live (inget konto) — relälistan är provad live genom vår klient. - Tailscale (feature
tailscale): byggd men vägrar[tunnel-tailscale-unimplemented]— det saknas en ren-Rust-koordinationsklient (ts2021 Noise-IK, MapRequest-long-poll, DERP); ingen finns i sviten eller i den lokala crates-spegeln, och tailscaled kan inte köras på en OS-lös låda. Testerna nålar vägran. - Dörrens livslängder:
Door::with_box_cert_valid(standard 24 h, var fast 6 h) ochDoor::forget(name)för en ominstallerad låda (domen behålls, bootstrap öppnas för den sloten). Installer-CA:ns livslängd (anroparens) måste överleva lådornas certifikat.
10. Det konstanta mediet (ägaren 2026-09-24 kväll, BYGGT i 0.2.0)
Ägarens order: ISO:n ska vara DENSAMMA mellan installationer. Oförändrad gunnar-commit ⇒ samma sha ⇒ mediet tas över, ingen komponering, ingen uppladdning, ingen grind. Inget UpCloud-metadata eller user_data: det ENDA som matar en låda som startar är installerarens mTLS-dörr.
- Fröet bär bara det som når dörren: estate, tunneltyp, dörrens adress, installer-CA:ns certifikat, bootstrap-certet + nyckel,
peers_expected(+ de publika mullvad/tailscale-valen).Front.tunnel_pubkeyochFront.endpointär BORTA ur fröet (ett 0.1-frö med dem läses fortfarande; fälten ignoreras). - Frontens tunnel matas vid join:
Door::front(FrontTunnel { tunnel_pubkey, endpoint }), skrivet av laptopen i dörrens ledger när installeraren förbereds, listas först i varje svarspeerssom peernfrontmed roll front och endpoint (JoinReply::front()). Relänyckeln präglas per uppläggning, på fronten; den står aldrig på mediet. - Installer-CA:n är per MEDIUM, inte per uppläggning: präglas av laptopen, sparas bredvid mediets kvitto (0600) och skjuts till varje ny fronts dörr. Lång livstid (anroparens val; private-gunnar-ops väljer 180 dagar, ny CA — och därmed nytt medium — när under 30 dagar återstår). "Giltig endast under bootstrap" upprätthålls av DÖRREN (
spentper joinad slot,withdrawnnär ledgern är komplett), inte av ett kort utgångsdatum.mtls::cert_leftläser vad ett certifikat har kvar. - Dörrens certifikat bakåtdateras ett dygn (
DOOR_BACKDATE): lådan prövar det med en klocka som ingen ställt än (klockan matas av dörren EFTER join). Dörrens cert lever lika länge som CA:n. - Fleet-testet (draupnir, tre containrar, WireGuard via reläet) grönt med frontnyckeln tagen ur svaret i stället för fröet.
Kvar hos gunnar/ops (samma dag): gunnar skriver wg0.conf ur den matade fronten; mediet bär monetize_pubkey=fed och ingen klocka; dörrens adress deklareras i estate.toml [installer] door_ip så att mediet kan komponeras innan fronten finns, och alla tre maskiner startas samtidigt. Öppen ägarfråga: om UpCloud alls lägger en server på en VALD utility-adress, och att den förra fronten håller sin adress tills down (riv sist) — se ops ANALYS-INSTALL-XTASK-2026-09-24.md.
11. Erbjudandet: det lådan ger installeraren (0.3.1, 2026-09-25)
Installeraren matar lådorna (POST /install/feed); 0.3.1 lägger till den andra riktningen. GET /install/offer svarar med det lådan ERBJUDER — publika värden som bara den kan prägla: primärens ticket-nyckel (som operatörens verktyg för vidare till monetize), tvillingens replikeringsnyckel och signeringsbegäran, certifikatet primären signerade åt tvillingen.
Vm::offer()(standard: tomt) på lådan;Client::offer()hos installeraren (en låda utan vägen, 404, är ett tomt erbjudande).- Installeraren lägger varje erbjudet värde i dörrens matning som
offer.<vm>.<nyckel>(bootstrap::fed_offer_key, tillbaka medbootstrap::offers_in) — nästa svar till varje låda skiljer sig då och matas igen — och säger det tillSink::offer.FolderSinkskriveroffers.toml(installer::read_offers). - Så byter två lådor som ännu inte når varandra det bara de kan prägla, med installeraren, som redan är betrodd åt båda hållen, som bärare. Aldrig en privat nyckel: erbjudandet loggas.