From e74dd405d629e71a7014ad7d8caba446500acdbe Mon Sep 17 00:00:00 2001 From: Lior Date: Tue, 26 May 2026 01:52:16 -0400 Subject: [PATCH 1/2] =?UTF-8?q?feat(B-0792=20iter-5.2.1):=20auto-generate?= =?UTF-8?q?=20node-<6hex>=20hostname=20when=20--host=20not=20specified=20?= =?UTF-8?q?=E2=80=94=20operator=20can=20rename=20later=20via=20digital-twi?= =?UTF-8?q?n=20substrate=20(Aaron=202026-05-26)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The maintainer 2026-05-26 architectural framing: > "can we have it auto generate the host name we can change > later via digital twin after it self registers" Composes iter-5.2 (--host injection mechanism) with B-0794 (node self-registration / digital-twin substrate). Zero-typing default for operators who don't want to think about names at flash time; rename later via editing the digital-twin node-config YAML once self-registration substrate (B-0794) ships. Implementation: when operator runs `zflash` without --host flag, generate `node-<6hex>` from 3 random bytes (Web Crypto getRandomValues). 24-bit entropy = ~16M possible names → negligible collision risk for any homelab cluster size + mDNS uniqueness preserved per-node. Operator-named hostnames (via --host) take priority; auto-gen only fires when --host omitted AND --no-inject NOT set (no ESP write to carry the name anyway in --no-inject path). Generated name logged CLEARLY pre-flash so operator knows what to ssh to post-install: iter-5.2.1: --host not specified; auto-generated hostname: node-a3f9c2 (rename later via digital-twin substrate per B-0794) cluster will be reachable as: ssh zeta@node-a3f9c2.local Composes with iter-5.1+5.2 (#5103 merged at 6ee3a29d) + B-0794 self-registration target. Future iter-5.4+ can extend the auto-gen with memorable-name dictionaries (pokemon, gemstones, fruits, etc.) once the framework is empirically validated. Co-Authored-By: Claude Opus 4.7 --- full-ai-cluster/tools/zflash.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/full-ai-cluster/tools/zflash.ts b/full-ai-cluster/tools/zflash.ts index 46b0c486b9..531c8982ee 100755 --- a/full-ai-cluster/tools/zflash.ts +++ b/full-ai-cluster/tools/zflash.ts @@ -900,6 +900,38 @@ async function main() { // Pre-flight: determine if iter-4.2 inject will run + which key const pubkeyPath = sshKeyOverride ?? DEFAULT_SSH_KEY; + + // iter-5.2.1 (B-0792): if operator didn't pass --host, auto-generate + // a random unique hostname `node-<6hex>` (24-bit entropy = ~16M + // possible names, negligible collision risk for any homelab cluster + // size; node-by-node mDNS uniqueness preserved). Operator can rename + // later via the digital-twin substrate per B-0794 self-registration: + // edit the node-config YAML in maintainers//cluster-nodes// + // → ArgoCD reconciles → hostname updates. + // + // The maintainer 2026-05-26: "can we have it auto generate the host + // name we can change later via digital twin after it self registers." + // + // Auto-gen happens only when --host was NOT passed (preserves + // operator intent when they did pick a name). The generated name is + // logged CLEARLY pre-flash so the operator knows what to ssh to + // post-install. Skipped entirely when --no-inject is set (no ESP + // write to carry the name anyway). + if (hostOverride === null && !noInject) { + // Web Crypto: 3 random bytes → 6 hex chars; node-XXXXXX. Prefix + // `node-` keeps the namespace clean (operator-named hosts can + // avoid the `node-` prefix to distinguish from auto-named). + const rand = new Uint8Array(3); + crypto.getRandomValues(rand); + const hex = Array.from(rand, (b) => b.toString(16).padStart(2, "0")).join(""); + hostOverride = `node-${hex}`; + process.stdout.write( + `\niter-5.2.1: --host not specified; auto-generated hostname: ${hostOverride}\n` + + ` (rename later via digital-twin substrate per B-0794)\n` + + ` cluster will be reachable as: ssh zeta@${hostOverride}.local\n\n`, + ); + } + let willInject = !noInject; if (willInject && !existsSync(pubkeyPath)) { process.stderr.write( From 901e80472610e2600cb6f23d92acc044f99ac61b Mon Sep 17 00:00:00 2001 From: Lior Date: Tue, 26 May 2026 02:00:16 -0400 Subject: [PATCH 2/2] fix(iter-5.2.1): gate auto-hostname on willInject + drop concrete path to unimplemented B-0794 substrate (Copilot P0 + P1 on #5107) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - P0 race: auto-gen was running BEFORE pubkey-existence check that sets willInject=false. If pubkey missing, willInject becomes false but auto-gen already happened + already printed "ssh zeta@node-XXXXXX.local" — misleading the operator since the hostname never gets written to the USB ESP. Move auto-gen AFTER willInject finalized + gate on willInject (was: gate on !noInject which doesn't account for missing-pubkey path). - P1 misleading path: comment referenced `maintainers//cluster-nodes//` which doesn't exist yet (B-0794 substrate not implemented; current maintainers/aaron/ only has legal-entities/). Reword to point at B-0794 backlog row instead of a concrete-but-fictional path. Same reword in the printed operator-facing line. Co-Authored-By: Claude Opus 4.7 --- full-ai-cluster/tools/zflash.ts | 38 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/full-ai-cluster/tools/zflash.ts b/full-ai-cluster/tools/zflash.ts index 531c8982ee..66325d16aa 100755 --- a/full-ai-cluster/tools/zflash.ts +++ b/full-ai-cluster/tools/zflash.ts @@ -900,24 +900,34 @@ async function main() { // Pre-flight: determine if iter-4.2 inject will run + which key const pubkeyPath = sshKeyOverride ?? DEFAULT_SSH_KEY; + let willInject = !noInject; + if (willInject && !existsSync(pubkeyPath)) { + process.stderr.write( + `\nzflash: iter-4.2 inject skipped — pubkey not found at ${pubkeyPath}\n` + + ` (proceeding with flash; cluster node will need manual operator-ssh-keys.nix\n` + + ` edit + nixos-rebuild on first login per iter-4 v1 fallback)\n\n`, + ); + willInject = false; + } // iter-5.2.1 (B-0792): if operator didn't pass --host, auto-generate // a random unique hostname `node-<6hex>` (24-bit entropy = ~16M // possible names, negligible collision risk for any homelab cluster // size; node-by-node mDNS uniqueness preserved). Operator can rename - // later via the digital-twin substrate per B-0794 self-registration: - // edit the node-config YAML in maintainers//cluster-nodes// - // → ArgoCD reconciles → hostname updates. + // later via the digital-twin substrate planned under B-0794 (node + // self-registration; not yet shipped — see B-0794 row for the + // target node-config substrate that will host the rename mechanism). // // The maintainer 2026-05-26: "can we have it auto generate the host // name we can change later via digital twin after it self registers." // // Auto-gen happens only when --host was NOT passed (preserves - // operator intent when they did pick a name). The generated name is - // logged CLEARLY pre-flash so the operator knows what to ssh to - // post-install. Skipped entirely when --no-inject is set (no ESP - // write to carry the name anyway). - if (hostOverride === null && !noInject) { + // operator intent when they did pick a name) AND when iter-4.2 + // inject will actually run (gated on `willInject` so we never + // promise an ssh target for a hostname that won't be written to + // the USB ESP — finalized AFTER the pubkey existence check so + // missing-pubkey path doesn't print a misleading ssh promise). + if (hostOverride === null && willInject) { // Web Crypto: 3 random bytes → 6 hex chars; node-XXXXXX. Prefix // `node-` keeps the namespace clean (operator-named hosts can // avoid the `node-` prefix to distinguish from auto-named). @@ -927,21 +937,11 @@ async function main() { hostOverride = `node-${hex}`; process.stdout.write( `\niter-5.2.1: --host not specified; auto-generated hostname: ${hostOverride}\n` + - ` (rename later via digital-twin substrate per B-0794)\n` + + ` (rename later via B-0794 digital-twin substrate when shipped)\n` + ` cluster will be reachable as: ssh zeta@${hostOverride}.local\n\n`, ); } - let willInject = !noInject; - if (willInject && !existsSync(pubkeyPath)) { - process.stderr.write( - `\nzflash: iter-4.2 inject skipped — pubkey not found at ${pubkeyPath}\n` + - ` (proceeding with flash; cluster node will need manual operator-ssh-keys.nix\n` + - ` edit + nixos-rebuild on first login per iter-4 v1 fallback)\n\n`, - ); - willInject = false; - } - // Stdio inherit — child handles all I/O directly (readline, sudo Touch ID // PAM prompt, dd progress). We are a thin invocation wrapper. const flashUsbArgs = willInject