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 HEADb2831d33132967b4c4fb69e6068b2b05fff8fdffchanges onlyflake.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.mdarchitecture 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.nixcomposes the desktop;profiles/laptop.nixadds laptop support;profiles/ai-desktop.nixcomposesai-agents.nixandai-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-inmodules/profiles/backup.nix, imported by current desktop and server compositions, not directly bycommon/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.nixfiles are composition roots: they select modules and profiles, set host-specific values, and import local hardware/Disko files. Put Electra'sigpu/dgpuhardware-presence differences in the appropriate specialisation; ADR-001 records why they are one flake target. users/nimmo.nixdefines 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.nixwires Home Manager into each NixOS target and selectshome/users/${config.nixosConfig.primaryUser}. The current primary-user entry point ishome/users/nimmo/default.nix;home/nimmo.nixis 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.nixreturns a launcher package to its caller; it is not a NixOS module simply because it is a.nixfile. 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.nixgathers desktop support (audio, fonts, printing, removable storage, Firefox, utilities). It is not the complete graphical profile;profiles/gui.nixis that composition point.modules/profiles/ai-runtime.nixowns a coupled OpenCode/Paseo runtime, credentials, and Home Manager wiring, whileai-desktop.nixowns the experience-level options and host-specific provider overrides.modules/profiles/ollama.nixkeeps 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.nixprovides 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.