Skip to content

Commit 772ce00

Browse files
fix(onboard): create portable network before host alias (#9578)
<!-- markdownlint-disable MD041 --> ## Summary This pull request (PR) fixes portable rootless onboarding after the host gateway address blocked Podman network creation. Portable host preparation now creates or validates the network before it adds the overlapping loopback address. ## Related Issue Fixes #9577. ## E2E Root Cause - E2E root cause: `portable host preparation / rootless Podman network creation / host loopback address reserves the portable network subnet` - Source run: [automatic run 32220977895](https://github.com/NVIDIA/NemoClaw/actions/runs/32220977895) (run 32220977895, attempt 1) - Failed jobs: [rootless-linux 95971243086](https://github.com/NVIDIA/NemoClaw/actions/runs/32220977895/job/95971243086) and [portable-launch 95971243205](https://github.com/NVIDIA/NemoClaw/actions/runs/32220977895/job/95971243205) - Signature: `Creating the portable sandbox network failed: subnet 169.254.1.0/24 is already used on the host` - Scope: one root cause ## Base SHA Reconciliation Latest PR commit `c85831dce4cd5b1e1d5c218aa1d90c6f6309dab3` is an ordinary signed merge with ordered parents [`c9499420d566ef4ff0c1fcc4654d1cd3f5456b0a`, `0ac5a7cd0565ca8aac32fab1a22603da459a4158`]. PR base SHA `0ac5a7cd0565ca8aac32fab1a22603da459a4158` adds #9493 after previous base SHA `2c74cbb87c35d1ec8700c0c4264a0fcc03c715c2`. #9493 changes only the live registry-target test and its E2E-support registry-discovery test. It does not change the two files in this PR or the portable-host preparation boundary. The net PR diff remains the production portable-host preparation file and its focused test. Automatic exact-base [E2E run 32262846265](https://github.com/NVIDIA/NemoClaw/actions/runs/32262846265) completed with 11 passed and 27 expected skipped jobs. Its substantive failure is the separate, already owned and decision-blocked #9280 launch-readiness publication contract; the Relevant E2E job failed downstream. ## Changes - Add a stateful host-command regression test that models the rootless Podman subnet conflict. - Separate portable network preparation from registry-container preparation. - Order portable host effects as network preparation, host gateway configuration, then registry-container preparation. ## Type of Change - [x] Code change (feature, bug fix, or refactor) - [ ] Code change with doc updates - [ ] Doc only (prose changes, no code sample modifications) - [ ] Doc only (includes code sample changes) ## Quality Gates - [x] Tests added or updated for changed behavior - [ ] Existing tests cover changed behavior — justification: - [ ] Tests not applicable — justification: - [x] Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging) - [x] Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: independent exact-`c85831dce4cd5b1e1d5c218aa1d90c6f6309dab3` correctness, nine-category security, and documentation reviews passed; automated and maintainer review remain pending - [x] Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue: automatic exact-base [E2E run 32262846265](https://github.com/NVIDIA/NemoClaw/actions/runs/32262846265) failed the separate #9280 launch-readiness publication contract; this reconciliation authorization accepts that owned failure as unrelated to #9578 ## DGX Station Hardware Evidence - [ ] Tested on DGX Station - Tested commit: - Station profile/scenario: - Result: - Supporting evidence: ## Security and Documentation Review - The network name, subnet, registry address, Podman socket authority, privileged command, and fail-closed error paths are unchanged. - A network-creation failure stops before the host address or registry container changes. A host-address failure stops before registry-container mutation. - No credential, workflow, dependency, cryptography, redaction, cleanup, or authorization path changes. - No documentation change is required. Existing documentation already describes the final portable gateway and registry topology; this PR changes only the internal order that reaches it. ## Additional Local Evidence - Before the production fix, the new regression test failed 1/1 with `Creating the portable sandbox network failed: subnet 169.254.1.0/24 is already used on the host`. - A four-file topology command also ran `src/lib/onboard/docker-driver-gateway-env.test.ts`. Two unrelated tests encountered durable `/tmp/nemoclaw-gateway` state from another process and stopped at the existing generated-gateway identity gate. The shared network-authority test passed separately, and this PR does not change that gateway state or identity path. ## Credential-Free Live Evidence - Latest PR commit c85831d preserves the portable-host blobs from credential-free live commit 7d94953, which created the openshell-docker network with subnet 169.254.1.0/24 before it added host loopback address 169.254.1.2/32. - The live run did not reproduce the #9577 subnet collision. Rootless Podman remained the lifecycle authority, and Docker state did not change. - A later Hermes probe exposed a separate address-authority defect tracked by #9587. This PR does not include that defect or its remediation. ## Verification - [x] PR description includes a `Signed-off-by:` line and every commit appears as `Verified` in GitHub - [x] Normal `pre-commit`, `commit-msg`, and `pre-push` hooks passed, or `npm run validate:pr` passed after refreshing `origin/main` when hooks were skipped or unavailable - [x] Targeted behavior tests pass for the current change set, or tests are marked not applicable above — command/result or justification: `npm exec -- vitest run --project cli src/lib/onboard/experimental/portable-host-preparation.test.ts src/lib/onboard/gateway-sandbox-reachability.test.ts src/lib/onboard/host-service-reachability.test.ts` passed 106/106 on latest PR commit `c85831dce4cd5b1e1d5c218aa1d90c6f6309dab3`; the isolated network-order regression passed 1/1; the #9579 installer fixture passed 3/3; the #9518 provider/local-inference and #9507 Dockerfile-patch compatibility suite passed 153/153; the #9493 E2E-support registry-discovery suite passed 11/11; the #9561 DCode compatibility suites passed 53/53 and 139/139; the #9512 shared-state compatibility suite passed 74/74 - [ ] Applicable broad gate passed — `npm test` for broad runtime/test-harness changes; `npm run check` for repo-wide validation/coverage changes — command/result: `npm run test:changed` passed growth guardrails 32/32 and found no additional changed-project test files beyond the explicit focused suites; `npm run typecheck:cli`, `git diff --check`, and `npm run validate:pr` passed; the net change remains one onboarding order and its regression test, so `npm test` was not run locally - [x] Quality Gates section completed with required justifications or waivers - [x] No secrets, API keys, or credentials committed - [ ] `npm run docs` builds without warnings (doc changes only) - [ ] Doc pages follow the [style guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md) (doc changes only) - [ ] New doc pages include SPDX header and frontmatter (new pages only) --- Signed-off-by: Julie Yaunches <jyaunches@nvidia.com> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved portable host preparation by creating and validating the sandbox network before configuring the host gateway. * Ensured registry setup occurs only after network and gateway preparation is complete. * **Tests** * Added regression coverage verifying the correct order of network creation, gateway configuration, and registry operations. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Julie Yaunches <jyaunches@nvidia.com> Signed-off-by: Senthil Ravichandran <senthilr@nvidia.com> Co-authored-by: Senthil Ravichandran <senthilr@nvidia.com>
1 parent 0ac5a7c commit 772ce00

2 files changed

Lines changed: 58 additions & 1 deletion

File tree

src/lib/onboard/experimental/portable-host-preparation.test.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,56 @@ describe("preparePortableExperimentalHost", () => {
423423
expect(sudo).not.toHaveBeenCalled();
424424
});
425425

426+
it("creates the portable network before adding its host gateway address (#9577)", () => {
427+
const home = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-portable-"));
428+
tempDirs.push(home);
429+
let hostGatewayConfigured = false;
430+
const commands: string[] = [];
431+
const docker = vi.fn<(args: readonly string[], env: NodeJS.ProcessEnv) => SpawnResult>(
432+
(args) => {
433+
switch (args.slice(0, 2).join(" ")) {
434+
case "--version":
435+
return result();
436+
case "network inspect":
437+
return result(1);
438+
case "network create":
439+
commands.push("create network");
440+
return hostGatewayConfigured
441+
? result(125, `subnet ${PORTABLE_DOCKER_NETWORK_SUBNET} is already used on the host`)
442+
: result();
443+
case "inspect --format":
444+
return result(1);
445+
case "run -d":
446+
return result();
447+
default:
448+
return result(1, `unexpected docker command: ${args.join(" ")}`);
449+
}
450+
},
451+
);
452+
const ip = vi.fn(() =>
453+
result(
454+
0,
455+
hostGatewayConfigured
456+
? `1: lo inet ${PORTABLE_HOST_GATEWAY_IP}/32 scope global lo\n`
457+
: "1: lo inet 127.0.0.1/8 scope host lo\n",
458+
),
459+
);
460+
const sudo = vi.fn(() => {
461+
commands.push("add host gateway");
462+
hostGatewayConfigured = true;
463+
return result();
464+
});
465+
466+
preparePortableExperimentalHost(
467+
{ NEMOCLAW_EXPERIMENTAL_PROFILE: "portable" },
468+
portablePreparationDeps(home, docker, { ip, sudo }),
469+
undefined,
470+
{ simulateExistingPortableNetwork: false },
471+
);
472+
473+
expect(commands).toEqual(["create network", "add host gateway"]);
474+
});
475+
426476
it("configures and verifies the portable gateway loopback alias before registry mutation (#9461)", () => {
427477
const home = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-portable-"));
428478
tempDirs.push(home);

src/lib/onboard/experimental/portable-host-preparation.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ function validateOwnedConfigAuthority(input: {
340340
if (socketPath) assertOwnedDescendants(runtimeDir, path.dirname(socketPath));
341341
}
342342

343-
function ensureRegistryContainer(
343+
function ensurePortableSandboxNetwork(
344344
env: NodeJS.ProcessEnv,
345345
docker: NonNullable<PortableHostPreparationDeps["docker"]>,
346346
networkName: string,
@@ -367,7 +367,13 @@ function ensureRegistryContainer(
367367
"Creating the portable sandbox network",
368368
);
369369
}
370+
}
370371

372+
function ensureRegistryContainer(
373+
env: NodeJS.ProcessEnv,
374+
docker: NonNullable<PortableHostPreparationDeps["docker"]>,
375+
networkName: string,
376+
): void {
371377
const inspection = docker(
372378
[
373379
"inspect",
@@ -637,6 +643,7 @@ export function preparePortableExperimentalHost(
637643
env: childEnv,
638644
timeout: HOST_COMMAND_TIMEOUT_MS,
639645
}));
646+
ensurePortableSandboxNetwork(podmanEnv, docker, dockerNetworkName);
640647
ensurePortableHostGatewayAlias(podmanEnv, ip, sudo);
641648
ensureRegistryContainer(podmanEnv, docker, dockerNetworkName);
642649
if (socketAuthority) {

0 commit comments

Comments
 (0)