Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions docs/reference/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3121,7 +3121,6 @@ For contributor guidance on how these command files are structured, refer to
| `$$nemoclaw internal uninstall plan` | `uninstall.sh` / `$$nemoclaw uninstall` | Build a deterministic uninstall plan without applying it. |
| `$$nemoclaw internal uninstall run-plan` | `uninstall.sh` / `$$nemoclaw uninstall` | Remove host-side NemoClaw resources from a previously built plan. |
| `$$nemoclaw internal uninstall classify-shim` | `uninstall.sh` / `$$nemoclaw uninstall` | Classify whether a shim path is safe for the uninstaller to remove. |
| `$$nemoclaw internal dns setup-proxy` | onboarding / sandbox setup | Configure the DNS forwarder bridge inside a sandbox pod. |
| `$$nemoclaw internal dns fix-coredns` | onboarding / sandbox setup | Patch CoreDNS to use a non-loopback upstream resolver. |
| `$$nemoclaw internal dev npm-link-or-shim` | `scripts/npm-link-or-shim.sh` (development) | Run `npm link`, falling back to a user-local NemoClaw development shim. |

Expand Down Expand Up @@ -3564,7 +3563,7 @@ The following flags change defaults for commands that manage existing sandboxes.
|----------|--------|--------|
| `NEMOCLAW_CLEANUP_GATEWAY` | `1`, `true`, or `yes` to enable; `0`, `false`, or `no` to disable | Overrides the platform default (macOS unattended: cleanup; Linux/Windows: preserve) for whether `$$nemoclaw <name> destroy` removes the shared gateway when destroying the last sandbox. Command-line `--cleanup-gateway` and `--no-cleanup-gateway` still take precedence. |
| `NEMOCLAW_CONFIRM_LEGACY_MANAGED_RECREATE` | Exact JSON array of sandbox names | Confirms to the installer that the exact listed set of pre-fingerprint OpenClaw or Hermes sandboxes used NemoClaw-managed images, allowing recovery onto the current managed image. The normalized names must exactly match the installer's printed array. Set it only after verifying every named sandbox. Recorded custom-image evidence remains blocked. |
| `NEMOCLAW_DISABLE_INFERENCE_ROUTE_REPAIR` | `1` to enable | Skips the automatic DNS-proxy repair for stale `inference.local` routes during `$$nemoclaw <name> connect` and `$$nemoclaw <name> connect --probe-only`. Use only as a troubleshooting escape hatch. |
| `NEMOCLAW_DISABLE_INFERENCE_ROUTE_REPAIR` | `1` to enable | Skips automatic route recovery for stale `inference.local` routes during `$$nemoclaw <name> connect` and `$$nemoclaw <name> connect --probe-only`. Use only as a troubleshooting escape hatch. |
| `NEMOCLAW_DISABLE_SUPERVISOR_RELAUNCH` | `1` to enable | Skips the automatic trusted container recreation during `$$nemoclaw <name> recover` when two managed scans find no supervisor while PID 1 remains stable. Use only as a troubleshooting escape hatch; recovery then falls back to the rebuild or re-onboard guidance. |
| `NEMOCLAW_SHIELDS_ACCEPT_LEGACY_BASELINE` | `1` to opt in | Allows advanced immutable-config verification to trust the current on-disk bytes for older or partial content baselines. Use only after you have rebuilt or manually inspected the sandbox state and accepted that the baseline is operator-approved. |
| `NEMOCLAW_SHIELDS_SETTLE_MS` | milliseconds (default `750`, clamped to `0` to `10000`) | Settle window NemoClaw waits after re-applying a config lockdown (during shields auto-restore and `$$nemoclaw <name> shields up` drift remediation) before re-confirming the lock still holds. Detects when an in-sandbox reconciler changes config file permissions after lockdown and re-applies the lock; if NemoClaw cannot re-confirm the lock within the retry budget, shields stay down. This narrows the window in which a reconciler can revert permissions rather than eliminating it. The best-effort `chattr +i` immutable bit remains the only fully durable lock. Raise it on hosts where the gateway settles slowly. |
Expand Down
2 changes: 1 addition & 1 deletion scripts/checks/legacy-sandbox-transports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..
type ReviewedSiteTuple = readonly [string, LegacySandboxTransportKind, number];

