3 42 Configuration Ownership Boundaries
Nimmo edited this page 2026-09-13 08:54:02 +01:00

Configuration Ownership Boundaries

  • Type: Policy
  • Status: Current
  • Scope: Choosing where NixOS, Home Manager, host, service, and package changes belong in nixos-config
  • Canonical sources: README.md (How It All Fits Together; Architecture Rules and Boundary Decisions); AGENTS.md; flake.nix; modules/common/base.nix; modules/profiles/backup.nix; modules/profiles/gui.nix; modules/profiles/laptop.nix; modules/profiles/ai-desktop.nix; modules/profiles/ai-runtime.nix; modules/profiles/ollama.nix; modules/desktop/environment.nix; modules/services/paseo-daemon.nix; users/nimmo.nix; home/users/nimmo/default.nix
  • Last verified: Source commit 556df88494686003b1c4f20c8e0b99b5afc16a6e (2026-09-12); source HEAD b2831d33132967b4c4fb69e6068b2b05fff8fdff changes only flake.lock. Boundary examples checked 2026-09-13.
  • Review triggers: Changes to module/profile composition, host imports or specialisations, account privileges, Home Manager wiring, helper/package promotion, or README.md architecture rules

Policy and rationale

Place a change at the narrowest boundary that owns its behavior and can be understood independently. A source file's directory is a clue, not an automatic deployment scope: only an explicit import or flake-builder module selection brings it into a host. See Repository Structure and Import Chain to trace that graph.

This separation keeps hardware facts in host files, reusable capabilities in modules, selected bundles in profiles, account privileges in NixOS user modules, and personal tools/settings in Home Manager. It prevents a change for one workload from silently broadening to the whole fleet. It also avoids turning every helper into a new NixOS option or every composition file into a large general-purpose module.

Choose the owner

Change Normal owner Boundary check
A genuinely universal host default or option provider modules/common/ Would every registered host, including stable VMs, safely inherit it?
One coherent system capability or policy modules/<domain>/ (for example services/, networking/, hardware/) Can the behavior be described and tested without one host's hardware facts?
A bundle that selects several capabilities for an environment or workload modules/profiles/ Is it composition, rather than unrelated implementations hidden in one file?
Hardware facts, local mounts, service placement, provider URLs, or values for one machine hosts/<host>/ Is the decision genuinely local, even if a reusable module implements it?
Electra behavior only when the removable NVIDIA expansion bay is present or absent hosts/electra/default.nix specialisation or its host-local hardware module Does this reflect physical hardware presence, not a workload preference?
Login account, group membership, authorized keys, or system privilege users/ (imported by the appropriate host/profile) Does it affect the NixOS account/security boundary?
Per-user shell, Git, browser, desktop settings, or user packages home/users/<user>/ Can it remain user-scoped without system service or hardware policy?
Pure derivation, launcher, or generated wrapper packages/ or a focused helper expression Does it really need a public NixOS option or independent module lifecycle?
Reproducible package/module with its own upstream pin and release cycle Independent wrapper flake when justified Has ADR-002 ruled out a simpler upstream/nixpkgs module or OCI boundary?

When scope spans layers, keep the implementation in its natural owner and compose it at the host or profile boundary. For example, a service module can declare a state directory and backup contribution while its consuming host chooses the mount, listen address, and whether the service is enabled. Do not copy the same implementation into several host files solely to avoid a module.

Required controls

Modules and profiles

  • A reusable module owns a coherent capability, its options, and the system behavior behind those options. It may import lower-level modules. Do not make a host-specific device path or address an invisible assumption of a supposedly reusable module.
  • A profile composes capabilities for a usable environment or workload. A profile may also contain closely coupled settings, but it is not a dumping ground for unrelated services. profiles/gui.nix composes the desktop; profiles/laptop.nix adds laptop support; profiles/ai-desktop.nix composes ai-agents.nix and ai-runtime.nix.
  • Before adding something to modules/common/base.nix, check whether it is truly universal. That file supplies shared defaults and monitoring to every current host. Backrest is intentionally reached via the opt-in modules/profiles/backup.nix, imported by current desktop and server compositions, not directly by common/base.nix.
  • An imported module may still expose an enable option whose value decides whether the capability runs. Conversely, adding an unused module file changes no host. Trace the actual import and option path before claiming fleet-wide or live behavior.

Hosts, accounts, and Home Manager

  • Host default.nix files are composition roots: they select modules and profiles, set host-specific values, and import local hardware/Disko files. Put Electra's igpu/dgpu hardware-presence differences in the appropriate specialisation; ADR-001 records why they are one flake target.
  • users/nimmo.nix defines a NixOS login account, groups, and SSH keys. That is not the place for personal shell/browser configuration. Adding a group or privilege deserves security review even if the edit is one line.
  • flake.nix wires Home Manager into each NixOS target and selects home/users/${config.nixosConfig.primaryUser}. The current primary-user entry point is home/users/nimmo/default.nix; home/nimmo.nix is only a compatibility shim. Home Manager owns the user environment, not host hardware, system service placement, or account privileges.
  • A NixOS module may deliberately contribute to home-manager.users.${config.nixosConfig.primaryUser} when system and per-user behavior form one coupled feature. Keep such wiring visible in the owning module rather than treating all Home Manager configuration as independent of NixOS.

Helpers, packages, and secrets

  • A function such as modules/common/pangolin-mcp-launcher.nix returns a launcher package to its caller; it is not a NixOS module simply because it is a .nix file. Keep package expressions and generated wrappers as implementation details until there is a real reusable option or consumer.
  • A module or host declares the runtime use of service-scoped SOPS files. Secret values do not belong in package derivations, Home Manager store text, or a host's plain Nix expression. Follow Secret Editing and Recipient Rotation for recipient and credential handling.

Intentional mixed-boundary cases

  • modules/desktop/environment.nix gathers desktop support (audio, fonts, printing, removable storage, Firefox, utilities). It is not the complete graphical profile; profiles/gui.nix is that composition point.
  • modules/profiles/ai-runtime.nix owns a coupled OpenCode/Paseo runtime, credentials, and Home Manager wiring, while ai-desktop.nix owns the experience-level options and host-specific provider overrides.
  • modules/profiles/ollama.nix keeps its GPU-specific service loadout with the workload profile. Splitting it merely to enforce one file per layer would separate tightly coupled behavior without improving reuse.
  • modules/services/paseo-daemon.nix provides the shared headless daemon contract; Vega and Lyra retain host-specific container, storage, and monitoring choices. A shared service module does not erase host ownership.

These are established boundaries, not templates to copy indiscriminately. Prefer a small extraction only when it creates a capability that is independently understandable, reusable, or testable. Do not introduce a new repository-wide module framework as a side effect of routine cleanup.

Review and enforcement

For each change, identify the selected hosts and users, the module/profile that owns implementation, and the host values or privileges it changes. Review the import path against flake.nix and the affected host entries, then apply the validation and deployment gates in AGENTS.md. New .nix files must be staged before flake evaluation. Cross-cutting, service, security, or multi-host changes normally use a Forgejo issue, branch, and merge request; a bounded single-app edit may follow the direct-commit rule after targeted validation.

An exception should name the concrete coupling or reuse reason in the code review. Do not move files just for taxonomy, broaden common/base.nix by default, or mistake an evaluated configuration for evidence of a live service. For a service-placement choice, see ADR-002 and Service Migration and Cutover. For the package and root-input procedure, use Adding Packages and Flake Inputs. For new capability and composition files, use Adding a Module or Profile.