Skip to content
Closed
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
efb5fa7
fix(onboard): grant Jetson nvmap group write access
sandl99 Aug 3, 2026
09bef64
docs(troubleshooting): simplify Jetson nvmap guidance
sandl99 Aug 3, 2026
632a5c9
test(onboard): cover failed nvmap permission verification
sandl99 Aug 3, 2026
0bb4a74
docs(troubleshooting): clarify nvmap rule lifecycle
sandl99 Aug 3, 2026
ea03eab
test(onboard): cover missing Jetson nvmap device
sandl99 Aug 3, 2026
c378434
Merge branch 'main' into fix/jetson-nvmap-nonroot
sandl99 Aug 4, 2026
29262e9
fix(onboard): preserve Jetson sandbox GPU access
sandl99 Aug 4, 2026
2257f36
Merge remote-tracking branch 'origin/main' into fix/jetson-nvmap-nonroot
sandl99 Aug 4, 2026
c9a80f8
fix(onboard): scope Jetson nvmap access to OpenClaw
sandl99 Aug 4, 2026
457d1f9
refactor(onboard): keep entrypoint line budget neutral
sandl99 Aug 4, 2026
9bd5a3a
test(images): stage Jetson bootstrap helper
sandl99 Aug 4, 2026
67a8579
fix(images): stage Jetson bootstrap script
sandl99 Aug 4, 2026
3690a12
Merge branch 'main' into fix/jetson-nvmap-nonroot
sandl99 Aug 4, 2026
ed7f1ec
fix(onboard): preserve managed Jetson groups
sandl99 Aug 4, 2026
1a17a1c
Merge branch 'main' into fix/jetson-nvmap-nonroot
apurvvkumaria Aug 4, 2026
8785891
Merge branch 'main' into fix/jetson-nvmap-nonroot
sandl99 Aug 5, 2026
5237552
Merge branch 'main' into fix/jetson-nvmap-nonroot
sandl99 Aug 5, 2026
92e10e2
Merge branch 'main' into fix/jetson-nvmap-nonroot
sandl99 Aug 5, 2026
5a92cb1
Merge remote-tracking branch 'origin/main' into fix/jetson-nvmap-nonroot
sandl99 Aug 5, 2026
70600b3
fix(installer): configure Jetson nvmap before L4T classification
sandl99 Aug 5, 2026
25d2c8b
fix(onboard): verify Jetson nvmap access before sandbox creation
sandl99 Aug 5, 2026
e9198bc
test(jetson): add nvmap boundary proof
sandl99 Aug 5, 2026
c7d6e05
fix(test): initialize Jetson POC readonly values
sandl99 Aug 5, 2026
f27f724
fix(test): make Jetson nvmap proof fail closed
apurvvkumaria Aug 5, 2026
8508897
Merge remote-tracking branch 'origin/main' into fix/jetson-nvmap-nonroot
sandl99 Aug 5, 2026
bf27905
fix(onboard): grant Jetson devices in sandbox policy
sandl99 Aug 5, 2026
98e392e
test(jetson): add CUDA boundary probe
sandl99 Aug 5, 2026
ba6080d
merge: resolve conflicts with main
github-actions[bot] Aug 6, 2026
06d7407
chore(onboard): revert prior Jetson GPU changes
sandl99 Aug 6, 2026
da3d48e
merge: sync main before focused Jetson fix
sandl99 Aug 6, 2026
000e88e
fix(onboard): permit Jetson GPU policy paths
sandl99 Aug 6, 2026
48338b7
fix(onboard): apply Jetson policy to default OpenClaw
sandl99 Aug 6, 2026
deee7ef
fix(onboard): preserve Jetson device groups
sandl99 Aug 6, 2026
a6bf7b5
fix(onboard): stage Jetson group bootstrap
sandl99 Aug 6, 2026
8255118
chore(onboard): add Jetson GPU diagnostic
sandl99 Aug 6, 2026
7fad34a
chore(onboard): add Jetson device-group proof
sandl99 Aug 6, 2026
0a65abe
fix(onboard): preserve Jetson groups in compatibility path
sandl99 Aug 6, 2026
4f65114
Merge remote-tracking branch 'origin/main' into fix/7610-pr-revert
sandl99 Aug 6, 2026
d6cfa87
refactor(onboard): carry agent scope with patch options
sandl99 Aug 6, 2026
03529c5
fix(images): audit Jetson wrapper metadata check
sandl99 Aug 6, 2026
7757675
chore(onboard): add cuInit boundary diagnostic
sandl99 Aug 6, 2026
f0a6720
chore(onboard): prove OpenRM policy boundary
sandl99 Aug 6, 2026
1964870
fix(onboard): make OpenRM proof non-interactive
sandl99 Aug 6, 2026
2b4a89a
fix(onboard): run OpenRM proof before rollback
sandl99 Aug 6, 2026
f06f365
fix(onboard): resolve Jetson review findings
apurvvkumaria Aug 6, 2026
b319e3f
Merge remote-tracking branch 'origin/main' into codex/pr8099-feedback…
apurvvkumaria Aug 6, 2026
cd42a42
test(images): cover Jetson helper permissions
apurvvkumaria Aug 6, 2026
0a0bc13
fix(onboard): recreate sandbox for OpenRM proof
sandl99 Aug 6, 2026
6e1cb76
fix(onboard): honor explicit sandbox recreation
sandl99 Aug 6, 2026
5a0fa51
chore(onboard): expand Jetson OpenRM proof
sandl99 Aug 6, 2026
d9cbe35
docs(onboard): describe OpenRM proof matrix
sandl99 Aug 6, 2026
f25c2b5
fix(onboard): validate Jetson groups before mutation
apurvvkumaria Aug 6, 2026
7b70ce2
fix(onboard): fail closed on OpenRM restore
apurvvkumaria Aug 6, 2026
e0e1e9c
Merge branch 'main' into fix/jetson-nvmap-nonroot
sandl99 Aug 7, 2026
8fde81f
test(onboard): make Jetson policy proof standalone
sandl99 Aug 7, 2026
72dd2f3
test(onboard): enable standalone OpenRM matrix
sandl99 Aug 7, 2026
21f21ee
test(onboard): cover invalid group database
apurvvkumaria Aug 7, 2026
ceff582
test(onboard): isolate Jetson process confinement
sandl99 Aug 7, 2026
3c81134
test(onboard): isolate remaining Jetson boundary
sandl99 Aug 7, 2026
60339b6
fix(onboard): allow Jetson NvSci IPC device
sandl99 Aug 7, 2026
250b7b4
fix(onboard): remove unproven NvSci policy
sandl99 Aug 7, 2026
f26b486
merge: resolve conflicts with main
github-actions[bot] Aug 8, 2026
65adec8
Merge branch 'main' into fix/jetson-nvmap-nonroot
apurvvkumaria Aug 10, 2026
a8ca0a0
Merge branch 'main' into fix/jetson-nvmap-nonroot
sandl99 Aug 11, 2026
1c723ba
test(onboard): expand Jetson CUDA diagnostics
sandl99 Aug 11, 2026
5e2459a
Revert "test(onboard): expand Jetson CUDA diagnostics"
sandl99 Aug 11, 2026
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
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@
COPY scripts/lib/openclaw_device_approval_policy.py /usr/local/lib/nemoclaw/openclaw_device_approval_policy.py
COPY scripts/lib/clean_runtime_shell_env_shim.py /usr/local/lib/nemoclaw/clean_runtime_shell_env_shim.py
COPY scripts/lib/normalize_mutable_config_perms.py /usr/local/lib/nemoclaw/normalize_mutable_config_perms.py
COPY scripts/jetson-device-group-bootstrap.sh /usr/local/lib/nemoclaw/jetson-device-group-bootstrap.sh
COPY scripts/state-dir-guard.py /usr/local/lib/nemoclaw/state-dir-guard.py
COPY scripts/openclaw-config-guard.py /usr/local/lib/nemoclaw/openclaw-config-guard.py
COPY scripts/managed-gateway-control.py /usr/local/lib/nemoclaw/managed-gateway-control.py
Expand Down Expand Up @@ -1068,6 +1069,7 @@
/usr/local/bin/nemoclaw-managed-bootstrap \
/usr/local/bin/nemoclaw-managed-startup-hold \
/usr/local/lib/nemoclaw/sandbox-init.sh \
/usr/local/lib/nemoclaw/jetson-device-group-bootstrap.sh \
/scripts/generate-openclaw-config.mts \
/scripts/validate-openclaw-tool-search.mts \
/src/lib/messaging/applier/build/messaging-build-applier.mts \
Expand Down Expand Up @@ -1163,10 +1165,10 @@
# CHAT_UI_URL is a non-loopback address (Brev Launchable, remote deployments)
# since terminal-based pairing is impossible in those contexts.
# Default: "0" (device auth enabled for local deployments — secure by default).
ARG NEMOCLAW_DISABLE_DEVICE_AUTH=0

