Nix Runtime¶
Goal¶
The Nix setup in this repo is designed to provide a stable Linux development runtime with a narrow host boundary:
Nix owns Python, compiler toolchains, FFmpeg, OpenGL/Vulkan userspace, CUDA userspace, and project Python packages.
The host only provides the NVIDIA driver boundary:
libcuda.so.1NVIDIA GL/EGL/Vulkan driver libraries
Vulkan ICD JSON
EGL vendor JSON
This is the closest practical approximation to “one reproducible dev shell across Linux hosts with NVIDIA drivers installed”.
The intended consumption model is library-first:
prefer
import simplefrom inside the dev shelltreat
uv run eval,datagen, and similar CLI entry points as convenience wrappers around the Python APIif another project integrates SIMPLE, it should usually depend on the package/runtime, not shell out to the CLI
Why The Setup Exists¶
The environment is more complex than a pure CPU Python project because this repo mixes:
Python packaging managed by
uvNative extension builds for vendored packages
CUDA userspace managed by Nix
NVIDIA driver userspace managed by the host
Isaac Sim / CuRobo GPU-specific post-install work
The complexity does not come from Nix alone. It comes from the boundary between:
reproducible userspace we can manage in Nix
GPU driver components that remain host-specific
Supported Host Model¶
The intended host baseline is:
Linux
NVIDIA drivers already installed
Ubuntu-style driver layout is explicitly supported
NixOS-style
/run/opengl-driver/...layout is also supportedWSL driver layout is partially supported through
/usr/lib/wsl/libat least ~80 GiB free on
/nixis strongly recommended for a first CUDA-heavy bootstrapat least ~20 GiB free on the workspace filesystem is recommended for caches, extracted wheels, and virtualenvs
Current host lookup paths:
CUDA:
/run/opengl-driver/lib/libcuda.so.1/usr/lib/x86_64-linux-gnu/libcuda.so.1/usr/lib/x86_64-linux-gnu/nvidia/current/libcuda.so.1/usr/lib/x86_64-linux-gnu/nvidia/libcuda.so.1/usr/lib/wsl/lib/libcuda.so.1
Vulkan ICD:
/run/opengl-driver/share/vulkan/icd.d/nvidia_icd.x86_64.json/usr/share/vulkan/icd.d/nvidia_icd.json/usr/share/vulkan/icd.d/nvidia_icd.x86_64.json/etc/vulkan/icd.d/nvidia_icd.json
EGL vendor:
/run/opengl-driver/share/glvnd/egl_vendor.d/10_nvidia.json/usr/share/glvnd/egl_vendor.d/10_nvidia.json/usr/share/glvnd/egl_vendor.d/50_nvidia.json
If an Ubuntu host keeps NVIDIA files elsewhere, update scripts/nix/common.sh.
Runtime Layout¶
1. Base Nix runtime¶
Owns:
Python runtime
wrapped compiler toolchain
libc handling via the wrapped compiler toolchain
CUDA toolkit userspace
FFmpeg and graphics userspace libs
Important rule:
do not add
glibc.devas a regular shell package to “help” C/C++ buildsthe wrapped compiler already knows how to find libc headers in the right order
adding
glibc.devdirectly can break libstdc++’s#include_nextchain forstdlib.h
2. Host NVIDIA bridge¶
Owns:
staging host NVIDIA driver libraries into
.runtime-state/host-libcudaexposing Vulkan/EGL host metadata into the shell
4. Shell orchestration¶
Owns:
per-shell writable cache/config dirs
project virtualenv path policy
shell-time assertions
optional auto-bootstrap
5. Bootstrap phases¶
Python/bootstrap phase: scripts/nix/bootstrap-python.sh
GPU/bootstrap phase: scripts/nix/bootstrap-gpu.sh
Convenience wrapper: scripts/nix/bootstrap.sh
This split is intentional:
Python/bootstrap should remain readable and mostly host-independent.
GPU/bootstrap is where CuRobo and GPU-specific work belongs.
Runtime Invariants¶
The shell/bootstrap assert the following:
PYTHONPATHis unsetPYTHONHOMEis unsetLD_PRELOADis unsetPYTHONNOUSERSITE=1VIRTUAL_ENV == UV_PROJECT_ENVIRONMENTLD_LIBRARY_PATHonly contains:/nix/store/....runtime-state/host-libcudaapproved NVIDIA host-driver paths
If those invariants fail, the scripts should stop with an exact error instead of silently continuing with a polluted runtime.
Entry Points¶
Check prerequisites on a new host¶
Run this first on a new machine:
./scripts/nix/prereq-check.sh
It checks:
required commands such as
nix,git,git-lfs, anduvwhether
nixworks withLD_LIBRARY_PATHclearedfree space on
/nixand the workspace filesystemsupported NVIDIA driver file layout
required vendored dependencies such as
third_party/curoboobvious outer-shell pollution such as
LD_LIBRARY_PATH,PYTHONPATH, orCONDA_PREFIX
Start the shell¶
direnv allow
or
nix --extra-experimental-features "nix-command flakes" develop
Bootstrap explicitly¶
./scripts/nix/bootstrap.sh
Use SIMPLE as a library¶
Inside the dev shell:
import gymnasium as gym
import simple.envs as _
env = gym.make("simple/FrankaTabletopGrasp-v0", sim_mode="isaac", headless=True)
obs, info = env.reset()
env.close()
Prefer this integration model over invoking the CLI from another Python application.
Run only the Python phase¶
./scripts/nix/bootstrap-python.sh
Run only the GPU phase¶
./scripts/nix/bootstrap-gpu.sh
Failure Model¶
The setup should fail early in these cases:
host NVIDIA runtime files are missing
runtime env variables are polluted by the outer shell
vendored dependencies are missing or uninitialized
GPU architecture cannot be detected automatically
/nixor the workspace filesystem is too full to realize the CUDA/Python closure reliably
That is deliberate. Silent fallback to host libraries is harder to debug and less reproducible than a hard assertion.
Why We Still Cannot Be Perfectly Host-Independent¶
Even with Nix managing almost all userspace, the following still come from the host:
running NVIDIA kernel driver
driver-provided user libraries matched to that kernel driver
Vulkan/EGL metadata installed by the driver package
So the reproducibility boundary is:
reproducible above the driver boundary
host-dependent at the driver boundary
That is the practical limit for GPU-heavy Linux development without fully containerizing or fully standardizing the host OS image.
Maintenance Rule¶
When changing the runtime:
Put shared policy in scripts/nix/common.sh
Keep flake.nix as orchestration only
Put Nix-owned userspace in nix/runtime-base.nix
Put host NVIDIA bridging in nix/runtime-host-gpu.nix
Keep Python bootstrap separate from GPU bootstrap
Keep the user-facing contract stable:
prereq-check -> nix develop -> import simple
If a new fix requires copying shell logic into multiple files, that is usually a sign the fix belongs in common.sh instead.