Phase 3 — Nix Installation Inside Darling#
Priority: P0 · Effort: M (2–3 weeks) · Depends on: Phase 1 (syscall fixes), Phase 2 (sandbox stub)
With the syscall fixes from Phase 1 and the sandbox-exec stub from Phase 2,
the Nix package manager should be installable inside a Darling prefix. This
phase covers automating that installation, verifying core Nix commands, and
providing convenient wrappers for host-side usage.
Context#
The official Nix installer for macOS (nix-*-x86_64-darwin) has several
assumptions that conflict with Darling's environment:
-
Forces multi-user mode on Darwin. The installer detects
uname -s=Darwinand refuses single-user installation. Multi-user mode requiresdseditgroup,sysadminctl, and a workinglaunchd— none of which are fully functional in Darling yet. -
Requires
diskutil info /to check the root filesystem type (APFS vs HFS+). Darling'sdiskutilis a shell script that only supportseject. -
Requires
xmllintfor parsing plists. Not shipped in Darling. -
Requires Directory Services (
dseditgroup,dscl) for creating thenixbldgroup and build users. -
Calls
lchflagsduring profile installation (fixed in Phase 1). -
Root user quirks. Nix defaults
build-users-group = nixbldwhen running as root. In single-user mode as root, this must be overridden to empty.
All of these are solvable with a patched installer script and pre-configured
nix.conf.
Tasks#
3.1 — Create Automated Nix-in-Darling Installer#
Create a script at scripts/install-nix-in-darling.sh (run from the Linux
host) that automates the entire Nix installation inside a Darling prefix.
Steps the script should perform:
-
Verify prerequisites:
- Darling is installed and
darling shell echo okworks. - The prefix is initialized (
~/.darlingor$DPREFIXexists). - Phase 1 and Phase 2 fixes are in place (check for
/usr/bin/sandbox-execinside the prefix).
- Darling is installed and
-
Pre-configure Nix:
darling shell mkdir -p /etc/nix darling shell tee /etc/nix/nix.conf <<'EOF' # Single-user mode: no build users group build-users-group = # Disable macOS sandbox (we use the sandbox-exec stub) sandbox = false # Use the Nix binary cache substituters = https://cache.nixos.org trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= EOF -
Download the Nix installer:
- Fetch the latest
nix-*-x86_64-darwininstaller tarball fromhttps://releases.nixos.org/nix/. - Verify its signature / hash.
- Extract it into a temporary directory inside the prefix.
- Fetch the latest
-
Patch the installer:
- Remove or bypass the
uname-based multi-user enforcement. - Remove the
diskutil infocheck. - Remove the
xmllintdependency (or provide a stub). - Suppress the "installing as root is not supported" warning.
- Force
--no-daemonmode.
The patching should be done with
sedor a patch file applied to the extractedinstallscript. Keep the patch minimal and well-documented so it can be updated when Nix releases new installer versions. - Remove or bypass the
-
Run the patched installer:
darling shell /tmp/nix-installer/install --no-daemon -
Post-install verification:
- Run each command from the verification checklist (see below).
- Source the Nix profile:
. /Users/root/.nix-profile/etc/profile.d/nix.sh - Print the installed Nix version.
-
Clean up:
- Remove the temporary installer files.
- Optionally run
nix-collect-garbageto free space.
Error handling: The script should set -euo pipefail and provide clear
error messages at each step, indicating which phase/blocker is likely the cause
if something fails.
3.2 — Pre-Built Darling Prefix with Nix#
Create a Nix derivation (packages.x86_64-linux.darling-nix-prefix) that
produces a Darling prefix tarball with Nix pre-installed. This lets users skip
the installation process entirely.
Approach:
- Build Darling in a Nix sandbox.
- Initialize a fresh prefix.
- Run the installer script from 3.1 inside the prefix (this requires a working Darling at build time — may need to be done in a NixOS VM test context rather than a pure derivation, since Darling needs namespace capabilities).
- Snapshot the prefix as a tarball.
- Users restore with:
mkdir -p ~/.darling tar xf /nix/store/...-darling-nix-prefix.tar -C ~/.darling
Alternative: If building inside a Nix sandbox is too complex (due to namespace requirements), provide a script that generates the prefix on the user's machine and document it as a one-time setup step.
3.3 — Verify Core Nix Commands#
After installation, the following commands must work without errors inside
darling shell. Each one exercises a different subsystem:
| Command | What It Tests |
|---|---|
nix --version |
Binary loads, dyld resolves all libraries |
nix-env --version |
Same, plus libnixstore loads correctly |
nix-store --verify |
Store database access, file system operations |
nix-instantiate --eval -E '1 + 1' |
Nix evaluator, no build needed |
nix eval --expr '1 + 1' |
Flake-enabled CLI, evaluator |
nix-store --dump-db |
SQLite database access in /nix/var/nix/db/ |
nix-env -qa hello |
Channel/registry querying, HTTP fetching |
Known potential issues at this stage:
-
SQLite: Nix's store database uses SQLite. If Darling's
fcntllocking (viaF_SETLK/F_GETLK) is buggy, database operations will fail or hang. Add SQLite lock testing to the verification. -
curl / TLS:
nix-env -qaand binary substitution need working HTTPS. Darling ships its own curl and SSL certificates. If they're outdated or the TLS handshake uses unimplemented syscalls, fetching will fail. Verify with:darling shell curl -sI https://cache.nixos.org/nix-cache-info -
/nixpath: By default, Nix installs to/nix. Inside Darling, this is within the prefix overlay at~/.darling/nix. This is fine for isolated usage. For shared-store mode (Phase 7), we'll need to symlink this to the host's/nixvia/Volumes/SystemRoot/nix.
3.4 — Host-Side Wrapper: darling-nix#
Create a convenience wrapper script (installed as part of the Darling Nix package) that runs Nix commands inside Darling from the Linux host:
#!/usr/bin/env bash
# darling-nix — run Nix commands inside a Darling prefix
set -euo pipefail
# Source Nix profile and run the command
exec darling shell bash -lc '
. /Users/root/.nix-profile/etc/profile.d/nix.sh
exec "$@"
' -- "$@"
Usage examples:
# Evaluate an expression
darling-nix nix-instantiate --eval -E '1 + 1'
# Build a trivial derivation
darling-nix nix-build --expr 'derivation { name = "test"; builder = "/bin/bash"; args = ["-c" "echo ok > $out"]; system = "x86_64-darwin"; }'
# Install a package
darling-nix nix-env -iA nixpkgs.hello
# Interactive Nix repl
darling-nix nix repl
Install location: $out/bin/darling-nix in the Darling Nix package.
Enhancements for later:
- Support
--prefix <path>to use a non-default Darling prefix. - Support
--store <path>to configure the Nix store location. - Capture and forward exit codes correctly.
- Handle signals (SIGINT, SIGTERM) and propagate them to the Darling process.
3.5 — Nix Channel / Registry Setup#
After Nix is installed, set up a usable channel or flake registry so users can immediately start building packages:
# Add the nixpkgs channel (for nix-env / nix-shell)
darling shell nix-channel --add https://nixos.org/channels/nixpkgs-unstable nixpkgs
darling shell nix-channel --update
# Or, for flakes:
darling shell nix registry add nixpkgs github:NixOS/nixpkgs/nixpkgs-unstable
This should be part of the installer script (3.1) as an optional post-install step.
Potential issue: nix-channel --update downloads and unpacks a tarball,
which exercises curl, xz, tar, and filesystem operations. Any crash here
points to remaining syscall gaps from Phase 1.
Shared Store Considerations#
For Phase 3, Nix runs with its own store inside the Darling prefix
(~/.darling/nix/store). This is the simplest setup and avoids any
interaction with the host's Nix store.
For later phases (especially Phase 7 — Remote Builder), we'll want to share the
host's /nix/store with the Darling prefix. The mechanism:
# Inside the Darling prefix, /Volumes/SystemRoot is the host's /
# So /Volumes/SystemRoot/nix/store is the host's /nix/store
# Option A: Symlink
darling shell ln -sf /Volumes/SystemRoot/nix /nix
# Option B: Bind mount (if overlayfs allows it)
# Configured in darlingserver / prefix init
This is NOT part of Phase 3 — just documented here so the installation script doesn't make assumptions that would conflict with shared-store mode later. In particular:
- Don't hardcode paths that assume
/nixis local to the prefix. - Make the store location configurable in
nix.conf. - Ensure the installer doesn't fail if
/nixis a symlink.
Debugging Tips#
If installation fails, here are the most useful debugging techniques:
Trace the installer script:
darling shell bash -x /tmp/nix-installer/install --no-daemon 2>&1 | tee install.log
Trace Nix binary startup:
# On the host, trace darlingserver while running a Nix command:
strace -f -p $(pidof darlingserver) -e trace=openat,stat,fstat,lstat,readlink 2>&1 | head -200 &
darling shell /nix/store/.../bin/nix --version
Trace inside Darling with xtrace:
DARLING_XTRACE=1 darling shell /nix/store/.../bin/nix-env --version 2>&1 | head -500
Check for unimplemented syscalls:
darling shell /nix/store/.../bin/nix --version 2>&1 | grep -i "unimplemented\|STUB\|not.implemented"
Inspect the store database:
darling shell sqlite3 /nix/var/nix/db/db.sqlite ".tables"
darling shell sqlite3 /nix/var/nix/db/db.sqlite "SELECT count(*) FROM ValidPaths;"
Verification Checklist#
After completing Phase 3, ALL of the following must pass:
-
scripts/install-nix-in-darling.shcompletes without errors -
darling shell nix --versionprints the Nix version -
darling shell nix-env --versionprints the Nix version -
darling shell nix-store --verifyreports no errors -
darling shell nix-instantiate --eval -E '1 + 1'prints2 -
darling shell nix eval --expr '1 + 1'prints2 -
darling shell curl -sI https://cache.nixos.org/nix-cache-inforeturns HTTP 200 -
darling-nix nix --versionworks from the Linux host -
darling-nix nix-instantiate --eval -E 'builtins.currentSystem'prints"x86_64-darwin" - No "Unimplemented syscall" messages during any of the above
- No segfaults during any of the above
Risk Assessment#
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| SQLite locking doesn't work | Medium | High — store operations fail | Test fcntl locking early; if broken, use PRAGMA locking_mode=EXCLUSIVE |
| curl/TLS fails | Medium | High — no binary substitution | Test HTTPS early; fall back to --option substitute false for offline mode |
| Nix installer changes break our patches | Medium | Medium — need to update patches | Pin a specific Nix version; provide a patch file rather than inline sed |
/nix path conflicts with shared store |
Low | Medium — need reconfiguration | Keep store location configurable from the start |
| Nix evaluator hits unimplemented syscalls | Low | Medium — eval works but slowly | Phase 1 triage (1.7) should catch these |