Check warning on line 1168 in Dockerfile

View workflow job for this annotation

GitHub Actions / PR build and direct managed startup (OpenClaw)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ARG "NEMOCLAW_DISABLE_DEVICE_AUTH") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
# Internal audit provenance for the opt-out above. Standard onboarding rewrites
# this to managed-onboard; direct image builders retain operator provenance.
ARG NEMOCLAW_DEVICE_AUTH_OPT_OUT_SOURCE=operator

Check warning on line 1171 in Dockerfile

View workflow job for this annotation

GitHub Actions / PR build and direct managed startup (OpenClaw)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ARG "NEMOCLAW_DEVICE_AUTH_OPT_OUT_SOURCE") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
# Compatibility build arg for older custom Dockerfiles and rebuild tooling.
# NemoClaw-managed images intentionally do not consume it; gateway auth tokens
# are generated at container startup and are never baked into image layers.
Expand Down Expand Up @@ -1200,7 +1202,7 @@
# NEMOCLAW_MESSAGING_PLAN_B64 intentionally remains ARG-only: Docker exposes it
# to build RUN processes without retaining the full plan in the final image env.
# Direct ARG interpolation into inline source is a code injection vector (C-2).
ENV NEMOCLAW_MODEL=${NEMOCLAW_MODEL} \

