aboutsummaryrefslogtreecommitdiffstats
path: root/openspec
diff options
context:
space:
mode:
Diffstat (limited to 'openspec')
-rw-r--r--openspec/changes/archive/2026-09-24-enable-nix-ld/.openspec.yaml2
-rw-r--r--openspec/changes/archive/2026-09-24-enable-nix-ld/design.md37
-rw-r--r--openspec/changes/archive/2026-09-24-enable-nix-ld/proposal.md25
-rw-r--r--openspec/changes/archive/2026-09-24-enable-nix-ld/specs/dynamic-linking-support/spec.md23
-rw-r--r--openspec/changes/archive/2026-09-24-enable-nix-ld/tasks.md13
-rw-r--r--openspec/specs/dynamic-linking-support/spec.md25
6 files changed, 125 insertions, 0 deletions
diff --git a/openspec/changes/archive/2026-09-24-enable-nix-ld/.openspec.yaml b/openspec/changes/archive/2026-09-24-enable-nix-ld/.openspec.yaml
new file mode 100644
index 0000000..75289e4
--- /dev/null
+++ b/openspec/changes/archive/2026-09-24-enable-nix-ld/.openspec.yaml
@@ -0,0 +1,2 @@
1schema: spec-driven
2created: 2026-09-24
diff --git a/openspec/changes/archive/2026-09-24-enable-nix-ld/design.md b/openspec/changes/archive/2026-09-24-enable-nix-ld/design.md
new file mode 100644
index 0000000..aa9abff
--- /dev/null
+++ b/openspec/changes/archive/2026-09-24-enable-nix-ld/design.md
@@ -0,0 +1,37 @@
1## Context
2
3See `proposal.md` - Why for motivation. Relevant constraints verified against this flake's pinned nixpkgs (`nixos-unstable`, rev `b1b875982b17dabde9b4a37f3e229e74913e6db3`), `nixos/modules/programs/nix-ld.nix`:
4
5- The module's real option surface is `programs.nix-ld.enable` (bool), `programs.nix-ld.package`, and `programs.nix-ld.libraries` (`listOf package`).
6- `programs.nix-ld.libraries` is a list-typed option, so NixOS's module system merges every module's contribution by concatenation rather than override.
7- Once `enable`d, the module's own `config` block (not its `default`) unconditionally sets `programs.nix-ld.libraries` to a baseline set: `zlib zstd stdenv.cc.cc curl openssl attr libssh bzip2 libxml2 acl libsodium util-linux xz systemd`. This baseline always applies — it is not something a consuming module can accidentally clobber by also setting the option.
8- `modules/nixos/common.nix` currently has no `lib.mkIf`/feature-flag pattern anywhere; every service (SSH, GNOME, Docker, PipeWire, printing) is turned on unconditionally for every host, with `lib.mkDefault` used only for the two settings (`time.timeZone`, `i18n.defaultLocale`) that a per-host `configuration.nix` is expected to override.
9- Only one real host exists today (`hosts/test-vm/`), and neither it nor `hosts/template/` sets any GPU/CUDA-related packages that would need to be folded into a `nix-ld` library list.
10- `programs.nix-ld` has no equivalent in home-manager and is not read by `modules/home/*`; it is purely a NixOS system module concern, so it cannot affect the standalone `homeConfigurations` built via `mkHome`.
11
12## Goals / Non-Goals
13
14**Goals:**
15- `uv`-fetched prebuilt CPython interpreters and native wheel extensions run correctly on every NixOS host built from this flake, today and for hosts added later.
16- The library list is easy to extend later without restructuring anything, since new uv-fetched binaries may need libraries not anticipated now.
17
18**Non-Goals:**
19- A full FHS-compatible environment (`buildFHSEnv`/`buildFHSUserEnv`) — `nix-ld` alone is sufficient for the driving use case (running foreign dynamically-linked binaries), and an FHS wrapper is a much larger, unrelated commitment.
20- GPU/CUDA-specific libraries — no host in this flake currently has a GPU driver or CUDA toolkit configured, so there is nothing host-specific to add yet.
21- A per-host opt-out or feature flag for `nix-ld` — only one real host exists, and `modules/nixos/common.nix` doesn't use feature flags for any of its other unconditional services; a future host that genuinely needs to opt out can still override with `lib.mkForce false` without requiring this design to add a flag preemptively.
22
23## Decisions
24
25**Enable `programs.nix-ld.enable` unconditionally in `modules/nixos/common.nix`, not per-host.**
26Matches this file's existing style — every other service (Docker, GNOME, SSH) is unconditional, with `mkDefault` reserved specifically for settings a host is expected to override (locale/timezone). `nix-ld` has no such per-host variance today. Alternative considered: gate it behind a new `home-manager`-style option so hosts could opt in/out — rejected as unnecessary complexity with no current use case, and inconsistent with how every other service in this file is already handled.
27
28**Extend `programs.nix-ld.libraries` with a curated Python-native-extension list, rather than leaving it at nix-ld's own defaults.**
29nix-ld's built-in baseline (`zlib`, `openssl`, `bzip2`, `libxml2`, `xz`, `stdenv.cc.cc`, etc.) covers a lot, but common CPython build dependencies it does *not* include are `ncurses`, `libffi`, `sqlite`, `readline`, `gdbm`, and `expat` — all commonly required by python-build-standalone interpreters and native extension wheels. Because the option merges additively, `common.nix` only needs to state the delta, not restate nix-ld's own defaults. Alternative considered: leave `programs.nix-ld.libraries` unset and rely solely on nix-ld's own baseline — rejected because it would likely leave real CPython interpreter/extension failures unresolved, defeating the purpose of this change.
30
31**Keep the library list inline in `modules/nixos/common.nix` rather than a separate `lib/nix-ld-libraries.nix` file.**
32The list is short (six packages) and consumed in exactly one place, unlike `lib/unfree-allowlist.nix`, which is imported by both `mkNixos` and `mkHome`. Alternative considered: mirror the `lib/unfree-allowlist.nix` pattern with a dedicated file — rejected as premature indirection for a list this size with a single consumer; can be extracted later if it grows or gains a second consumer.
33
34## Risks / Trade-offs
35
36- **[Risk]** The curated library list is best-effort and may not cover every binary `uv` ever fetches, so some future interpreter or native extension could still fail to load a shared library. → **Mitigation**: `programs.nix-ld.libraries` merges additively (confirmed from the module source), so closing a future gap is a one-line addition to the existing list, not a restructuring.
37- **[Risk]** This change cannot be fully verified from this development environment — confirming a `uv`-managed interpreter or native extension actually runs requires an activated NixOS host (`nixos-rebuild switch`), not just a successful build. → **Mitigation**: verification here is limited to `nix flake check` and building the `test-vm` system closure; end-to-end confirmation is called out as a manual follow-up in `tasks.md` rather than claimed as done.
diff --git a/openspec/changes/archive/2026-09-24-enable-nix-ld/proposal.md b/openspec/changes/archive/2026-09-24-enable-nix-ld/proposal.md
new file mode 100644
index 0000000..2a51e37
--- /dev/null
+++ b/openspec/changes/archive/2026-09-24-enable-nix-ld/proposal.md
@@ -0,0 +1,25 @@
1## Why
2
3`uv` (provisioned in `modules/home/dev-tools.nix`) doesn't just run Python code — `uv python install` fetches prebuilt CPython interpreters (python-build-standalone), and `uv pip install`/`uv tool install` can pull wheels with prebuilt native (C-extension) binaries. These are ordinary dynamically-linked ELF binaries built against a generic glibc loader path (`/lib64/ld-linux-x86-64.so.2`) that doesn't exist on NixOS, so they fail to execute without `nix-ld`. This was explicitly flagged as a known, deferred gap in the change that added `uv` itself (`openspec/changes/archive/2026-09-24-add-uv-and-docker-runtime/proposal.md`: *"no `nix-ld`/extra container-toolchain packages beyond what's already provisioned"*). This change closes that gap.
4
5## What Changes
6
7- Enable `programs.nix-ld.enable = true;` unconditionally on every NixOS host, in `modules/nixos/common.nix`, alongside the other unconditional host-level services (GNOME, SSH, Docker) — matching that file's existing no-feature-flag style.
8- Extend `programs.nix-ld.libraries` with a small curated set of common CPython native-extension dependencies (`ncurses`, `libffi`, `sqlite`, `readline`, `gdbm`, `expat`) that aren't already covered by nix-ld's own built-in baseline list (`zlib`, `zstd`, `stdenv.cc.cc`, `curl`, `openssl`, `attr`, `libssh`, `bzip2`, `libxml2`, `acl`, `libsodium`, `util-linux`, `xz`, `systemd`, confirmed from the pinned nixpkgs module source). `programs.nix-ld.libraries` merges additively across modules, so this only adds to nix-ld's own defaults rather than replacing them.
9- **BREAKING**: none — additive only. Only applies to NixOS hosts (`modules/nixos/common.nix`); the standalone `tom@linux`/`tom@darwin` home-manager profiles (`homeConfigurations`, built via `mkHome`) are unaffected, since `programs.nix-ld` is a NixOS-only module option with no equivalent in home-manager.
10
11## Capabilities
12
13### New Capabilities
14- `dynamic-linking-support`: NixOS hosts built from this flake can run prebuilt, foreign (non-Nix-built) dynamically-linked binaries — such as the interpreters and native wheel extensions `uv` fetches — via `nix-ld`, rather than failing with a missing-interpreter/exec error.
15
16### Modified Capabilities
17- None. `developer-environment` already provisions `uv` itself and makes no claim about dynamic-linking behavior, so its spec is untouched.
18
19## Impact
20
21- **Affected code**: `modules/nixos/common.nix` only (add `programs.nix-ld.enable` and extend `programs.nix-ld.libraries`).
22- **Not affected**: `modules/home/*` (including `dev-tools.nix`, which already provisions `uv`), `homeConfigurations`/`lib/mkHome.nix`, `lib/unfree-allowlist.nix` (the `nix-ld` package and all added libraries are free-licensed, no allowlist entry needed), `hosts/template/` and `hosts/test-vm/` (no per-host override needed — this is a shared baseline, same as GNOME/SSH/Docker).
23- **Dependencies**: nixpkgs' built-in `nix-ld` NixOS module (`nixos/modules/programs/nix-ld.nix`) and its default `nix-ld` package, both already present in the pinned `nixos-unstable` input; no new flake inputs.
24- **Platforms**: NixOS hosts only (`nixosConfigurations`, via `mkNixos`/`modules/nixos/common.nix`). No effect on standalone home-manager-only platforms.
25- **Known limitation, out of scope**: the curated library list is best-effort, not exhaustive — some future uv-fetched binary could still need a library not in the list. Because `programs.nix-ld.libraries` merges additively, closing such a gap later is a one-line addition, not a rework.
diff --git a/openspec/changes/archive/2026-09-24-enable-nix-ld/specs/dynamic-linking-support/spec.md b/openspec/changes/archive/2026-09-24-enable-nix-ld/specs/dynamic-linking-support/spec.md
new file mode 100644
index 0000000..e859aa4
--- /dev/null
+++ b/openspec/changes/archive/2026-09-24-enable-nix-ld/specs/dynamic-linking-support/spec.md
@@ -0,0 +1,23 @@
1## Purpose
2
3Lets NixOS hosts built from this flake run prebuilt, foreign (non-Nix-built) dynamically-linked binaries — such as the interpreters and native wheel extensions that developer tools like `uv` fetch at runtime — instead of failing with a missing-interpreter execution error.
4
5## ADDED Requirements
6
7### Requirement: NixOS hosts run foreign dynamically-linked binaries via nix-ld
8Every NixOS host built from this flake's shared system module SHALL enable `nix-ld`, so that prebuilt binaries linked against a standard (non-NixOS) dynamic loader path execute correctly instead of failing to run.
9
10#### Scenario: A uv-managed prebuilt Python interpreter executes
11- **WHEN** `uv python install` downloads a prebuilt CPython interpreter and that interpreter is invoked
12- **THEN** the interpreter starts successfully instead of failing with a missing-interpreter or "cannot execute binary file" error
13
14#### Scenario: A native wheel extension loads its shared library dependencies
15- **WHEN** a Python package installed by `uv` includes a prebuilt native (C-extension) binary that depends on common shared libraries (e.g. `ncurses`, `libffi`, `sqlite`, `readline`, `gdbm`, `expat`, or another library covered by `nix-ld`'s configured library set)
16- **THEN** the extension loads its dependencies successfully instead of failing with a missing shared library error
17
18### Requirement: The nix-ld library set is host-wide and additive
19The set of libraries made available to foreign binaries via `nix-ld` SHALL be defined once for all NixOS hosts built from this flake's shared system module, and SHALL be extensible by appending further libraries without removing or replacing the existing set.
20
21#### Scenario: A host-level addition does not remove existing libraries
22- **WHEN** a NixOS host's own configuration adds an additional library to the nix-ld library set
23- **THEN** the libraries already provided by the shared system module remain available, unaffected by the addition
diff --git a/openspec/changes/archive/2026-09-24-enable-nix-ld/tasks.md b/openspec/changes/archive/2026-09-24-enable-nix-ld/tasks.md
new file mode 100644
index 0000000..fdd8abe
--- /dev/null
+++ b/openspec/changes/archive/2026-09-24-enable-nix-ld/tasks.md
@@ -0,0 +1,13 @@
1## 1. Enable nix-ld on NixOS hosts
2
3- [x] 1.1 In `modules/nixos/common.nix`, add `programs.nix-ld.enable = true;` alongside the other unconditional host-level services, with a short comment explaining why (uv-managed prebuilt binaries need an FHS-style loader path). Verify by re-reading the diff: the option is set unconditionally, matching the file's existing style.
4- [x] 1.2 In the same file, add `programs.nix-ld.libraries = with pkgs; [ ncurses libffi sqlite readline gdbm expat ];`. Verify the six packages resolve in the pinned nixpkgs by running `nix eval '.#nixosConfigurations.test-vm.config.programs.nix-ld.libraries' --apply 'builtins.length'` (or equivalent) and confirming it evaluates without error.
5
6## 2. Verify the change builds
7
8- [x] 2.1 Run `nix flake check` and confirm it passes.
9- [x] 2.2 Run `nix build '.#nixosConfigurations.test-vm.config.system.build.toplevel'` and confirm the system closure builds successfully with `programs.nix-ld` enabled.
10
11## 3. Manual follow-up (deferred, not verifiable in this environment)
12
13- [ ] 3.1 After deploying to an activated NixOS host, run `uv python install 3.12` and confirm the installed interpreter executes (e.g. `uv run python3.12 --version`) instead of failing with a missing-interpreter error. This cannot be verified from this development environment since it requires an activated host, not just a successful build.
diff --git a/openspec/specs/dynamic-linking-support/spec.md b/openspec/specs/dynamic-linking-support/spec.md
new file mode 100644
index 0000000..5bc7fd9
--- /dev/null
+++ b/openspec/specs/dynamic-linking-support/spec.md
@@ -0,0 +1,25 @@
1# dynamic-linking-support Specification
2
3## Purpose
4
5Lets NixOS hosts built from this flake run prebuilt, foreign (non-Nix-built) dynamically-linked binaries — such as the interpreters and native wheel extensions that developer tools like `uv` fetch at runtime — instead of failing with a missing-interpreter execution error.
6
7## Requirements
8
9### Requirement: NixOS hosts run foreign dynamically-linked binaries via nix-ld
10Every NixOS host built from this flake's shared system module SHALL enable `nix-ld`, so that prebuilt binaries linked against a standard (non-NixOS) dynamic loader path execute correctly instead of failing to run.
11
12#### Scenario: A uv-managed prebuilt Python interpreter executes
13- **WHEN** `uv python install` downloads a prebuilt CPython interpreter and that interpreter is invoked
14- **THEN** the interpreter starts successfully instead of failing with a missing-interpreter or "cannot execute binary file" error
15
16#### Scenario: A native wheel extension loads its shared library dependencies
17- **WHEN** a Python package installed by `uv` includes a prebuilt native (C-extension) binary that depends on common shared libraries (e.g. `ncurses`, `libffi`, `sqlite`, `readline`, `gdbm`, `expat`, or another library covered by `nix-ld`'s configured library set)
18- **THEN** the extension loads its dependencies successfully instead of failing with a missing shared library error
19
20### Requirement: The nix-ld library set is host-wide and additive
21The set of libraries made available to foreign binaries via `nix-ld` SHALL be defined once for all NixOS hosts built from this flake's shared system module, and SHALL be extensible by appending further libraries without removing or replacing the existing set.
22
23#### Scenario: A host-level addition does not remove existing libraries
24- **WHEN** a NixOS host's own configuration adds an additional library to the nix-ld library set
25- **THEN** the libraries already provided by the shared system module remain available, unaffected by the addition