From a9c6cc075e5ff78c01d8dd31a7d4379dc6071cde Mon Sep 17 00:00:00 2001 From: Senthil Ravichandran Date: Fri, 17 Jul 2026 18:02:23 -0700 Subject: [PATCH 1/4] feat(installer): add temporary Station metadata override Signed-off-by: Senthil Ravichandran --- ci/platform-matrix.json | 2 +- docs/get-started/dgx-station-preparation.mdx | 19 ++- docs/get-started/quickstart.mdx | 4 +- docs/inference/set-up-vllm.mdx | 3 +- docs/reference/platform-support.mdx | 2 +- scripts/install.sh | 77 ++++++++-- scripts/prepare-dgx-station-host.sh | 40 ++++-- test/install-express-prompt.test.ts | 116 ++++++++++++++- test/install-station-dgx-os.test.ts | 140 ++++++++++++++++++- test/station-doc-ownership.test.ts | 8 ++ 10 files changed, 382 insertions(+), 29 deletions(-) diff --git a/ci/platform-matrix.json b/ci/platform-matrix.json index 648a38fe3c8..bac2881a971 100644 --- a/ci/platform-matrix.json +++ b/ci/platform-matrix.json @@ -66,7 +66,7 @@ "prd_priority": "P1", "ci_tested": false, "prerequisites_notes": "Deferred. For evaluation on a qualifying DGX Station GB300, see [Additional Setup for DGX Station](prerequisites/dgx-station-preparation) before the Quickstart.", - "notes": "The PRD marks this platform as P1. Workstation form-factor with NVIDIA GPUs and the same Docker + NVIDIA Container Toolkit + CDI requirements as DGX Spark. Station remains Deferred. For canonical hardware qualification, image requirements, preparation, repair limits, and reboot handoff, see [Prepare DGX Station to Install NemoClaw](../get-started/prerequisites/dgx-station-preparation). On a Station GB300, the installer accepts either a clean generic Ubuntu 24.04 ARM64 image or stock DGX OS `7.2.0`, `7.4.0`, or `7.5.0` when a safe, root-owned `/etc/dgx-release` marker identifies `DGX Server for GALAXY-GB300`. The stock DGX OS path validates the factory GB300 driver and volatile ECC state, Docker and containerd, Buildx, `nvidia-ctk`, CDI, and GPU visibility through both CDI and `--gpus all` before it proceeds; it does not replace host packages or rewrite the runtime. Unknown DGX OS versions, NVIDIA BaseOS images, unsafe release markers, and other Station generations stop before host preparation. The installer offers express install with the pinned `nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-NVFP4` recipe, including an approximately 352 GB model download, without follow-up provider, model, policy, or sandbox-name choices. On Station GB300 only, direct-GPU sandbox policies add the exact read-only BDF directory for each host-discovered NVIDIA display-class PCI device, plus only existing CPU, memory, NUMA topology, and NVIDIA module initialization paths; they do not add `/sys`, the PCI parent subtree, or sysfs write access. One physical DGX OS `7.5.0` GB300 validation completed with released OpenShell `0.0.85`, local Nemotron Ultra serving, sandbox `cuInit(0)`, and a Hermes write/read file-tool task. In that validation, reads of `/sys/fs/cgroup/cgroup.controllers` and `/sys/class/net/lo/address` remained denied. DGX Station remains Deferred pending repeat qualification from a clean host and CI coverage. Pass `--station-deepseek` to use `deepseek-ai/DeepSeek-V4-Flash` while retaining the one-confirmation express flow. The flag requires an interactive terminal, and `/dev/tty` must be available when the installer runs through `curl | bash`. For headless setup on a qualifying Station, select `NEMOCLAW_PROVIDER=install-vllm` and `NEMOCLAW_VLLM_MODEL=deepseek-v4-flash` instead. Direct managed-vLLM onboarding still defaults to `deepseek-ai/DeepSeek-V4-Flash` when no model override is set." + "notes": "The PRD marks this platform as P1. Workstation form-factor with NVIDIA GPUs and the same Docker + NVIDIA Container Toolkit + CDI requirements as DGX Spark. Station remains Deferred. For canonical hardware qualification, image requirements, preparation, repair limits, reboot handoff, and the maintainer-directed temporary metadata override, see [Prepare DGX Station to Install NemoClaw](../get-started/prerequisites/dgx-station-preparation). On a Station GB300, the installer accepts either a clean generic Ubuntu 24.04 ARM64 image or stock DGX OS `7.2.0`, `7.4.0`, or `7.5.0` when a safe, root-owned `/etc/dgx-release` marker identifies `DGX Server for GALAXY-GB300`. The stock DGX OS path validates the factory GB300 driver and volatile ECC state, Docker and containerd, Buildx, `nvidia-ctk`, CDI, and GPU visibility through both CDI and `--gpus all` before it proceeds; it does not replace host packages or rewrite the runtime. By default, unknown DGX OS versions, NVIDIA BaseOS images, unsafe release markers, and other Station generations stop before host preparation. The installer offers express install with the pinned `nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-NVFP4` recipe, including an approximately 352 GB model download, without follow-up provider, model, policy, or sandbox-name choices. On Station GB300 only, direct-GPU sandbox policies add the exact read-only BDF directory for each host-discovered NVIDIA display-class PCI device, plus only existing CPU, memory, NUMA topology, and NVIDIA module initialization paths; they do not add `/sys`, the PCI parent subtree, or sysfs write access. One physical DGX OS `7.5.0` GB300 validation completed with released OpenShell `0.0.85`, local Nemotron Ultra serving, sandbox `cuInit(0)`, and a Hermes write/read file-tool task. In that validation, reads of `/sys/fs/cgroup/cgroup.controllers` and `/sys/class/net/lo/address` remained denied. DGX Station remains Deferred pending repeat qualification from a clean host and CI coverage. Pass `--station-deepseek` to use `deepseek-ai/DeepSeek-V4-Flash` while retaining the one-confirmation express flow. The flag requires an interactive terminal, and `/dev/tty` must be available when the installer runs through `curl | bash`. For headless setup on a qualifying Station, select `NEMOCLAW_PROVIDER=install-vllm` and `NEMOCLAW_VLLM_MODEL=deepseek-v4-flash` instead. Direct managed-vLLM onboarding still defaults to `deepseek-ai/DeepSeek-V4-Flash` when no model override is set." }, { "name": "NVIDIA RTX (consumer and Pro workstation GPUs)", diff --git a/docs/get-started/dgx-station-preparation.mdx b/docs/get-started/dgx-station-preparation.mdx index 6693056974d..329a2ba072f 100644 --- a/docs/get-started/dgx-station-preparation.mdx +++ b/docs/get-started/dgx-station-preparation.mdx @@ -16,9 +16,26 @@ Complete this preparation before following the Quickstart on a qualifying DGX St DGX Station GB300 express install accepts either the generic Ubuntu 24.04 ARM64 image or stock DGX OS `7.2.0`, `7.4.0`, or `7.5.0`. A stock DGX OS image qualifies only when a safe, root-owned `/etc/dgx-release` marker identifies `DGX Server for GALAXY-GB300`. -An unknown version, malformed or unsafe marker, NVIDIA BaseOS image, or other Station generation stops before host preparation. +By default, an unknown version, malformed or unsafe marker, NVIDIA BaseOS image, or other Station generation stops before host preparation. On an unqualified system, set `NEMOCLAW_PROVIDER` or `NEMOCLAW_NO_EXPRESS=1` explicitly to continue without Station host automation. + +Use `--force-station-install` only when a NemoClaw maintainer directs you to continue on a genuine DGX Station GB300 whose DGX release metadata is not recognized. +The flag bypasses only the `/etc/dgx-release` allowlist. +It still requires ARM64 Ubuntu 24.04, DGX Station GB300 hardware identity, exactly one healthy GB300 for inference, zero volatile GB300 ECC errors, adequate storage, healthy required system services, no active agent, inference, or Docker workloads, and working Docker, Buildx, NVIDIA Container Toolkit, CDI, and container GPU-visibility probes. +The override preserves the existing factory driver, packages, and container-runtime configuration; it does not install or repair missing factory components. +It cannot enable Station Express on another Station generation or hardware platform. + +Run the override from an interactive terminal: + +```bash +curl -fsSL https://www.nvidia.com/nemoclaw.sh | bash -s -- --force-station-install +``` + +Add `--station-deepseek` to the same command to select DeepSeek V4 Flash. +The installer records the override in the printed relogin command when Docker-group access requires a new session. + + ## Prepare Generic Ubuntu On the generic Ubuntu path, accepting express install prepares the host with NVIDIA open driver `610.43.02`, Docker CE `29.6.1` with Buildx, and NVIDIA Container Toolkit `1.19.1`. diff --git a/docs/get-started/quickstart.mdx b/docs/get-started/quickstart.mdx index 49f065c48be..e0899d19679 100644 --- a/docs/get-started/quickstart.mdx +++ b/docs/get-started/quickstart.mdx @@ -123,7 +123,8 @@ Use these details when your first-run path needs more control. Station accepts the generic Ubuntu 24.04 ARM64 image and stock DGX OS `7.2.0`, `7.4.0`, or `7.5.0` when a safe, root-owned `/etc/dgx-release` marker identifies `DGX Server for GALAXY-GB300`. On a qualifying Station, accepting the prompt selects the pinned `nemotron-3-ultra-550b-a55b` managed-vLLM recipe and completes onboarding without more provider, model, policy, or sandbox-name choices. [Prepare DGX Station to Install NemoClaw](prerequisites/dgx-station-preparation) defines Station qualification, generic Ubuntu preparation, stock DGX OS validation, repair limits, and reboot handoff. - Unknown versions, unsafe release markers, NVIDIA BaseOS images, and other Station generations stop before host preparation; set `NEMOCLAW_PROVIDER` or `NEMOCLAW_NO_EXPRESS=1` to bypass Station host automation. + By default, unknown versions, unsafe release markers, NVIDIA BaseOS images, and other Station generations stop before host preparation; set `NEMOCLAW_PROVIDER` or `NEMOCLAW_NO_EXPRESS=1` to bypass Station host automation. + For a maintainer-directed temporary override on genuine Station GB300 hardware with unrecognized release metadata, follow the `--force-station-install` safeguards in the Station preparation guide. One physical DGX OS `7.5.0` GB300 validation completed, but Station remains Deferred pending repeat clean-host qualification and CI coverage. Pass `--station-deepseek` to use DeepSeek V4 Flash for a Station demo instead; the flag selects the interactive prompt and requires terminal access. Refer to [Platform Support](../reference/platform-support) and [Choose an Inference Provider](../inference/learn-and-choose/choose-inference-provider) for the current platform behavior. @@ -200,6 +201,7 @@ Use these details when your first-run path needs more control. One physical DGX OS `7.5.0` GB300 validation completed, but Station remains Deferred pending repeat clean-host qualification and CI coverage. To select DeepSeek V4 Flash while retaining the one-confirmation Station express flow, run `curl -fsSL https://www.nvidia.com/nemoclaw.sh | bash -s -- --station-deepseek`. The `--station-deepseek` flag requires an interactive terminal; in a `curl | bash` pipeline, `/dev/tty` must be available. + The temporary `--force-station-install` flag has the same terminal requirement and bypasses only DGX release-metadata qualification on genuine Station GB300 hardware. Without terminal access, the installer stops before it installs Docker or build dependencies instead of ignoring the flag. For a headless or CI install on a qualifying DGX Station GB300 after host preparation, omit the flag and select the same managed-vLLM recipe explicitly. diff --git a/docs/inference/set-up-vllm.mdx b/docs/inference/set-up-vllm.mdx index 336dfb49fce..6314247217e 100644 --- a/docs/inference/set-up-vllm.mdx +++ b/docs/inference/set-up-vllm.mdx @@ -165,7 +165,8 @@ The container publishes port `8000` through Docker, so apply the firewall guidan DGX Station remains Deferred. On DGX Station GB300, NemoClaw can prepare a clean generic Ubuntu 24.04 ARM64 host for the reviewed Station path. -For stock DGX OS `7.2.0`, `7.4.0`, or `7.5.0`, NemoClaw instead requires the supported release marker and validates the factory GPU runtime in place. +By default, stock DGX OS `7.2.0`, `7.4.0`, or `7.5.0` requires the supported release marker before NemoClaw validates the factory GPU runtime in place. +For the maintainer-directed temporary metadata override, follow [Prepare DGX Station to Install NemoClaw](../../get-started/prerequisites/dgx-station-preparation). On Station GB300 only, a direct GPU sandbox receives the exact read-only BDF directory for each host-discovered NVIDIA display-class PCI device, plus the existing CPU, memory, NUMA topology, and NVIDIA module initialization paths. The policy does not grant `/sys`, the PCI parent subtree, or sysfs write access. In the physical DGX OS `7.5.0` validation, this exact-BDF policy passed sandbox `cuInit(0)` while reads of `/sys/fs/cgroup/cgroup.controllers` and `/sys/class/net/lo/address` remained denied. diff --git a/docs/reference/platform-support.mdx b/docs/reference/platform-support.mdx index 66a976b0bbf..c3238375062 100644 --- a/docs/reference/platform-support.mdx +++ b/docs/reference/platform-support.mdx @@ -79,7 +79,7 @@ For install requirements and the shorter setup-oriented platform view, refer to | OS | Container runtime | Status | PRD priority | CI | Notes | |----|-------------------|--------|--------------|----|-------| | DGX OS (Spark) | Docker | Tested | P1 | Yes | Use the standard installer and `$$nemoclaw onboard`. For an end-to-end walkthrough with local inference, see the [NVIDIA Spark playbook](https://build.nvidia.com/spark/nemoclaw). | -| DGX OS (Station) | Docker | Deferred | P1 | No | The PRD marks this platform as P1. Workstation form-factor with NVIDIA GPUs and the same Docker + NVIDIA Container Toolkit + CDI requirements as DGX Spark. Station remains Deferred. For canonical hardware qualification, image requirements, preparation, repair limits, and reboot handoff, see [Prepare DGX Station to Install NemoClaw](../get-started/prerequisites/dgx-station-preparation). On a Station GB300, the installer accepts either a clean generic Ubuntu 24.04 ARM64 image or stock DGX OS `7.2.0`, `7.4.0`, or `7.5.0` when a safe, root-owned `/etc/dgx-release` marker identifies `DGX Server for GALAXY-GB300`. The stock DGX OS path validates the factory GB300 driver and volatile ECC state, Docker and containerd, Buildx, `nvidia-ctk`, CDI, and GPU visibility through both CDI and `--gpus all` before it proceeds; it does not replace host packages or rewrite the runtime. Unknown DGX OS versions, NVIDIA BaseOS images, unsafe release markers, and other Station generations stop before host preparation. The installer offers express install with the pinned `nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-NVFP4` recipe, including an approximately 352 GB model download, without follow-up provider, model, policy, or sandbox-name choices. On Station GB300 only, direct-GPU sandbox policies add the exact read-only BDF directory for each host-discovered NVIDIA display-class PCI device, plus only existing CPU, memory, NUMA topology, and NVIDIA module initialization paths; they do not add `/sys`, the PCI parent subtree, or sysfs write access. One physical DGX OS `7.5.0` GB300 validation completed with released OpenShell `0.0.85`, local Nemotron Ultra serving, sandbox `cuInit(0)`, and a Hermes write/read file-tool task. In that validation, reads of `/sys/fs/cgroup/cgroup.controllers` and `/sys/class/net/lo/address` remained denied. DGX Station remains Deferred pending repeat qualification from a clean host and CI coverage. Pass `--station-deepseek` to use `deepseek-ai/DeepSeek-V4-Flash` while retaining the one-confirmation express flow. The flag requires an interactive terminal, and `/dev/tty` must be available when the installer runs through `curl \| bash`. For headless setup on a qualifying Station, select `NEMOCLAW_PROVIDER=install-vllm` and `NEMOCLAW_VLLM_MODEL=deepseek-v4-flash` instead. Direct managed-vLLM onboarding still defaults to `deepseek-ai/DeepSeek-V4-Flash` when no model override is set. | +| DGX OS (Station) | Docker | Deferred | P1 | No | The PRD marks this platform as P1. Workstation form-factor with NVIDIA GPUs and the same Docker + NVIDIA Container Toolkit + CDI requirements as DGX Spark. Station remains Deferred. For canonical hardware qualification, image requirements, preparation, repair limits, reboot handoff, and the maintainer-directed temporary metadata override, see [Prepare DGX Station to Install NemoClaw](../get-started/prerequisites/dgx-station-preparation). On a Station GB300, the installer accepts either a clean generic Ubuntu 24.04 ARM64 image or stock DGX OS `7.2.0`, `7.4.0`, or `7.5.0` when a safe, root-owned `/etc/dgx-release` marker identifies `DGX Server for GALAXY-GB300`. The stock DGX OS path validates the factory GB300 driver and volatile ECC state, Docker and containerd, Buildx, `nvidia-ctk`, CDI, and GPU visibility through both CDI and `--gpus all` before it proceeds; it does not replace host packages or rewrite the runtime. By default, unknown DGX OS versions, NVIDIA BaseOS images, unsafe release markers, and other Station generations stop before host preparation. The installer offers express install with the pinned `nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-NVFP4` recipe, including an approximately 352 GB model download, without follow-up provider, model, policy, or sandbox-name choices. On Station GB300 only, direct-GPU sandbox policies add the exact read-only BDF directory for each host-discovered NVIDIA display-class PCI device, plus only existing CPU, memory, NUMA topology, and NVIDIA module initialization paths; they do not add `/sys`, the PCI parent subtree, or sysfs write access. One physical DGX OS `7.5.0` GB300 validation completed with released OpenShell `0.0.85`, local Nemotron Ultra serving, sandbox `cuInit(0)`, and a Hermes write/read file-tool task. In that validation, reads of `/sys/fs/cgroup/cgroup.controllers` and `/sys/class/net/lo/address` remained denied. DGX Station remains Deferred pending repeat qualification from a clean host and CI coverage. Pass `--station-deepseek` to use `deepseek-ai/DeepSeek-V4-Flash` while retaining the one-confirmation express flow. The flag requires an interactive terminal, and `/dev/tty` must be available when the installer runs through `curl \| bash`. For headless setup on a qualifying Station, select `NEMOCLAW_PROVIDER=install-vllm` and `NEMOCLAW_VLLM_MODEL=deepseek-v4-flash` instead. Direct managed-vLLM onboarding still defaults to `deepseek-ai/DeepSeek-V4-Flash` when no model override is set. | | Linux | Docker | Tested | P0 | Yes | Primary tested path. Ubuntu 24.04 has host-level onboarding validation. A digest-pinned Ubuntu 26.04 userspace lane builds the CLI and runs preflight, installer, and platform contracts on eligible main pushes; Docker-host, AppArmor, Landlock, and live onboarding validation on 26.04 remain pending. Other distros (Ubuntu 22.04, Fedora, Rocky, Alma, NixOS, Arch) may work but are not validated. | | macOS (Apple Silicon) | Colima, Docker Desktop | Tested with limitations | P0 | Yes | Start the container runtime (Colima or Docker Desktop) before running the installer. Homebrew Colima users must install both Colima and the Docker CLI (`brew install colima docker`) before `docker info` can work. Xcode Command Line Tools (`xcode-select --install`) are typically required for Node native modules during install. NemoClaw recommends them but does not enforce them during preflight. | | NVIDIA RTX (consumer and Pro workstation GPUs) | Docker | Deferred | P1 | No | The PRD marks this platform as P1. Covers RTX consumer cards and RTX Pro workstation cards on Linux hosts that meet the generic-Linux-GPU requirements (NVIDIA Container Toolkit + CDI present). The provider menu emits managed vLLM behind `NEMOCLAW_EXPERIMENTAL=1` or `NEMOCLAW_PROVIDER=install-vllm` for this host class today; the end-to-end onboard path on this hardware is not yet validated in CI. | diff --git a/scripts/install.sh b/scripts/install.sh index 74d54083b62..a515cf85ddb 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -748,6 +748,7 @@ usage() { printf " --yes-i-accept-third-party-software Accept the third-party software notice without prompting\n" printf " --fresh Discard any failed/interrupted onboarding session and start over\n" printf " --station-deepseek Use DeepSeek V4 Flash for DGX Station express install (interactive terminal required)\n" + printf " --force-station-install Bypass only the DGX release-metadata allowlist for Station GB300 express install\n" printf " --version, -v Print installer version and exit\n" printf " --help, -h Show this help message and exit\n\n" printf " ${C_DIM}Environment:${C_RESET}\n" @@ -3024,7 +3025,13 @@ detect_express_platform() { generic-ubuntu | supported-dgx-os | supported-colossus-baseos | supported-ai-developer-tools) printf "DGX Station" ;; - *) printf "Unsupported DGX Station OS" ;; + *) + if [ "${FORCE_STATION_INSTALL:-}" = "1" ]; then + printf "DGX Station" + else + printf "Unsupported DGX Station OS" + fi + ;; esac return fi @@ -3076,6 +3083,36 @@ fail_station_deepseek_terminal_required() { error "--station-deepseek selects the DGX Station express prompt, which needs an interactive terminal. Re-run from a terminal (for a curl|bash pipe, /dev/tty must be available), or omit --station-deepseek and configure the install non-interactively." } +fail_force_station_terminal_required() { + error "--force-station-install selects the DGX Station express prompt, which needs an interactive terminal. Re-run from a terminal (for a curl|bash pipe, /dev/tty must be available), or omit --force-station-install." +} + +validate_force_station_install_override() { + local platform="$1" + if [ "${FORCE_STATION_INSTALL:-}" != "1" ]; then + return 0 + fi + if [ "$platform" != "DGX Station" ]; then + error "--force-station-install requires DGX Station GB300 hardware (detected: ${platform:-unsupported platform})." + fi + if [ "${NEMOCLAW_NO_EXPRESS:-}" = "1" ]; then + error "--force-station-install cannot be combined with NEMOCLAW_NO_EXPRESS=1. Remove one override." + fi + if [ "${NON_INTERACTIVE:-}" = "1" ]; then + local trigger_note="" + if [ -n "${NON_INTERACTIVE_SOURCE:-}" ]; then + trigger_note=" (triggered by: ${NON_INTERACTIVE_SOURCE})" + fi + error "--force-station-install selects the DGX Station express prompt and cannot be combined with non-interactive mode${trigger_note}." + fi + if [ -n "${NEMOCLAW_PROVIDER:-}" ]; then + error "--force-station-install conflicts with NEMOCLAW_PROVIDER=${NEMOCLAW_PROVIDER}. Remove the provider override to use Station express install." + fi + if ! express_prompt_can_read_tty; then + fail_force_station_terminal_required + fi +} + validate_station_deepseek_override() { local platform="$1" if [ "${STATION_DEEPSEEK:-}" != "1" ]; then @@ -3129,6 +3166,7 @@ preflight_explicit_express_flags() { local platform platform="$(detect_express_platform)" validate_express_platform_boundary "$platform" + validate_force_station_install_override "$platform" validate_station_deepseek_override "$platform" } @@ -3360,6 +3398,9 @@ station_express_resume_command() { printf 'curl -fsSL https://www.nvidia.com/nemoclaw.sh | NEMOCLAW_INSTALL_TAG=%s NEMOCLAW_AGENT=%s NEMOCLAW_SANDBOX_NAME=%s NEMOCLAW_POLICY_TIER=%s bash' \ "$_STATION_EXPRESS_RESUME_REVISION" "$_STATION_EXPRESS_RESUME_AGENT" \ "$_STATION_EXPRESS_RESUME_SANDBOX" "$_STATION_EXPRESS_RESUME_POLICY_TIER" + if [ "${FORCE_STATION_INSTALL:-}" = "1" ]; then + printf ' -s -- --force-station-install' + fi } clear_station_express_resume() { @@ -3447,8 +3488,12 @@ run_station_host_preparation() { # selected ref before executing this payload. Keep the sibling lookup and # fail-closed check so Station preparation cannot drift from that ref. local helper="${SCRIPT_DIR}/prepare-dgx-station-host.sh" + local -a helper_args=(--apply) [[ -f "$helper" ]] || error "DGX Station host preparation helper is missing: ${helper}" - bash "$helper" --apply + if [ "${FORCE_STATION_INSTALL:-}" = "1" ]; then + helper_args+=(--force-station-install) + fi + bash "$helper" "${helper_args[@]}" } ensure_station_express_host() { @@ -3463,7 +3508,14 @@ ensure_station_express_host() { supported-colossus-baseos) info "Validating the pinned BaseOS package inventory and preparing Docker access and CDI. Host packages and the NVIDIA driver will not be changed." ;; - *) info "Checking pinned DGX Station host prerequisites. Exact matches are reused." ;; + *) + if [ "${FORCE_STATION_INSTALL:-}" = "1" ]; then + warn "Proceeding with explicit --force-station-install intent; DGX release metadata qualification is bypassed, but Station GB300 hardware and factory-runtime health checks remain required." + info "Validating the existing Station GPU and local container runtime. Host packages, the NVIDIA driver, and runtime configuration will not be changed." + else + info "Checking pinned DGX Station host prerequisites. Exact matches are reused." + fi + ;; esac local status=0 run_station_host_preparation || status=$? @@ -3552,7 +3604,11 @@ describe_express_install() { printf " Factory Ubuntu with NVIDIA AI Developer Tools reuses its driver and container stack after local GPU, CDI, Docker, and Buildx validation. It may add this account to the Docker group, but does not install or replace host packages, rewrite the Docker runtime, or require a reboot.\n" ;; *) - printf " Station host setup reuses exact prerequisite versions, applies the reviewed factory DKMS transition when present, installs missing pinned driver, Docker, and NVIDIA Container Toolkit packages, and may require one reboot.\n" + if [ "${FORCE_STATION_INSTALL:-}" = "1" ]; then + printf " Explicit --force-station-install intent bypasses only DGX release-metadata qualification. Setup preserves the existing driver and container stack and proceeds only after Station GB300, GPU, ECC, Docker, Buildx, Toolkit, CDI, and container GPU-visibility checks pass.\n" + else + printf " Station host setup reuses exact prerequisite versions, applies the reviewed factory DKMS transition when present, installs missing pinned driver, Docker, and NVIDIA Container Toolkit packages, and may require one reboot.\n" + fi ;; esac printf " Host setup may add this trusted local account to the docker group, which grants root-equivalent control. This flow is only for trusted single-user development hosts; shared or managed hosts require an organization-approved Docker access path.\n" @@ -3600,6 +3656,7 @@ maybe_offer_express_install() { local platform platform="$(detect_express_platform)" validate_express_platform_boundary "$platform" + validate_force_station_install_override "$platform" validate_station_deepseek_override "$platform" # Not on a platform we have an express recipe for — say nothing. if [ -z "$platform" ]; then @@ -3685,6 +3742,7 @@ main() { ACCEPT_THIRD_PARTY_SOFTWARE="" FRESH="" STATION_DEEPSEEK="" + FORCE_STATION_INSTALL="" for arg in "$@"; do case "$arg" in --non-interactive) @@ -3694,6 +3752,7 @@ main() { --yes-i-accept-third-party-software) ACCEPT_THIRD_PARTY_SOFTWARE=1 ;; --fresh) FRESH=1 ;; --station-deepseek) STATION_DEEPSEEK=1 ;; + --force-station-install) FORCE_STATION_INSTALL=1 ;; --version | -v) local version_suffix version_suffix="$(installer_version_for_display)" @@ -3725,14 +3784,14 @@ main() { # alone clears the preflight below but the install can still partial-fail at # run_onboard with the same TTY error, leaving phases 1/2 on disk anyway. # - # #7008: `--station-deepseek` is the exception — it explicitly selects the - # interactive DGX Station express prompt, so accepting the notice must NOT + # #7008: Station-only prompt flags are the exception — they explicitly select + # the interactive DGX Station express prompt, so accepting the notice must NOT # imply non-interactive there. The two signals are orthogonal: one accepts a - # licence, the other opts into an interactive express flow. Inferring + # license, the other opts into an interactive express flow. Inferring # non-interactive from the notice would make the express flow reject its own - # required flag (validate_station_deepseek_override). + # required flag validation. if [ "${ACCEPT_THIRD_PARTY_SOFTWARE:-}" = "1" ] && [ "${NON_INTERACTIVE:-}" != "1" ] \ - && [ "${STATION_DEEPSEEK:-}" != "1" ]; then + && [ "${STATION_DEEPSEEK:-}" != "1" ] && [ "${FORCE_STATION_INSTALL:-}" != "1" ]; then NON_INTERACTIVE=1 fi diff --git a/scripts/prepare-dgx-station-host.sh b/scripts/prepare-dgx-station-host.sh index d7093894d11..c24153e8126 100755 --- a/scripts/prepare-dgx-station-host.sh +++ b/scripts/prepare-dgx-station-host.sh @@ -13,6 +13,7 @@ readonly GB300_PCI_VENDOR="0x10de" readonly GB300_PCI_DEVICE="0x31c2" readonly GB300_PCI_CLASS_PREFIX="0x03" STATION_HOST_PROFILE="generic-ubuntu" +FORCE_STATION_INSTALL=0 # The qualified generic image currently ships this OEM telemetry bootcmd. Its # exception disappears automatically when the file changes or the bootcmd # failure is fixed; update the pin only with a newly audited image. @@ -269,11 +270,15 @@ on_error() { usage() { cat <<'EOF' -Usage: prepare-dgx-station-host.sh --check|--apply|--verify +Usage: prepare-dgx-station-host.sh --check|--apply|--verify [--force-station-install] --check Read-only eligibility and current-state report. --apply Install exact prerequisites or finish post-reboot runtime setup. --verify Read-only host verification plus ephemeral GPU container tests. + --force-station-install + Bypass only the DGX release-metadata allowlist. ARM64 Ubuntu 24.04, + Station GB300 hardware, and all factory-runtime health checks still + apply. The existing driver and container runtime are preserved. Exit 10 from --apply means an operator-controlled reboot is required. After the reboot, run --apply once more, followed by --verify. @@ -282,11 +287,22 @@ run --apply again; a reboot is not required. EOF } -is_valid_mode() { - case "${1:-}" in - --check | --apply | --verify | --classify-dgx-release) return 0 ;; - *) return 1 ;; - esac +parse_args() { + local arg + MODE="" + FORCE_STATION_INSTALL=0 + for arg in "$@"; do + case "$arg" in + --check | --apply | --verify | --classify-dgx-release) + [[ -z "$MODE" ]] || return 1 + MODE="$arg" + ;; + --force-station-install) FORCE_STATION_INSTALL=1 ;; + *) return 1 ;; + esac + done + [[ -n "$MODE" ]] || return 1 + [[ "$MODE" != "--classify-dgx-release" || "$FORCE_STATION_INSTALL" == "0" ]] } is_station_gb300_product() { @@ -556,7 +572,7 @@ verify_baseos_packages() { station_uses_factory_runtime() { case "$STATION_HOST_PROFILE" in - stock-dgx-os | colossus-baseos | ai-developer-tools) return 0 ;; + stock-dgx-os | colossus-baseos | ai-developer-tools | forced-factory-runtime) return 0 ;; *) return 1 ;; esac } @@ -605,7 +621,12 @@ check_platform() { supported-colossus-baseos) STATION_HOST_PROFILE="colossus-baseos" ;; supported-ai-developer-tools) STATION_HOST_PROFILE="ai-developer-tools" ;; *) - fatal "This DGX Station OS image is outside the validated boundary" + if ((FORCE_STATION_INSTALL == 1)); then + STATION_HOST_PROFILE="forced-factory-runtime" + warn "DGX release metadata allowlist bypassed by explicit --force-station-install intent; all hardware and factory-runtime health checks remain required" + else + fatal "This DGX Station OS image is outside the validated boundary" + fi ;; esac info "platform=${product} profile=${STATION_HOST_PROFILE} release=${release_state} os=${PRETTY_NAME} arch=${arch} kernel=$(uname -r)" @@ -1543,11 +1564,10 @@ run_verify() { } main() { - if (($# != 1)) || ! is_valid_mode "${1:-}"; then + if ! parse_args "$@"; then usage >&2 exit 2 fi - MODE=$1 if [[ "$MODE" == "--classify-dgx-release" ]]; then dgx_station_release_state return 0 diff --git a/test/install-express-prompt.test.ts b/test/install-express-prompt.test.ts index fe0250adb3e..2f6965760b9 100644 --- a/test/install-express-prompt.test.ts +++ b/test/install-express-prompt.test.ts @@ -43,8 +43,9 @@ detect_express_platform() { printf "$EXPRESS_PLATFORM"; } print_banner() { :; } ensure_docker() { :; } ensure_openshell_build_deps() { :; } -# Stop immediately after the real express prompt configures the DeepSeek -# recipe, before setup-jetson.sh or any installation side effect can run. +# Stop immediately after the real Station express prompt configures its recipe, +# before setup-jetson.sh or any installation side effect can run. +classify_dgx_station_release() { printf "%s" "\${EXPRESS_RELEASE_STATE:-generic-ubuntu}"; } bash() { printf "RESULT NON_INTERACTIVE=%s SUDO_MODE=%s PROVIDER=%s MODEL=%s VLLM_MODEL=%s POLICY=%s YES=%s SANDBOX=%s STATION_EXPRESS=%s\\n" \ "\${NON_INTERACTIVE:-}" "\${NEMOCLAW_NON_INTERACTIVE_SUDO_MODE:-}" "\${NEMOCLAW_PROVIDER:-}" "\${NEMOCLAW_MODEL:-}" \ @@ -148,7 +149,11 @@ sys.exit(exit_code) ); } - function detectExpressPlatform(productName: string, releasePath: string) { + function detectExpressPlatform( + productName: string, + releasePath: string, + extraEnv: Record = {}, + ) { return spawnSync( "bash", [ @@ -197,6 +202,7 @@ detect_express_platform ), EXPRESS_PRODUCT_NAME: productName, EXPRESS_DGX_RELEASE_PATH: releasePath, + ...extraEnv, }, }, ); @@ -269,6 +275,19 @@ detect_express_platform ); }); + it("parses and documents the metadata-only Station override", () => { + const result = spawnSync("bash", [INSTALLER_PAYLOAD, "--force-station-install", "--help"], { + cwd: path.join(import.meta.dirname, ".."), + encoding: "utf-8", + }); + const output = `${result.stdout}${result.stderr}`; + + expect(result.status, output).toBe(0); + expect(output).toMatch( + /--force-station-install\s+Bypass only the DGX release-metadata allowlist/, + ); + }); + it("offers express install when curl-piped stdin still has a controlling TTY", () => { const result = runExpressPromptWithTty("y\n", "pipe"); const output = `${result.stdout}${result.stderr}`; @@ -488,6 +507,22 @@ ensure_station_express_host`, }); it.each([ + { + name: "a forced Station install on DGX Spark", + args: ["--force-station-install"], + platform: "DGX Spark", + env: {}, + message: + /--force-station-install requires DGX Station GB300 hardware \(detected: DGX Spark\)/, + }, + { + name: "a forced Station install in non-interactive mode", + args: ["--force-station-install", "--non-interactive"], + platform: "DGX Station", + env: {}, + message: + /--force-station-install selects the DGX Station express prompt and cannot be combined with non-interactive mode \(triggered by: the --non-interactive flag\)/, + }, { name: "a Station-only flag on DGX Spark", args: ["--station-deepseek"], @@ -604,6 +639,46 @@ main "$@" expect(output).not.toMatch(/cannot be combined with non-interactive mode/); }); + it.each<{ + name: string; + extraEnv: Record; + entrypointArgs: string[]; + }>([ + { + name: "environment notice acceptance", + extraEnv: { + NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE: "1", + EXPRESS_RELEASE_STATE: "unsupported-dgx-os", + }, + entrypointArgs: ["--force-station-install"], + }, + { + name: "the CLI notice-acceptance flag", + extraEnv: { EXPRESS_RELEASE_STATE: "unsupported-dgx-os" }, + entrypointArgs: ["--force-station-install", "--yes-i-accept-third-party-software"], + }, + ])("reaches and accepts the forced Station express prompt through main with $name", ({ + extraEnv, + entrypointArgs, + }) => { + const result = runExpressPromptWithTty( + "\n", + "pipe", + "DGX Station", + extraEnv, + "accepted-station-main", + entrypointArgs, + ); + const output = `${result.stdout}${result.stderr}`; + + expect(result.status, output).toBe(0); + expect(output).toMatch(/Explicit --force-station-install intent bypasses only/); + expect(output.match(/Run express install with these settings\?/g)).toHaveLength(1); + expect(output).toMatch(/Using express install for DGX Station/); + expect(output).toMatch(/PROVIDER=install-vllm/); + expect(output).not.toMatch(/cannot be combined with non-interactive mode/); + }); + it("errors instead of silently skipping --station-deepseek when no interactive terminal is available (#7014)", () => { // Python's start_new_session runs main without a controlling terminal, and // stdin is /dev/null — so neither `-t 0` nor /dev/tty is available. This is @@ -824,6 +899,41 @@ detect_express_platform expect(result.stdout).toBe("DGX Station"); }); + it("requires explicit intent before treating unrecognized metadata as Station Express", () => { + const releasePath = path.join( + fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-dgx-release-force-")), + "dgx-release", + ); + fs.writeFileSync( + releasePath, + [ + 'DGX_NAME="DGX Server"', + 'DGX_PRETTY_NAME="NVIDIA DGX GB300WS"', + 'DGX_SWBUILD_DATE="2026-04-02-08-20-16"', + 'DGX_SWBUILD_VERSION="7.5.0-GB300ws-GB200ws"', + 'DGX_PLATFORM="DGX Server for GALAXY-GB300"', + "", + ].join("\n"), + ); + + const rejected = detectExpressPlatform("DGX Station GB300", releasePath); + const forced = detectExpressPlatform("DGX Station GB300", releasePath, { + FORCE_STATION_INSTALL: "1", + }); + + expect(rejected.status, `${rejected.stdout}${rejected.stderr}`).toBe(0); + expect(rejected.stdout).toBe("Unsupported DGX Station OS"); + expect(forced.status, `${forced.stdout}${forced.stderr}`).toBe(0); + expect(forced.stdout).toBe("DGX Station"); + }); + + it("does not let the metadata override impersonate Station GB300 hardware", () => { + const result = detectExpressPlatform("DGX Spark", "", { FORCE_STATION_INSTALL: "1" }); + + expect(result.status, `${result.stdout}${result.stderr}`).toBe(0); + expect(result.stdout).toBe("DGX Spark"); + }); + it.each([ ["unreviewed version", stockDgxRelease("7.6.0")], ["wrong DGX platform", stockDgxRelease("7.5.0", "DGX Server for GALAXY-GB200")], diff --git a/test/install-station-dgx-os.test.ts b/test/install-station-dgx-os.test.ts index 745ffc7950f..9fe50458b83 100644 --- a/test/install-station-dgx-os.test.ts +++ b/test/install-station-dgx-os.test.ts @@ -6,10 +6,12 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { describe, expect, it } from "vitest"; -import { TEST_SYSTEM_PATH } from "./helpers/installer-sourced-env"; +import { INSTALLER_PAYLOAD, TEST_SYSTEM_PATH } from "./helpers/installer-sourced-env"; const REPO_ROOT = path.resolve(import.meta.dirname, ".."); const STATION_PREPARE = path.join(REPO_ROOT, "scripts", "prepare-dgx-station-host.sh"); +const STATION_REVISION = "a".repeat(40); +const STATION_GENERATION = "0123456789abcdef0123456789abcdef"; function runSourced(script: string, body: string, extraEnv: Record = {}) { const home = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-station-dgx-os-")); @@ -29,7 +31,7 @@ function runSourced(script: string, body: string, extraEnv: Record { + const forced = runSourced( + STATION_PREPARE, + ` +printf 'ID=ubuntu\nVERSION_ID="24.04"\nPRETTY_NAME="Ubuntu 24.04"\n' >"$HOME/os-release" +printf 'NVIDIA DGX Station GB300\n' >"$HOME/product-name" +uname() { printf 'aarch64\n'; } +station_os_release_path() { printf '%s' "$HOME/os-release"; } +station_product_name_path() { printf '%s' "$HOME/product-name"; } +dgx_station_release_path() { printf '%s' "$HOME/dgx-release"; } +dgx_station_release_state() { printf 'unsupported-dgx-os'; } +FORCE_STATION_INSTALL=1 +check_platform +printf 'PROFILE=%s\n' "$STATION_HOST_PROFILE" +`, + ); + + expect(forced.result.status, forced.output).toBe(0); + expect(forced.output).toContain("release metadata allowlist bypassed"); + expect(forced.output).toContain("PROFILE=forced-factory-runtime"); + + const unforced = runSourced( + STATION_PREPARE, + ` +printf 'ID=ubuntu\nVERSION_ID="24.04"\nPRETTY_NAME="Ubuntu 24.04"\n' >"$HOME/os-release" +printf 'NVIDIA DGX Station GB300\n' >"$HOME/product-name" +uname() { printf 'aarch64\n'; } +station_os_release_path() { printf '%s' "$HOME/os-release"; } +station_product_name_path() { printf '%s' "$HOME/product-name"; } +dgx_station_release_path() { printf '%s' "$HOME/dgx-release"; } +dgx_station_release_state() { printf 'unsupported-dgx-os'; } +check_platform +`, + ); + + expect(unforced.result.status, unforced.output).not.toBe(0); + expect(unforced.output).toContain("outside the validated boundary"); + }); + + it("parses the metadata override only alongside a preparation mode", () => { + const accepted = runSourced( + STATION_PREPARE, + ` +parse_args --apply --force-station-install +printf 'MODE=%s FORCE=%s\n' "$MODE" "$FORCE_STATION_INSTALL" +`, + ); + const classifier = runSourced( + STATION_PREPARE, + "parse_args --classify-dgx-release --force-station-install", + ); + + expect(accepted.result.status, accepted.output).toBe(0); + expect(accepted.output).toContain("MODE=--apply FORCE=1"); + expect(classifier.result.status, classifier.output).not.toBe(0); + }); + + it("does not let explicit metadata intent bypass architecture validation", () => { + const { result, output } = runSourced( + STATION_PREPARE, + ` +uname() { printf 'x86_64\n'; } +FORCE_STATION_INSTALL=1 +check_platform +`, + ); + + expect(result.status, output).not.toBe(0); + expect(output).toContain("Expected ARM64, found x86_64"); + }); +}); + +describe("DGX Station forced metadata installer handoff", () => { + it("forwards explicit intent only to Station host preparation", () => { + const { home, result, output } = runSourced( + INSTALLER_PAYLOAD, + ` +SCRIPT_DIR="$HOME" +touch "$SCRIPT_DIR/prepare-dgx-station-host.sh" +bash() { printf 'HELPER_ARGS=%s\n' "$*"; } +FORCE_STATION_INSTALL=1 +run_station_host_preparation +`, + ); + + expect(result.status, output).toBe(0); + expect(output).toContain( + `HELPER_ARGS=${path.join(home, "prepare-dgx-station-host.sh")} --apply --force-station-install`, + ); + }); + + it("preserves explicit intent in the relogin command", () => { + const { result, output } = runSourced( + INSTALLER_PAYLOAD, + ` +_SELECTED_EXPRESS_PLATFORM='DGX Station' +NEMOCLAW_VLLM_MODEL='deepseek-v4-flash' +FORCE_STATION_INSTALL=1 +station_installer_revision() { printf '${STATION_REVISION}'; } +station_express_resume_generation() { printf '${STATION_GENERATION}'; } +run_station_host_preparation() { return 11; } +ensure_station_express_host +`, + ); + + expect(result.status, output).toBe(11); + expect(output).toContain("bash -s -- --force-station-install"); + }); }); describe("DGX Station stock DGX OS runtime validation", () => { + it("preserves packages and the runtime for a forced metadata profile", () => { + const { result, output } = runSourced( + STATION_PREPARE, + ` +require_command() { + [[ "$1" == "sudo" ]] || { printf 'UNEXPECTED_REQUIREMENT %s\n' "$1"; return 1; } +} +acquire_sudo() { :; } +common_preflight() { STATION_HOST_PROFILE=forced-factory-runtime; } +verify_dgx_os_runtime_sudo() { printf 'FACTORY_RUNTIME_VALIDATED\n'; } +ensure_docker_group() { printf 'DOCKER_GROUP_PRESENT\n'; } +install_packages() { printf 'PACKAGE_MUTATION\n'; return 1; } +finish_runtime() { printf 'RUNTIME_MUTATION\n'; return 1; } +run_apply +`, + ); + + expect(result.status, output).toBe(0); + expect(output).toContain("FACTORY_RUNTIME_VALIDATED"); + expect(output).toContain("APPLY_RESULT=COMPLETE"); + expect(output).not.toContain("PACKAGE_MUTATION"); + expect(output).not.toContain("RUNTIME_MUTATION"); + expect(output).not.toContain("UNEXPECTED_REQUIREMENT"); + }); + it("requires the exact qualified BaseOS package inventory", () => { const exact = runSourced( STATION_PREPARE, diff --git a/test/station-doc-ownership.test.ts b/test/station-doc-ownership.test.ts index ebd3ed02c77..3a260440b79 100644 --- a/test/station-doc-ownership.test.ts +++ b/test/station-doc-ownership.test.ts @@ -15,6 +15,8 @@ const STATION_PREPARATION = path.join( "dgx-station-preparation.mdx", ); const STATION_QUICKSTART = path.join(REPO_ROOT, "docs", "get-started", "quickstart.mdx"); +const PLATFORM_SUPPORT = path.join(REPO_ROOT, "docs", "reference", "platform-support.mdx"); +const VLLM_SETUP = path.join(REPO_ROOT, "docs", "inference", "set-up-vllm.mdx"); const WINDOWS_PREPARATION = path.join(REPO_ROOT, "docs", "get-started", "windows-preparation.mdx"); const DOCS_INDEX = path.join(REPO_ROOT, "docs", "index.yml"); @@ -24,6 +26,8 @@ describe("DGX Station documentation ownership", () => { const prerequisites = fs.readFileSync(PREREQUISITES, "utf-8"); const stationPreparation = fs.readFileSync(STATION_PREPARATION, "utf-8"); const quickstart = fs.readFileSync(STATION_QUICKSTART, "utf-8"); + const platformSupport = fs.readFileSync(PLATFORM_SUPPORT, "utf-8"); + const vllmSetup = fs.readFileSync(VLLM_SETUP, "utf-8"); const pinnedValues = [ "DRIVER_VERSION", "DOCKER_VERSION", @@ -47,6 +51,10 @@ describe("DGX Station documentation ownership", () => { } expect(stationPreparation).toContain("DGX Server for GALAXY-GB300"); expect(quickstart).toContain("DGX Server for GALAXY-GB300"); + expect(stationPreparation).toContain("--force-station-install"); + expect(quickstart).toContain("--force-station-install"); + expect(platformSupport).toContain("maintainer-directed temporary metadata override"); + expect(vllmSetup).toContain("maintainer-directed temporary metadata override"); expect(stationPreparation).toMatch(/(?:DGX )?Station(?: remains|'s) Deferred/); expect(stationPreparation).toContain("One physical DGX OS `7.5.0` GB300 validation completed"); expect(stationPreparation).toContain("[Platform Support](../../reference/platform-support)"); From 33f84039bcc738074b34fe6a2aae51b8a55cba34 Mon Sep 17 00:00:00 2001 From: Senthil Ravichandran Date: Fri, 17 Jul 2026 18:14:04 -0700 Subject: [PATCH 2/4] fix(installer): harden Station override validation Signed-off-by: Senthil Ravichandran --- docs/get-started/dgx-station-preparation.mdx | 2 ++ scripts/prepare-dgx-station-host.sh | 11 ++++++++--- test/install-station-dgx-os.test.ts | 1 + test/station-doc-ownership.test.ts | 2 ++ 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/get-started/dgx-station-preparation.mdx b/docs/get-started/dgx-station-preparation.mdx index 329a2ba072f..f25176c5903 100644 --- a/docs/get-started/dgx-station-preparation.mdx +++ b/docs/get-started/dgx-station-preparation.mdx @@ -25,6 +25,8 @@ The flag bypasses only the `/etc/dgx-release` allowlist. It still requires ARM64 Ubuntu 24.04, DGX Station GB300 hardware identity, exactly one healthy GB300 for inference, zero volatile GB300 ECC errors, adequate storage, healthy required system services, no active agent, inference, or Docker workloads, and working Docker, Buildx, NVIDIA Container Toolkit, CDI, and container GPU-visibility probes. The override preserves the existing factory driver, packages, and container-runtime configuration; it does not install or repair missing factory components. It cannot enable Station Express on another Station generation or hardware platform. +This override exists for current genuine Station factory images whose `/etc/dgx-release` metadata omits or varies fields used by the exact automatic allowlist. +Remove the override after those images publish recognized metadata or NemoClaw replaces the exact allowlist with a separately qualified platform-family and runtime-capability check. Run the override from an interactive terminal: diff --git a/scripts/prepare-dgx-station-host.sh b/scripts/prepare-dgx-station-host.sh index c24153e8126..31ec208b94c 100755 --- a/scripts/prepare-dgx-station-host.sh +++ b/scripts/prepare-dgx-station-host.sh @@ -110,6 +110,10 @@ station_pci_devices_path() { printf '%s' /sys/bus/pci/devices } +reboot_required() { + [[ -e /var/run/reboot-required ]] +} + dgx_station_release_file_is_safe() { local path=$1 metadata uid gid mode size [[ -r "$path" && -f "$path" && ! -L "$path" ]] || return 1 @@ -1472,8 +1476,9 @@ run_apply() { common_preflight if station_uses_factory_runtime; then - [[ ! -e /var/run/reboot-required ]] \ - || fatal "A reboot is pending on the Station factory image; reboot before running Station express install" + if reboot_required; then + fatal "A reboot is pending on the Station factory image; reboot before running Station express install" + fi if [[ "$STATION_HOST_PROFILE" == "colossus-baseos" ]]; then finish_runtime fi @@ -1500,7 +1505,7 @@ run_apply() { require_command readlink require_command sha256sum - if [[ -e /var/run/reboot-required ]]; then + if reboot_required; then if all_packages_exact && ! driver_loaded_exact; then warn "A reboot is required before runtime setup can continue" exit "$REBOOT_REQUIRED_EXIT" diff --git a/test/install-station-dgx-os.test.ts b/test/install-station-dgx-os.test.ts index 9fe50458b83..3832000b4c4 100644 --- a/test/install-station-dgx-os.test.ts +++ b/test/install-station-dgx-os.test.ts @@ -512,6 +512,7 @@ require_command() { } acquire_sudo() { :; } common_preflight() { STATION_HOST_PROFILE=forced-factory-runtime; } +reboot_required() { return 1; } verify_dgx_os_runtime_sudo() { printf 'FACTORY_RUNTIME_VALIDATED\n'; } ensure_docker_group() { printf 'DOCKER_GROUP_PRESENT\n'; } install_packages() { printf 'PACKAGE_MUTATION\n'; return 1; } diff --git a/test/station-doc-ownership.test.ts b/test/station-doc-ownership.test.ts index 3a260440b79..1410854a676 100644 --- a/test/station-doc-ownership.test.ts +++ b/test/station-doc-ownership.test.ts @@ -52,6 +52,8 @@ describe("DGX Station documentation ownership", () => { expect(stationPreparation).toContain("DGX Server for GALAXY-GB300"); expect(quickstart).toContain("DGX Server for GALAXY-GB300"); expect(stationPreparation).toContain("--force-station-install"); + expect(stationPreparation).toContain("metadata omits or varies fields"); + expect(stationPreparation).toContain("Remove the override after"); expect(quickstart).toContain("--force-station-install"); expect(platformSupport).toContain("maintainer-directed temporary metadata override"); expect(vllmSetup).toContain("maintainer-directed temporary metadata override"); From 8101adb6d801b4c5d3072e5fa0bd562ad3d5632f Mon Sep 17 00:00:00 2001 From: Senthil Ravichandran Date: Fri, 17 Jul 2026 18:18:42 -0700 Subject: [PATCH 3/4] docs(station): clarify metadata override intent Signed-off-by: Senthil Ravichandran --- ci/platform-matrix.json | 2 +- docs/get-started/dgx-station-preparation.mdx | 3 ++- docs/get-started/quickstart.mdx | 2 +- docs/inference/set-up-vllm.mdx | 2 +- docs/reference/platform-support.mdx | 2 +- test/station-doc-ownership.test.ts | 4 ++-- 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/ci/platform-matrix.json b/ci/platform-matrix.json index bac2881a971..8e5f2de21ce 100644 --- a/ci/platform-matrix.json +++ b/ci/platform-matrix.json @@ -66,7 +66,7 @@ "prd_priority": "P1", "ci_tested": false, "prerequisites_notes": "Deferred. For evaluation on a qualifying DGX Station GB300, see [Additional Setup for DGX Station](prerequisites/dgx-station-preparation) before the Quickstart.", - "notes": "The PRD marks this platform as P1. Workstation form-factor with NVIDIA GPUs and the same Docker + NVIDIA Container Toolkit + CDI requirements as DGX Spark. Station remains Deferred. For canonical hardware qualification, image requirements, preparation, repair limits, reboot handoff, and the maintainer-directed temporary metadata override, see [Prepare DGX Station to Install NemoClaw](../get-started/prerequisites/dgx-station-preparation). On a Station GB300, the installer accepts either a clean generic Ubuntu 24.04 ARM64 image or stock DGX OS `7.2.0`, `7.4.0`, or `7.5.0` when a safe, root-owned `/etc/dgx-release` marker identifies `DGX Server for GALAXY-GB300`. The stock DGX OS path validates the factory GB300 driver and volatile ECC state, Docker and containerd, Buildx, `nvidia-ctk`, CDI, and GPU visibility through both CDI and `--gpus all` before it proceeds; it does not replace host packages or rewrite the runtime. By default, unknown DGX OS versions, NVIDIA BaseOS images, unsafe release markers, and other Station generations stop before host preparation. The installer offers express install with the pinned `nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-NVFP4` recipe, including an approximately 352 GB model download, without follow-up provider, model, policy, or sandbox-name choices. On Station GB300 only, direct-GPU sandbox policies add the exact read-only BDF directory for each host-discovered NVIDIA display-class PCI device, plus only existing CPU, memory, NUMA topology, and NVIDIA module initialization paths; they do not add `/sys`, the PCI parent subtree, or sysfs write access. One physical DGX OS `7.5.0` GB300 validation completed with released OpenShell `0.0.85`, local Nemotron Ultra serving, sandbox `cuInit(0)`, and a Hermes write/read file-tool task. In that validation, reads of `/sys/fs/cgroup/cgroup.controllers` and `/sys/class/net/lo/address` remained denied. DGX Station remains Deferred pending repeat qualification from a clean host and CI coverage. Pass `--station-deepseek` to use `deepseek-ai/DeepSeek-V4-Flash` while retaining the one-confirmation express flow. The flag requires an interactive terminal, and `/dev/tty` must be available when the installer runs through `curl | bash`. For headless setup on a qualifying Station, select `NEMOCLAW_PROVIDER=install-vllm` and `NEMOCLAW_VLLM_MODEL=deepseek-v4-flash` instead. Direct managed-vLLM onboarding still defaults to `deepseek-ai/DeepSeek-V4-Flash` when no model override is set." + "notes": "The PRD marks this platform as P1. Workstation form-factor with NVIDIA GPUs and the same Docker + NVIDIA Container Toolkit + CDI requirements as DGX Spark. Station remains Deferred. For canonical hardware qualification, image requirements, preparation, repair limits, reboot handoff, and the explicit temporary metadata override, see [Prepare DGX Station to Install NemoClaw](../get-started/prerequisites/dgx-station-preparation). On a Station GB300, the installer accepts either a clean generic Ubuntu 24.04 ARM64 image or stock DGX OS `7.2.0`, `7.4.0`, or `7.5.0` when a safe, root-owned `/etc/dgx-release` marker identifies `DGX Server for GALAXY-GB300`. The stock DGX OS path validates the factory GB300 driver and volatile ECC state, Docker and containerd, Buildx, `nvidia-ctk`, CDI, and GPU visibility through both CDI and `--gpus all` before it proceeds; it does not replace host packages or rewrite the runtime. By default, unknown DGX OS versions, NVIDIA BaseOS images, unsafe release markers, and other Station generations stop before host preparation. The installer offers express install with the pinned `nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-NVFP4` recipe, including an approximately 352 GB model download, without follow-up provider, model, policy, or sandbox-name choices. On Station GB300 only, direct-GPU sandbox policies add the exact read-only BDF directory for each host-discovered NVIDIA display-class PCI device, plus only existing CPU, memory, NUMA topology, and NVIDIA module initialization paths; they do not add `/sys`, the PCI parent subtree, or sysfs write access. One physical DGX OS `7.5.0` GB300 validation completed with released OpenShell `0.0.85`, local Nemotron Ultra serving, sandbox `cuInit(0)`, and a Hermes write/read file-tool task. In that validation, reads of `/sys/fs/cgroup/cgroup.controllers` and `/sys/class/net/lo/address` remained denied. DGX Station remains Deferred pending repeat qualification from a clean host and CI coverage. Pass `--station-deepseek` to use `deepseek-ai/DeepSeek-V4-Flash` while retaining the one-confirmation express flow. The flag requires an interactive terminal, and `/dev/tty` must be available when the installer runs through `curl | bash`. For headless setup on a qualifying Station, select `NEMOCLAW_PROVIDER=install-vllm` and `NEMOCLAW_VLLM_MODEL=deepseek-v4-flash` instead. Direct managed-vLLM onboarding still defaults to `deepseek-ai/DeepSeek-V4-Flash` when no model override is set." }, { "name": "NVIDIA RTX (consumer and Pro workstation GPUs)", diff --git a/docs/get-started/dgx-station-preparation.mdx b/docs/get-started/dgx-station-preparation.mdx index f25176c5903..199d0262ea9 100644 --- a/docs/get-started/dgx-station-preparation.mdx +++ b/docs/get-started/dgx-station-preparation.mdx @@ -20,7 +20,8 @@ By default, an unknown version, malformed or unsafe marker, NVIDIA BaseOS image, On an unqualified system, set `NEMOCLAW_PROVIDER` or `NEMOCLAW_NO_EXPRESS=1` explicitly to continue without Station host automation. -Use `--force-station-install` only when a NemoClaw maintainer directs you to continue on a genuine DGX Station GB300 whose DGX release metadata is not recognized. +Use `--force-station-install` only on a genuine DGX Station GB300 when automatic Station detection rejects its DGX release metadata. +The flag handles metadata variants not yet recognized by NemoClaw; it is not a general compatibility bypass. The flag bypasses only the `/etc/dgx-release` allowlist. It still requires ARM64 Ubuntu 24.04, DGX Station GB300 hardware identity, exactly one healthy GB300 for inference, zero volatile GB300 ECC errors, adequate storage, healthy required system services, no active agent, inference, or Docker workloads, and working Docker, Buildx, NVIDIA Container Toolkit, CDI, and container GPU-visibility probes. The override preserves the existing factory driver, packages, and container-runtime configuration; it does not install or repair missing factory components. diff --git a/docs/get-started/quickstart.mdx b/docs/get-started/quickstart.mdx index e0899d19679..b9982ea8c18 100644 --- a/docs/get-started/quickstart.mdx +++ b/docs/get-started/quickstart.mdx @@ -124,7 +124,7 @@ Use these details when your first-run path needs more control. On a qualifying Station, accepting the prompt selects the pinned `nemotron-3-ultra-550b-a55b` managed-vLLM recipe and completes onboarding without more provider, model, policy, or sandbox-name choices. [Prepare DGX Station to Install NemoClaw](prerequisites/dgx-station-preparation) defines Station qualification, generic Ubuntu preparation, stock DGX OS validation, repair limits, and reboot handoff. By default, unknown versions, unsafe release markers, NVIDIA BaseOS images, and other Station generations stop before host preparation; set `NEMOCLAW_PROVIDER` or `NEMOCLAW_NO_EXPRESS=1` to bypass Station host automation. - For a maintainer-directed temporary override on genuine Station GB300 hardware with unrecognized release metadata, follow the `--force-station-install` safeguards in the Station preparation guide. + For an explicit temporary override on genuine Station GB300 hardware with unrecognized release metadata, follow the `--force-station-install` safeguards in the Station preparation guide. One physical DGX OS `7.5.0` GB300 validation completed, but Station remains Deferred pending repeat clean-host qualification and CI coverage. Pass `--station-deepseek` to use DeepSeek V4 Flash for a Station demo instead; the flag selects the interactive prompt and requires terminal access. Refer to [Platform Support](../reference/platform-support) and [Choose an Inference Provider](../inference/learn-and-choose/choose-inference-provider) for the current platform behavior. diff --git a/docs/inference/set-up-vllm.mdx b/docs/inference/set-up-vllm.mdx index 6314247217e..f4416b1c991 100644 --- a/docs/inference/set-up-vllm.mdx +++ b/docs/inference/set-up-vllm.mdx @@ -166,7 +166,7 @@ The container publishes port `8000` through Docker, so apply the firewall guidan DGX Station remains Deferred. On DGX Station GB300, NemoClaw can prepare a clean generic Ubuntu 24.04 ARM64 host for the reviewed Station path. By default, stock DGX OS `7.2.0`, `7.4.0`, or `7.5.0` requires the supported release marker before NemoClaw validates the factory GPU runtime in place. -For the maintainer-directed temporary metadata override, follow [Prepare DGX Station to Install NemoClaw](../../get-started/prerequisites/dgx-station-preparation). +For the explicit temporary metadata override, follow [Prepare DGX Station to Install NemoClaw](../../get-started/prerequisites/dgx-station-preparation). On Station GB300 only, a direct GPU sandbox receives the exact read-only BDF directory for each host-discovered NVIDIA display-class PCI device, plus the existing CPU, memory, NUMA topology, and NVIDIA module initialization paths. The policy does not grant `/sys`, the PCI parent subtree, or sysfs write access. In the physical DGX OS `7.5.0` validation, this exact-BDF policy passed sandbox `cuInit(0)` while reads of `/sys/fs/cgroup/cgroup.controllers` and `/sys/class/net/lo/address` remained denied. diff --git a/docs/reference/platform-support.mdx b/docs/reference/platform-support.mdx index c3238375062..f129d8802d4 100644 --- a/docs/reference/platform-support.mdx +++ b/docs/reference/platform-support.mdx @@ -79,7 +79,7 @@ For install requirements and the shorter setup-oriented platform view, refer to | OS | Container runtime | Status | PRD priority | CI | Notes | |----|-------------------|--------|--------------|----|-------| | DGX OS (Spark) | Docker | Tested | P1 | Yes | Use the standard installer and `$$nemoclaw onboard`. For an end-to-end walkthrough with local inference, see the [NVIDIA Spark playbook](https://build.nvidia.com/spark/nemoclaw). | -| DGX OS (Station) | Docker | Deferred | P1 | No | The PRD marks this platform as P1. Workstation form-factor with NVIDIA GPUs and the same Docker + NVIDIA Container Toolkit + CDI requirements as DGX Spark. Station remains Deferred. For canonical hardware qualification, image requirements, preparation, repair limits, reboot handoff, and the maintainer-directed temporary metadata override, see [Prepare DGX Station to Install NemoClaw](../get-started/prerequisites/dgx-station-preparation). On a Station GB300, the installer accepts either a clean generic Ubuntu 24.04 ARM64 image or stock DGX OS `7.2.0`, `7.4.0`, or `7.5.0` when a safe, root-owned `/etc/dgx-release` marker identifies `DGX Server for GALAXY-GB300`. The stock DGX OS path validates the factory GB300 driver and volatile ECC state, Docker and containerd, Buildx, `nvidia-ctk`, CDI, and GPU visibility through both CDI and `--gpus all` before it proceeds; it does not replace host packages or rewrite the runtime. By default, unknown DGX OS versions, NVIDIA BaseOS images, unsafe release markers, and other Station generations stop before host preparation. The installer offers express install with the pinned `nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-NVFP4` recipe, including an approximately 352 GB model download, without follow-up provider, model, policy, or sandbox-name choices. On Station GB300 only, direct-GPU sandbox policies add the exact read-only BDF directory for each host-discovered NVIDIA display-class PCI device, plus only existing CPU, memory, NUMA topology, and NVIDIA module initialization paths; they do not add `/sys`, the PCI parent subtree, or sysfs write access. One physical DGX OS `7.5.0` GB300 validation completed with released OpenShell `0.0.85`, local Nemotron Ultra serving, sandbox `cuInit(0)`, and a Hermes write/read file-tool task. In that validation, reads of `/sys/fs/cgroup/cgroup.controllers` and `/sys/class/net/lo/address` remained denied. DGX Station remains Deferred pending repeat qualification from a clean host and CI coverage. Pass `--station-deepseek` to use `deepseek-ai/DeepSeek-V4-Flash` while retaining the one-confirmation express flow. The flag requires an interactive terminal, and `/dev/tty` must be available when the installer runs through `curl \| bash`. For headless setup on a qualifying Station, select `NEMOCLAW_PROVIDER=install-vllm` and `NEMOCLAW_VLLM_MODEL=deepseek-v4-flash` instead. Direct managed-vLLM onboarding still defaults to `deepseek-ai/DeepSeek-V4-Flash` when no model override is set. | +| DGX OS (Station) | Docker | Deferred | P1 | No | The PRD marks this platform as P1. Workstation form-factor with NVIDIA GPUs and the same Docker + NVIDIA Container Toolkit + CDI requirements as DGX Spark. Station remains Deferred. For canonical hardware qualification, image requirements, preparation, repair limits, reboot handoff, and the explicit temporary metadata override, see [Prepare DGX Station to Install NemoClaw](../get-started/prerequisites/dgx-station-preparation). On a Station GB300, the installer accepts either a clean generic Ubuntu 24.04 ARM64 image or stock DGX OS `7.2.0`, `7.4.0`, or `7.5.0` when a safe, root-owned `/etc/dgx-release` marker identifies `DGX Server for GALAXY-GB300`. The stock DGX OS path validates the factory GB300 driver and volatile ECC state, Docker and containerd, Buildx, `nvidia-ctk`, CDI, and GPU visibility through both CDI and `--gpus all` before it proceeds; it does not replace host packages or rewrite the runtime. By default, unknown DGX OS versions, NVIDIA BaseOS images, unsafe release markers, and other Station generations stop before host preparation. The installer offers express install with the pinned `nvidia/NVIDIA-Nemotron-3-Ultra-550B-A55B-NVFP4` recipe, including an approximately 352 GB model download, without follow-up provider, model, policy, or sandbox-name choices. On Station GB300 only, direct-GPU sandbox policies add the exact read-only BDF directory for each host-discovered NVIDIA display-class PCI device, plus only existing CPU, memory, NUMA topology, and NVIDIA module initialization paths; they do not add `/sys`, the PCI parent subtree, or sysfs write access. One physical DGX OS `7.5.0` GB300 validation completed with released OpenShell `0.0.85`, local Nemotron Ultra serving, sandbox `cuInit(0)`, and a Hermes write/read file-tool task. In that validation, reads of `/sys/fs/cgroup/cgroup.controllers` and `/sys/class/net/lo/address` remained denied. DGX Station remains Deferred pending repeat qualification from a clean host and CI coverage. Pass `--station-deepseek` to use `deepseek-ai/DeepSeek-V4-Flash` while retaining the one-confirmation express flow. The flag requires an interactive terminal, and `/dev/tty` must be available when the installer runs through `curl \| bash`. For headless setup on a qualifying Station, select `NEMOCLAW_PROVIDER=install-vllm` and `NEMOCLAW_VLLM_MODEL=deepseek-v4-flash` instead. Direct managed-vLLM onboarding still defaults to `deepseek-ai/DeepSeek-V4-Flash` when no model override is set. | | Linux | Docker | Tested | P0 | Yes | Primary tested path. Ubuntu 24.04 has host-level onboarding validation. A digest-pinned Ubuntu 26.04 userspace lane builds the CLI and runs preflight, installer, and platform contracts on eligible main pushes; Docker-host, AppArmor, Landlock, and live onboarding validation on 26.04 remain pending. Other distros (Ubuntu 22.04, Fedora, Rocky, Alma, NixOS, Arch) may work but are not validated. | | macOS (Apple Silicon) | Colima, Docker Desktop | Tested with limitations | P0 | Yes | Start the container runtime (Colima or Docker Desktop) before running the installer. Homebrew Colima users must install both Colima and the Docker CLI (`brew install colima docker`) before `docker info` can work. Xcode Command Line Tools (`xcode-select --install`) are typically required for Node native modules during install. NemoClaw recommends them but does not enforce them during preflight. | | NVIDIA RTX (consumer and Pro workstation GPUs) | Docker | Deferred | P1 | No | The PRD marks this platform as P1. Covers RTX consumer cards and RTX Pro workstation cards on Linux hosts that meet the generic-Linux-GPU requirements (NVIDIA Container Toolkit + CDI present). The provider menu emits managed vLLM behind `NEMOCLAW_EXPERIMENTAL=1` or `NEMOCLAW_PROVIDER=install-vllm` for this host class today; the end-to-end onboard path on this hardware is not yet validated in CI. | diff --git a/test/station-doc-ownership.test.ts b/test/station-doc-ownership.test.ts index 1410854a676..4b358eef518 100644 --- a/test/station-doc-ownership.test.ts +++ b/test/station-doc-ownership.test.ts @@ -55,8 +55,8 @@ describe("DGX Station documentation ownership", () => { expect(stationPreparation).toContain("metadata omits or varies fields"); expect(stationPreparation).toContain("Remove the override after"); expect(quickstart).toContain("--force-station-install"); - expect(platformSupport).toContain("maintainer-directed temporary metadata override"); - expect(vllmSetup).toContain("maintainer-directed temporary metadata override"); + expect(platformSupport).toContain("explicit temporary metadata override"); + expect(vllmSetup).toContain("explicit temporary metadata override"); expect(stationPreparation).toMatch(/(?:DGX )?Station(?: remains|'s) Deferred/); expect(stationPreparation).toContain("One physical DGX OS `7.5.0` GB300 validation completed"); expect(stationPreparation).toContain("[Platform Support](../../reference/platform-support)"); From 91b73d18927217595c88295889900f4a9f71ba0a Mon Sep 17 00:00:00 2001 From: Senthil Ravichandran Date: Fri, 17 Jul 2026 18:37:28 -0700 Subject: [PATCH 4/4] fix(station): normalize BaseOS telemetry identity Signed-off-by: Senthil Ravichandran --- scripts/prepare-dgx-station-host.sh | 21 ++++++++- test/install-station-dgx-os.test.ts | 71 +++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/scripts/prepare-dgx-station-host.sh b/scripts/prepare-dgx-station-host.sh index 31ec208b94c..f41b91eb3df 100755 --- a/scripts/prepare-dgx-station-host.sh +++ b/scripts/prepare-dgx-station-host.sh @@ -81,7 +81,7 @@ readonly -a BASEOS_PACKAGE_SPECS=( readonly BASEOS_CLOUD_CFG_SHA256="038ba435093de59f4a21021caf6c921d63344e9aae3b88795ee5b2659f43f437" readonly BASEOS_CLOUD_INIT_UNIT_SHA256="e13dd95a7bfac6407ea1ce45ed6683c0f4e84c791840d305c937d38ae77d9456" readonly BASEOS_FLUENT_BIT_UNIT_SHA256="1854339f563e518894c156d081912595d2d6e175a1ed6692e74e88224b6bad5f" -readonly BASEOS_FLUENT_BIT_CFG_SHA256="bb380bf6103957cdd7440dfba60107b7a3f50db3c0e75c483a6bfdb5e046201c" +readonly BASEOS_FLUENT_BIT_CFG_NORMALIZED_SHA256="ffec8b1bcc628877b9a230c6b26313b5ee6b25c20398580832133dbb15349551" readonly BASEOS_FLUENT_BIT_PARSERS_SHA256="760e6a347874a6cbdc10c6cd21d82d1ee5388c8573ddfaab05ef37904749dbe1" readonly BASEOS_FLUENT_BIT_PLUGINS_SHA256="9d5aad2c1be151b4d35de53a460f9783f98ac3cc815ebc638b0e8489f4ecd577" readonly BASEOS_FWUPD_UNIT_SHA256="835e7c291761c247d3cd5c64652b768c6a7fdc7cc72fea1bf70fc92e4cb3cfd5" @@ -389,6 +389,21 @@ file_sha256_matches() { [[ "$actual" == "$expected" ]] } +baseos_fluent_bit_config_matches() { + local path=$1 expected=$2 actual + root_owned_file_is_not_writable_by_group_or_other "$path" || return 1 + actual="$({ + LC_ALL=C sed -E \ + -e 's/^([[:space:]]*Add Hostname) [A-Za-z0-9][A-Za-z0-9._-]*$/\1 /' \ + -e 's/^([[:space:]]*Add MAC) ([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$/\1 /' \ + -e 's/^([[:space:]]*Add IP) ([0-9]{1,3}\.){3}[0-9]{1,3}$/\1 /' \ + "$path" \ + | sha256sum \ + | awk '{print $1}' + } 2>/dev/null)" || return 1 + [[ "$actual" == "$expected" ]] +} + systemd_property_matches() { local unit=$1 property=$2 expected=$3 actual actual="$(systemctl show "$unit" -p "$property" --value 2>/dev/null)" || return 1 @@ -422,7 +437,8 @@ baseos_cloud_init_failure_is_qualified() { baseos_fluent_bit_failure_is_qualified() { baseos_failed_unit_matches fluent-bit.service \ /usr/lib/systemd/system/fluent-bit.service "$BASEOS_FLUENT_BIT_UNIT_SHA256" enabled 1 \ - && file_sha256_matches /etc/fluent-bit/fluent-bit.conf "$BASEOS_FLUENT_BIT_CFG_SHA256" \ + && baseos_fluent_bit_config_matches \ + /etc/fluent-bit/fluent-bit.conf "$BASEOS_FLUENT_BIT_CFG_NORMALIZED_SHA256" \ && file_sha256_matches /etc/fluent-bit/parsers.conf "$BASEOS_FLUENT_BIT_PARSERS_SHA256" \ && file_sha256_matches /etc/fluent-bit/plugins.conf "$BASEOS_FLUENT_BIT_PLUGINS_SHA256" } @@ -867,6 +883,7 @@ common_preflight() { require_command getent require_command grep require_command ps + require_command sed require_command sha256sum require_command ss require_command stat diff --git a/test/install-station-dgx-os.test.ts b/test/install-station-dgx-os.test.ts index 3832000b4c4..8b31d3c6655 100644 --- a/test/install-station-dgx-os.test.ts +++ b/test/install-station-dgx-os.test.ts @@ -617,6 +617,77 @@ baseos_failed_unit_matches cloud-init.service /usr/lib/systemd/system/cloud-init expect(drifted.result.status, drifted.output).not.toBe(0); }); + it("qualifies the BaseOS Fluent Bit template independently of host identity", () => { + const configDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-fluent-bit-")); + const config = path.join(configDir, "fluent-bit.conf"); + fs.writeFileSync( + config, + [ + "[FILTER]", + " Name modify", + " Match *", + " Add Hostname station-a", + " Add MAC A4:A6:8D:00:00:01", + " Add IP 10.88.4.21", + "", + ].join("\n"), + ); + const normalized = spawnSync( + "bash", + [ + "--noprofile", + "--norc", + "-c", + String.raw`sed -E -e 's/^([[:space:]]*Add Hostname) [A-Za-z0-9][A-Za-z0-9._-]*$/\1 /' -e 's/^([[:space:]]*Add MAC) ([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$/\1 /' -e 's/^([[:space:]]*Add IP) ([0-9]{1,3}\.){3}[0-9]{1,3}$/\1 /' "$CONFIG" | sha256sum | awk '{print $1}'`, + ], + { encoding: "utf-8", env: { ...process.env, CONFIG: config } }, + ).stdout.trim(); + + const exact = runSourced( + STATION_PREPARE, + ` +root_owned_file_is_not_writable_by_group_or_other() { return 0; } +baseos_fluent_bit_config_matches "$FLUENT_BIT_CONFIG" "$EXPECTED_SHA" +`, + { + EXPECTED_SHA: normalized, + FLUENT_BIT_CONFIG: config, + PATH: process.env.PATH ?? TEST_SYSTEM_PATH, + }, + ); + expect(exact.result.status, exact.output).toBe(0); + + fs.writeFileSync(config, fs.readFileSync(config, "utf-8").replace("station-a", "station-b")); + const differentHost = runSourced( + STATION_PREPARE, + ` +root_owned_file_is_not_writable_by_group_or_other() { return 0; } +baseos_fluent_bit_config_matches "$FLUENT_BIT_CONFIG" "$EXPECTED_SHA" +`, + { + EXPECTED_SHA: normalized, + FLUENT_BIT_CONFIG: config, + PATH: process.env.PATH ?? TEST_SYSTEM_PATH, + }, + ); + expect(differentHost.result.status, differentHost.output).toBe(0); + + fs.writeFileSync(config, fs.readFileSync(config, "utf-8").replace("Match *", "Match changed")); + const changedTemplate = runSourced( + STATION_PREPARE, + ` +root_owned_file_is_not_writable_by_group_or_other() { return 0; } +baseos_fluent_bit_config_matches "$FLUENT_BIT_CONFIG" "$EXPECTED_SHA" +`, + { + EXPECTED_SHA: normalized, + FLUENT_BIT_CONFIG: config, + PATH: process.env.PATH ?? TEST_SYSTEM_PATH, + }, + ); + expect(changedTemplate.result.status, changedTemplate.output).not.toBe(0); + }); + it("keeps stock DGX OS out of the generic package mutation path", () => { const { result, output } = runSourced( STATION_PREPARE,