Check warning on line 1205 in Dockerfile

View workflow job for this annotation

GitHub Actions / PR build and direct managed startup (OpenClaw)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "NEMOCLAW_DEVICE_AUTH_OPT_OUT_SOURCE") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 1205 in Dockerfile

View workflow job for this annotation

GitHub Actions / PR build and direct managed startup (OpenClaw)

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "NEMOCLAW_DISABLE_DEVICE_AUTH") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
NEMOCLAW_INFERENCE_PROVIDER_ID=${NEMOCLAW_INFERENCE_PROVIDER_ID} \
NEMOCLAW_UPSTREAM_PROVIDER=${NEMOCLAW_UPSTREAM_PROVIDER} \
NEMOCLAW_PRIMARY_MODEL_REF=${NEMOCLAW_PRIMARY_MODEL_REF} \
Expand Down
41 changes: 39 additions & 2 deletions docs/reference/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2687,8 +2687,45 @@ To skip GPU passthrough entirely, rerun with `--no-gpu` or set `NEMOCLAW_SANDBOX
#### Jetson and Tegra compatibility default

Automatic GPU onboarding uses the compatibility path directly; it does not make a native attempt first.
The path recreates the OpenShell-managed Docker container with NVIDIA GPU flags and propagates eligible host group IDs for the supported Jetson GPU device nodes.
Use `NEMOCLAW_DOCKER_GPU_PATCH=0` only for troubleshooting because it bypasses that group propagation and CUDA may not initialize.
The path recreates the OpenShell-managed Docker container with NVIDIA GPU flags.
Use `NEMOCLAW_DOCKER_GPU_PATCH=0` only for troubleshooting because it bypasses the compatibility patch and CUDA may not initialize.

<AgentOnly variant="openclaw">

It also keeps eligible Jetson GPU device group memberships when OpenShell starts the nonroot sandbox user.
The compatibility policy permits read-only access to the NVIDIA runtime library directory at `/opt/nvidia/l4t-gpu-libs` when that path is present.

If CUDA reports `NvRmMemInitNvmap failed with Permission denied` and `cuInit(0)=999` on JetPack 6, rerun the NemoClaw installer to apply persistent group read-write access to `/dev/nvmap`:
If the device is absent, setup warns and continues without installing the rule.

<Warning>
The installer grants write access to every member of the existing `/dev/nvmap` owning group.
</Warning>

```bash
curl -fsSL https://www.nvidia.com/nemoclaw.sh | bash
```

Verify the persistent rule and the live device permissions:

```bash
grep -Fx 'KERNEL=="nvmap", MODE="0660"' /etc/udev/rules.d/99-zz-nemoclaw-nvmap.rules
stat -c '%A %U:%G %n' /dev/nvmap
```

The rule must match exactly, and the group permission characters must be `rw`, such as the group field in `crw-rw----`.
The rule reapplies mode `0660` when udev recreates `/dev/nvmap`, including after reboot.
NemoClaw uninstall does not remove the rule; delete it and reload udev to stop future reapplication, while the live mode remains until device recreation.

After onboarding recreates the sandbox, verify CUDA as the nonroot sandbox user:

```bash
$$nemoclaw <sandbox-name> exec -- python3 -c 'import ctypes; lib=ctypes.CDLL("libcuda.so.1"); print(f"cuInit(0)={lib.cuInit(0)}")'
```

The command must print `cuInit(0)=0`.

</AgentOnly>

#### Common compatibility-path recovery

Expand Down
53 changes: 53 additions & 0 deletions scripts/jetson-device-group-bootstrap.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

set -euo pipefail

readonly GROUP_GIDS="${NEMOCLAW_JETSON_DEVICE_GROUP_GIDS:-}"
readonly SANDBOX_USER="sandbox"

[[ "$(id -u)" == "0" ]] || {
echo "Jetson device-group bootstrap must run as root before the OpenShell supervisor." >&2
exit 1
}
[[ $# -gt 0 ]] || {
echo "Jetson device-group bootstrap requires the OpenShell supervisor command." >&2
exit 1
}
id "$SANDBOX_USER" >/dev/null 2>&1 || {
echo "Jetson device-group bootstrap could not resolve the sandbox user." >&2
exit 1
}

IFS=',' read -r -a gids <<<"$GROUP_GIDS"
for gid in "${gids[@]}"; do
[[ "$gid" =~ ^[1-9][0-9]*$ ]] || {
echo "Jetson device-group bootstrap received an invalid group ID." >&2
exit 1
}

group_record="$(getent group "$gid" || true)"
if [[ -n "$group_record" ]]; then
IFS=':' read -r group_name _ <<<"$group_record"
else
group_name="nemoclaw_gpu_$gid"
groupadd --gid "$gid" "$group_name"
fi
[[ -n "$group_name" ]] || {
echo "Jetson device-group bootstrap could not resolve group ID $gid." >&2
exit 1
}
usermod --append --groups "$group_name" "$SANDBOX_USER"
done

# OpenShell calls initgroups() before setgid()/setuid(). The container group
# database must contain every device group before the supervisor starts.
for gid in "${gids[@]}"; do
[[ " $(id -G "$SANDBOX_USER") " == *" $gid "* ]] || {
echo "Jetson device-group bootstrap did not add sandbox to group ID $gid." >&2
exit 1
}
done

exec "$@"
33 changes: 33 additions & 0 deletions scripts/setup-jetson.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ set -euo pipefail
SUDO=()
((EUID != 0)) && SUDO=(sudo)

NVMAP_DEVICE="/dev/nvmap"
NVMAP_UDEV_RULE="/etc/udev/rules.d/99-zz-nemoclaw-nvmap.rules"
NVMAP_UDEV_RULE_CONTENT='KERNEL=="nvmap", MODE="0660"'

info() {
printf "[INFO] %s\n" "$*"
}
Expand Down Expand Up @@ -56,6 +60,32 @@ apply_br_netfilter_setup() {
echo "net.bridge.bridge-nf-call-iptables=1" | "${SUDO[@]}" tee /etc/sysctl.d/99-nemoclaw.conf >/dev/null
}

configure_nvmap_group_access() {
local device_state device_type verified_state verified_permissions

if ! device_state="$(LC_ALL=C stat -c '%F|%A' "$NVMAP_DEVICE" 2>/dev/null)"; then
warn "JetPack 6 host setup could not find $NVMAP_DEVICE. Non-root sandbox CUDA can fail until this device exists."
return 0
fi

device_type="${device_state%%|*}"
[[ "$device_type" == "character special file" ]] \
|| error "$NVMAP_DEVICE must be a character device before NemoClaw changes its group permissions."

warn "JetPack 6 host setup grants every member of the existing $NVMAP_DEVICE owning group write access and persists mode 0660 when udev recreates the device."
printf '%s\n' "$NVMAP_UDEV_RULE_CONTENT" | "${SUDO[@]}" tee "$NVMAP_UDEV_RULE" >/dev/null
"${SUDO[@]}" udevadm control --reload-rules
"${SUDO[@]}" chmod g+rw "$NVMAP_DEVICE"

verified_state="$(LC_ALL=C stat -c '%F|%A' "$NVMAP_DEVICE" 2>/dev/null)" \
|| error "Could not verify $NVMAP_DEVICE after granting group read-write access."
IFS='|' read -r device_type verified_permissions <<<"$verified_state"
[[ "$device_type" == "character special file" && "${verified_permissions:4:2}" == "rw" ]] \
|| error "$NVMAP_DEVICE does not grant its owning group read-write access after host setup."

info "$NVMAP_DEVICE grants its owning group read-write access. The udev rule $NVMAP_UDEV_RULE preserves this mode after reboot."
}

warn_host_setup_skipped() {
warn "Skipped Jetson host setup: iptables legacy mode and the Docker daemon.json adjustment (L4T 36.x only), and br_netfilter with net.bridge.bridge-nf-call-iptables=1 (every release)."
warn "Without br_netfilter, k3s inside the OpenShell gateway cannot NAT sandbox pod traffic to ClusterIP services, so sandbox pods cannot reach CoreDNS."
Expand Down Expand Up @@ -188,6 +218,9 @@ except Exception:
os.unlink(tmp)
raise
PYEOF
if [[ "${NEMOCLAW_AGENT:-openclaw}" == "openclaw" ]]; then
configure_nvmap_group_access
fi
;;
jp7-r38)
# JP7 R38 does not need iptables or Docker daemon.json changes.
Expand Down
2 changes: 1 addition & 1 deletion src/lib/onboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2714,6 +2714,7 @@ async function createSandboxWithBaseImageResolution(
} = await sandboxGpuCreateFlow.runSandboxGpuCreateFlow(
{
sandboxName,
agentName: agent?.name ?? "openclaw",
provider,
sandboxGpuConfig: effectiveSandboxGpuConfig,
gpuRoutePlan,
Expand Down Expand Up @@ -2742,7 +2743,6 @@ async function createSandboxWithBaseImageResolution(
if (initialSandboxPolicy.cleanup && initialSandboxPolicy.cleanup()) {
process.removeListener("exit", initialSandboxPolicy.cleanup);
}

// Clean up build context regardless of outcome.
// Use fs.rmSync instead of run() to avoid spawning a shell process.
// Only deregister the 'exit' safety net when inline cleanup succeeded;
Expand Down
11 changes: 11 additions & 0 deletions src/lib/onboard/docker-gpu-jetson-groups.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ describe("detectTegraDeviceGroupGids", () => {
{ gid: 0, mode: 0o660 },
{ gid: 2_147_483_648, mode: 0o660 },
{ gid: 44, mode: 0o600 },
{ gid: 44, mode: 0o440 },
{ gid: 44, mode: 0o620 },
{ gid: 104, mode: 0o666 },
];
let index = 0;
Expand All @@ -47,6 +49,15 @@ describe("detectTegraDeviceGroupGids", () => {
).toEqual([]);
});

it("does not treat a read-only nvmap group as CUDA access (#7610)", () => {
expect(
detectTegraDeviceGroupGids({
statDeviceAccess: () => ({ gid: 44, mode: 0o440 }),
listDevicePaths: () => ["/dev/nvmap"],
}),
).toEqual([]);
});

it("returns no GIDs when Tegra nodes are missing or unreadable", () => {
expect(
detectTegraDeviceGroupGids({
Expand Down
6 changes: 4 additions & 2 deletions src/lib/onboard/docker-gpu-jetson-groups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,15 @@ export function detectTegraDeviceGroupGids(
const gid = access?.gid ?? null;
const groupAccessBits = access === null ? 0 : (access.mode >> 3) & READ_WRITE_PERMISSION_BITS;
const otherAccessBits = access === null ? 0 : access.mode & READ_WRITE_PERMISSION_BITS;
const groupAddsAccess = (groupAccessBits & ~otherAccessBits) !== 0;
const groupAddsReadWriteAccess =
groupAccessBits === READ_WRITE_PERMISSION_BITS &&
otherAccessBits !== READ_WRITE_PERMISSION_BITS;
if (
gid !== null &&
Number.isSafeInteger(gid) &&
gid > 0 &&
gid <= MAX_DOCKER_SUPPLEMENTARY_GID &&
groupAddsAccess
groupAddsReadWriteAccess
) {
gids.add(String(gid));
}
Expand Down
49 changes: 38 additions & 11 deletions src/lib/onboard/docker-gpu-patch-clone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import type {
import { openshellSandboxCommandEnvValue } from "./docker-startup-command-env";

const OPENSHELL_SANDBOX_COMMAND_ENV = "OPENSHELL_SANDBOX_COMMAND";
const JETSON_DEVICE_GROUP_GIDS_ENV = "NEMOCLAW_JETSON_DEVICE_GROUP_GIDS";
const JETSON_DEVICE_GROUP_BOOTSTRAP = "/usr/local/lib/nemoclaw/jetson-device-group-bootstrap.sh";
const GPU_ENV_KEYS = new Set([
"NVIDIA_VISIBLE_DEVICES",
"NVIDIA_DRIVER_CAPABILITIES",
Expand Down Expand Up @@ -339,6 +341,18 @@ export function buildDockerGpuCloneRunArgs(
}
const args: string[] = ["--name", containerName, ...mode.args];
const gpuAugment = mode.kind !== "startup-command";
const extraGroupGids = [...(options.extraGroupGids ?? [])].map((gid) => String(gid).trim());
if (
extraGroupGids.some((gid) => {
if (!/^[1-9][0-9]*$/u.test(gid)) return true;
const parsed = Number(gid);
return !Number.isSafeInteger(parsed) || parsed > 2_147_483_647;
})
) {
throw new Error("Docker clone received an invalid supplementary group ID.");
}
const preserveJetsonGroups =
options.preserveJetsonDeviceGroupMembership === true && extraGroupGids.length > 0;

// Startup-command recreation must retain OpenShell's native CDI attachment.
if (!gpuAugment) {
Expand Down Expand Up @@ -373,6 +387,9 @@ export function buildDockerGpuCloneRunArgs(
if (sandboxCommand && !sawSandboxCommand) {
args.push("--env", `${OPENSHELL_SANDBOX_COMMAND_ENV}=${sandboxCommand}`);
}
if (preserveJetsonGroups) {
args.push("--env", `${JETSON_DEVICE_GROUP_GIDS_ENV}=${extraGroupGids.join(",")}`);
}

const labels = config.Labels || {};
for (const key of Object.keys(labels).sort()) {
Expand Down Expand Up @@ -407,11 +424,10 @@ export function buildDockerGpuCloneRunArgs(
for (const hostEntry of stringArray(host.ExtraHosts)) args.push("--add-host", hostEntry);
const groupAdds = new Set(stringArray(host.GroupAdd));
for (const group of groupAdds) args.push("--group-add", group);
for (const gid of options.extraGroupGids ?? []) {
const normalized = String(gid).trim();
if (normalized && !groupAdds.has(normalized)) {
groupAdds.add(normalized);
args.push("--group-add", normalized);
for (const gid of extraGroupGids) {
if (!groupAdds.has(gid)) {
groupAdds.add(gid);
args.push("--group-add", gid);
}
}
for (const ulimit of dockerUlimits(inspect, options.requiredUlimits)) {
Expand Down Expand Up @@ -444,16 +460,27 @@ export function buildDockerGpuCloneRunArgs(

const entrypoint = stringArray(config.Entrypoint);
const replacementEntrypoint = String(options.containerEntrypoint ?? "").trim();
if (replacementEntrypoint) {
if (preserveJetsonGroups && (replacementEntrypoint || options.containerCommand)) {
throw new Error("Jetson device-group bootstrap conflicts with a replacement process.");
}
if (preserveJetsonGroups && entrypoint.length === 0) {
throw new Error("Jetson device-group bootstrap requires the OpenShell supervisor entrypoint.");
}
if (preserveJetsonGroups) {
args.push("--entrypoint", JETSON_DEVICE_GROUP_BOOTSTRAP);
} else if (replacementEntrypoint) {
args.push("--entrypoint", replacementEntrypoint);
} else if (entrypoint.length > 0) {
args.push("--entrypoint", entrypoint[0]);
}
const commandArgs = options.containerCommand
? [...options.containerCommand]
: sandboxCommand
? []
: [...entrypoint.slice(1), ...stringArray(config.Cmd)];
const originalCommandArgs = sandboxCommand
? []
: [...entrypoint.slice(1), ...stringArray(config.Cmd)];
const commandArgs = preserveJetsonGroups
? [entrypoint[0], ...originalCommandArgs]
: options.containerCommand
? [...options.containerCommand]
: originalCommandArgs;
args.push(image, ...commandArgs);
return args;
}
Expand Down
Loading
Loading