const REVIEWED_SITE_TUPLES = [
["src/lib/actions/dns/index.ts", "docker-exec-command", 4],
["src/lib/actions/dns/index.ts", "docker-exec-command", 2],
["src/lib/actions/sandbox/host-aliases.ts", "docker-exec-command", 1],
["src/lib/actions/sandbox/sessions/passthrough.ts", "grpc-cli-read-only-fallback", 1],
["src/lib/actions/sandbox/process-recovery.ts", "privileged-sandbox-exec", 2],
Expand Down
24 changes: 0 additions & 24 deletions scripts/setup-dns-proxy.sh

This file was deleted.

30 changes: 0 additions & 30 deletions src/commands/internal/dns/setup-proxy.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/lib/actions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ src/lib/actions/<area>/<verb>.test.ts
Examples:

```text
dns/index.ts # internal dns fix-coredns/setup-proxy orchestration
dns/index.ts # internal dns fix-coredns orchestration
dev/npm-link-or-shim.ts # prepare-time dev shim orchestration
installer/plan.ts # deterministic installer planning
uninstall/plan.ts # host uninstall planning
Expand Down
110 changes: 1 addition & 109 deletions src/lib/actions/dns/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import { describe, expect, it, vi } from "vitest";
import type { CommandResult } from "./index";
import { runFixCoreDns, runSetupDnsProxy } from "./index.js";
import { runFixCoreDns } from "./index.js";

function ok(stdout = ""): CommandResult {
return { status: 0, stdout, stderr: "" };
Expand Down Expand Up @@ -157,111 +157,3 @@ describe("runFixCoreDns", () => {
expect(result.message).toBe("patch failed");
});
});

describe("runSetupDnsProxy", () => {
it("configures the DNS proxy through kubectl-in-docker argv calls", () => {
const calls: string[][] = [];
const log = vi.fn();
const runDocker = vi.fn((args: string[]) => {
calls.push(args);
const cmd = args.join(" ");
if (args[0] === "ps") return ok("openshell-cluster-nemoclaw\n");
if (cmd.includes("get service kube-dns")) return ok("10.43.0.10");
if (cmd.includes("get endpoints kube-dns")) return ok("10.42.0.15");
if (cmd.includes("get pods -n openshell -o name")) return ok("pod/box[1]-abc\n");
if (cmd.includes("ip addr show")) return ok("10.200.0.1\n");
if (cmd.includes("cat /tmp/dns-proxy.pid")) return ok("12345\n");
if (cmd.includes("cat /tmp/dns-proxy.log"))
return ok("dns-proxy: 10.200.0.1:53 -> 10.43.0.10:53 pid=12345\n");
if (cmd.includes("python3 -c")) return ok("ok");
if (cmd.includes("ls /run/netns/")) return ok("sandbox-ns\n");
if (cmd.includes("test -x")) return ok();
if (cmd.includes("cat /etc/resolv.conf")) return ok("nameserver 10.200.0.1\n");
if (cmd.includes("getent hosts github.com")) return ok("140.82.112.4 github.com\n");
return ok();
});

const result = runSetupDnsProxy(
{ gatewayName: "nemoclaw", sandboxName: "box[1]" },
{
env: { DOCKER_HOST: "unix:///tmp/fake-docker.sock" },
log,
runDocker,
sleep: vi.fn(),
},
);

expect(result.exitCode).toBe(0);
expect(result.dnsUpstream).toBe("10.43.0.10");
expect(result.pod).toBe("box[1]-abc");
expect(result.verificationPass).toBe(4);
expect(calls.some((args) => args.join(" ").includes("get service kube-dns"))).toBe(true);
expect(calls.some((args) => args.join(" ").includes("get endpoints kube-dns"))).toBe(false);
expect(calls.some((args) => args.includes("box[1]-abc"))).toBe(true);
expect(
calls.some((args) =>
args.join(" ").includes("nohup python3 -u /tmp/dns-proxy.py '10.43.0.10' '10.200.0.1'"),
),
).toBe(true);
expect(log).toHaveBeenCalledWith(" DNS verification: 4 passed, 0 failed");
});

it("falls back to the CoreDNS pod endpoint when the kube-dns service IP is unavailable", () => {
const calls: string[][] = [];
const runDocker = vi.fn((args: string[]) => {
calls.push(args);
const cmd = args.join(" ");
if (args[0] === "ps") return ok("openshell-cluster-nemoclaw\n");
if (cmd.includes("get service kube-dns")) return ok("");
if (cmd.includes("get endpoints kube-dns")) return ok("10.42.0.15");
if (cmd.includes("get pods -n openshell -o name")) return ok("pod/box-abc\n");
if (cmd.includes("ip addr show")) return ok("10.200.0.1\n");
if (cmd.includes("cat /tmp/dns-proxy.pid")) return ok("12345\n");
if (cmd.includes("cat /tmp/dns-proxy.log"))
return ok("dns-proxy: 10.200.0.1:53 -> 10.42.0.15:53 pid=12345\n");
if (cmd.includes("python3 -c")) return ok("ok");
if (cmd.includes("ls /run/netns/")) return ok("sandbox-ns\n");
if (cmd.includes("test -x")) return ok();
if (cmd.includes("cat /etc/resolv.conf")) return ok("nameserver 10.200.0.1\n");
if (cmd.includes("getent hosts github.com")) return ok("140.82.112.4 github.com\n");
return ok();
});

const result = runSetupDnsProxy(
{ gatewayName: "nemoclaw", sandboxName: "box" },
{
env: { DOCKER_HOST: "unix:///tmp/fake-docker.sock" },
log: vi.fn(),
runDocker,
sleep: vi.fn(),
},
);

expect(result.exitCode).toBe(0);
expect(result.dnsUpstream).toBe("10.42.0.15");
expect(calls.some((args) => args.join(" ").includes("get service kube-dns"))).toBe(true);
expect(calls.some((args) => args.join(" ").includes("get endpoints kube-dns"))).toBe(true);
});

it("rejects unsafe DNS upstreams before launching the forwarder", () => {
const calls: string[][] = [];
const result = runSetupDnsProxy(
{ gatewayName: "nemoclaw", sandboxName: "box" },
{
env: { DOCKER_HOST: "unix:///tmp/fake-docker.sock" },
runDocker: (args) => {
calls.push(args);
const cmd = args.join(" ");
if (args[0] === "ps") return ok("openshell-cluster-nemoclaw\n");
if (cmd.includes("get service kube-dns")) return ok("bad;rm\n");
return ok();
},
sleep: vi.fn(),
},
);

expect(result.exitCode).toBe(1);
expect(result.message).toContain("contains invalid characters");
expect(calls.some((args) => args.join(" ").includes("dns-proxy.py"))).toBe(false);
});
});
Loading