home file

This commit is contained in:
2026-08-02 19:40:54 +09:00
parent 12c9d5241b
commit 605e63acdb
8 changed files with 198 additions and 58 deletions
+53 -33
View File
@@ -65,23 +65,30 @@ Prefer the following placements:
## Unit Discovery and Identity
A directory below `modules/` is a unit if, and only if, it directly contains at
least one reserved file. Directories used only for classification, such as
`modules/applications/` or `modules/profiles/interface/`, are namespaces rather
than units when they have no reserved file of their own.
A directory below `modules/` is a unit if, and only if, it contains at least one
reserved root file or reserved Home Manager fragment. Directories used only for
classification, such as `modules/applications/` or
`modules/profiles/interface/`, are namespaces rather than units when they have
no reserved fragment of their own.
The Registry recognizes exactly these five reserved filenames:
The Registry recognizes exactly these eight reserved paths relative to a unit:
| File | Target and responsibility |
| ------------ | -------------------------------------------------------------------------------- |
| `common.nix` | System-side configuration fragment shared by NixOS and nix-darwin |
| `nixos.nix` | NixOS-only configuration fragment |
| `darwin.nix` | nix-darwin-only configuration fragment |
| `home.nix` | Home Manager configuration fragment |
| `meta.nix` | Registry descriptor for dependencies, external modules, and descriptive metadata |
| File | Target and responsibility |
| ----------------- | -------------------------------------------------------------------------------- |
| `common.nix` | System-side configuration fragment shared by NixOS and nix-darwin |
| `nixos.nix` | NixOS-only system configuration fragment |
| `darwin.nix` | nix-darwin-only system configuration fragment |
| `home.nix` | Home Manager fragment shared by NixOS and nix-darwin |
| `home/common.nix` | Home Manager fragment shared by NixOS and nix-darwin |
| `home/nixos.nix` | Home Manager fragment loaded only on NixOS |
| `home/darwin.nix` | Home Manager fragment loaded only on nix-darwin |
| `meta.nix` | Registry descriptor for dependencies, external modules, and descriptive metadata |
`common.nix` is never applied to Home Manager. OS-independent Home Manager
configuration still belongs in `home.nix`.
Root `common.nix` is never applied to Home Manager. `home.nix` and
`home/common.nix` have identical dispatch semantics; use either or both when a
useful file split exists. The `home/` directory is a reserved fragment directory
of its parent unit when it contains `common.nix`, `nixos.nix`, or `darwin.nix`;
it is not discovered as a child unit in that case.
The Registry derives a unit ID from the path relative to `modules/`, joining
path components with dots. Category names remain plural. It also derives the
@@ -137,6 +144,9 @@ of the following are valid units:
# Home Manager only
modules/applications/ghostty/
├── home.nix
├── home/
│ ├── nixos.nix
│ └── darwin.nix
└── settings.nix
# NixOS only
@@ -164,16 +174,16 @@ modules/systems/nix/
└── common.nix
```
If a unit has no `home.nix`, do not generate or apply a Home Manager module for
it. The same rule applies independently to `common.nix`, `nixos.nix`, and
`darwin.nix`.
Each fragment is optional and registered independently. A unit may therefore
contain only `home/nixos.nix` or `home/darwin.nix`; it does not need a
placeholder `home.nix` or `home/common.nix`.
### Configuration fragments
`common.nix`, `nixos.nix`, `darwin.nix`, and `home.nix` are configuration
fragments to which the Registry adds the enable condition. They return the
configuration for their class directly and must not define top-level `imports`,
`options`, or `config` attributes:
Every reserved path except `meta.nix` is a configuration fragment to which the
Registry adds the enable condition. These fragments return the configuration
for their class directly and must not define top-level `imports`, `options`, or
`config` attributes:
```nix
# modules/services/docker/nixos.nix
@@ -208,9 +218,9 @@ fragment.
### Helper files and directories
Every filename other than the five reserved names is an ordinary helper,
regardless of its extension. The Registry neither discovers nor automatically
imports helper files such as `settings.nix`, `keybindings.nix`, `packages.nix`,
Every path other than the eight reserved paths is an ordinary helper, regardless
of its extension. The Registry neither discovers nor automatically imports
helper files such as `settings.nix`, `keybindings.nix`, `packages.nix`,
`colors.nix`, `rules.nix`, or `helpers.nix`. Import a helper explicitly from the
reserved fragment that uses it:
@@ -253,9 +263,10 @@ modules/applications/niri/
```
Here `parts/` is not a unit because it directly contains no reserved file. A
helper directory that directly contains `home.nix` or another reserved file is
itself discovered as a unit, so never use reserved filenames inside a directory
that is intended to contain helpers only.
helper directory that directly contains a root reserved file is itself
discovered as a unit, so never use reserved filenames inside a directory that
is intended to contain helpers only. The reserved `home/` fragment directory is
the sole exception to ordinary recursive child-unit discovery.
### Registry metadata
@@ -436,8 +447,10 @@ mechanism, and do not design the repository around `import-tree`. Registry logic
has these responsibilities:
1. Recursively visit directories below `modules/`.
2. Check only the five reserved filenames directly within each directory.
3. Register a directory as a unit when at least one reserved file exists there.
2. Check the five reserved root filenames and the three reserved filenames
directly inside the unit's `home/` fragment directory.
3. Register a directory as a unit when at least one reserved fragment exists
there, including a unit that has only a reserved `home/` fragment.
4. Derive the unit ID from the path relative to `modules/`.
5. Record only class fragments that exist.
6. Evaluate `meta.nix` as a descriptor only when it exists.
@@ -446,7 +459,8 @@ has these responsibilities:
9. Enable included units from `meta.includes`.
10. Raise a clear evaluation error for a reference to a missing unit ID.
11. Apply only the fragments appropriate to the current host class.
12. Pass `home.nix` to Home Manager only for hosts that enable Home Manager.
12. Pass `home.nix`, `home/common.nix`, and the matching OS-specific Home
Manager fragment only for hosts that enable Home Manager.
A unit record may conceptually look like this; the implementation need not use
this exact representation:
@@ -461,6 +475,9 @@ this exact representation:
nixos = null;
darwin = null;
home = ./applications/ghostty/home.nix;
homeCommon = null;
homeNixos = ./applications/ghostty/home/nixos.nix;
homeDarwin = ./applications/ghostty/home/darwin.nix;
};
meta = { };
@@ -706,7 +723,10 @@ Derive the system class from the host's `system`:
- A Linux NixOS host receives `common.nix` and `nixos.nix`.
- A nix-darwin host receives `common.nix` and `darwin.nix`.
- A host with integrated Home Manager additionally receives `home.nix`.
- A NixOS host with integrated Home Manager additionally receives `home.nix`,
`home/common.nix`, and `home/nixos.nix`.
- A nix-darwin host with integrated Home Manager additionally receives
`home.nix`, `home/common.nix`, and `home/darwin.nix`.
Home Manager is additive, not a system class mutually exclusive with NixOS or
nix-darwin. Normal machine configurations combine NixOS or nix-darwin with Home
@@ -735,8 +755,8 @@ When implementing or modifying modules:
unit's enable option from a class fragment.
- Do not assume any non-reserved file is discovered or loaded automatically.
- Do not require an `_` prefix for helper or private files.
- Do not create unused `common.nix`, `nixos.nix`, `darwin.nix`, `home.nix`, or
`meta.nix` files.
- Do not create unused reserved fragments, including placeholder files under the
reserved `home/` fragment directory.
- Do not override a path-derived unit ID from `meta.nix`.
- Keep technical application dependencies separate from the applications a
personal environment chooses to combine in a profile.
+8 -2
View File
@@ -74,7 +74,10 @@ let
let
homePath = hostFile spec "home.nix";
homeModules = [
(registry.mkModule { class = "home"; })
(registry.mkModule {
class = "home";
systemClass = "nixos";
})
(registry.mkSelectionModule selected)
{ home.stateVersion = spec.stateVersion; }
]
@@ -96,7 +99,10 @@ let
let
homePath = hostFile spec "home.nix";
homeModules = [
(registry.mkModule { class = "home"; })
(registry.mkModule {
class = "home";
systemClass = "darwin";
})
(registry.mkSelectionModule selected)
{ home.stateVersion = spec.stateVersion; }
]
+77 -15
View File
@@ -4,7 +4,7 @@
modulesRoot,
}:
let
reservedFiles = {
rootFragmentFiles = {
common = "common.nix";
nixos = "nixos.nix";
darwin = "darwin.nix";
@@ -12,6 +12,14 @@ let
meta = "meta.nix";
};
homeFragmentFiles = {
homeCommon = "common.nix";
homeNixos = "nixos.nix";
homeDarwin = "darwin.nix";
};
fragmentFileNames = rootFragmentFiles // lib.mapAttrs (_: name: "home/${name}") homeFragmentFiles;
isFile = kind: kind == "regular" || kind == "symlink";
ensure =
@@ -103,13 +111,17 @@ let
);
makeUnit =
relativePath: entries:
relativePath: entries: homeEntries:
let
directory = pathFor relativePath;
id = lib.concatStringsSep "." relativePath;
fragments = lib.mapAttrs (
rootFragments = lib.mapAttrs (
_class: fileName: if entryIsFile entries fileName then directory + "/${fileName}" else null
) reservedFiles;
) rootFragmentFiles;
homeFragments = lib.mapAttrs (
_class: fileName: if entryIsFile homeEntries fileName then directory + "/home/${fileName}" else null
) homeFragmentFiles;
fragments = rootFragments // homeFragments;
baseUnit = {
inherit
id
@@ -138,11 +150,22 @@ let
let
directory = pathFor relativePath;
entries = builtins.readDir directory;
hasReservedFile = lib.any (fileName: entryIsFile entries fileName) (
builtins.attrValues reservedFiles
homeEntries =
if relativePath != [ ] && (entries.home or null) == "directory" then
builtins.readDir (directory + "/home")
else
{ };
hasRootFragment = lib.any (fileName: entryIsFile entries fileName) (
builtins.attrValues rootFragmentFiles
);
childDirectories = lib.filter (name: entries.${name} == "directory") (builtins.attrNames entries);
current = lib.optional hasReservedFile (makeUnit relativePath entries);
hasHomeFragment = lib.any (fileName: entryIsFile homeEntries fileName) (
builtins.attrValues homeFragmentFiles
);
hasFragment = hasRootFragment || hasHomeFragment;
childDirectories = lib.filter (
name: entries.${name} == "directory" && !(name == "home" && hasHomeFragment)
) (builtins.attrNames entries);
current = lib.optional hasFragment (makeUnit relativePath entries homeEntries);
children = lib.concatMap (name: walk (relativePath ++ [ name ])) childDirectories;
in
current ++ children;
@@ -208,12 +231,44 @@ let
"common"
"darwin"
];
home = [ "home" ];
home = {
nixos = [
"home"
"homeCommon"
"homeNixos"
];
darwin = [
"home"
"homeCommon"
"homeDarwin"
];
};
};
fragmentClassesFor =
{
class,
systemClass,
}:
ensure (builtins.hasAttr class fragmentClasses) "unsupported module class '${class}'" (
if class == "home" then
ensure
(builtins.elem systemClass [
"nixos"
"darwin"
])
"the home module class requires systemClass to be 'nixos' or 'darwin'"
fragmentClasses.home.${systemClass}
else
ensure (
systemClass == null
) "systemClass is only supported for the home module class" fragmentClasses.${class}
);
applyFragment =
{
config,
fragmentName,
fragmentPath,
options,
specialArgs,
@@ -244,8 +299,7 @@ let
);
resultValue = if builtins.isFunction fragment then fragment fragmentArgs else fragment;
result =
ensure (builtins.isAttrs resultValue)
"${unit.id}: ${builtins.baseNameOf fragmentPath} must return an attribute set"
ensure (builtins.isAttrs resultValue) "${unit.id}: ${fragmentName} must return an attribute set"
resultValue;
forbiddenKeys = lib.filter (name: builtins.hasAttr name result) [
"imports"
@@ -254,14 +308,20 @@ let
];
in
ensure (forbiddenKeys == [ ])
"${unit.id}: ${builtins.baseNameOf fragmentPath} is a configuration fragment and cannot define top-level ${lib.concatStringsSep ", " forbiddenKeys}"
"${unit.id}: ${fragmentName} is a configuration fragment and cannot define top-level ${lib.concatStringsSep ", " forbiddenKeys}"
result;
externalImports = class: lib.concatMap (unit: unit.meta.imports.${class}) discoveredUnits;
mkModule =
{ class }:
ensure (builtins.hasAttr class fragmentClasses) "unsupported module class '${class}'" (
{
class,
systemClass ? null,
}:
let
selectedFragmentClasses = fragmentClassesFor { inherit class systemClass; };
in
builtins.seq selectedFragmentClasses (
builtins.seq dependencyValidation (
{
config,
@@ -277,6 +337,7 @@ let
map (
fragmentClass:
let
fragmentName = fragmentFileNames.${fragmentClass};
fragmentPath = unit.fragments.${fragmentClass};
in
if fragmentPath == null then
@@ -285,13 +346,14 @@ let
lib.mkIf (enabled config unit) (applyFragment {
inherit
config
fragmentName
fragmentPath
options
specialArgs
unit
;
})
) fragmentClasses.${class}
) selectedFragmentClasses
)
) discoveredUnits;
in
@@ -0,0 +1,3 @@
{
test.homeValues = [ "home-common" ];
}
@@ -0,0 +1,3 @@
{
test.homeValues = [ "home-darwin" ];
}
@@ -0,0 +1,3 @@
{
test.homeValues = [ "home-nixos" ];
}
@@ -0,0 +1,3 @@
{
test.homeValues = [ "gamma-home-nixos" ];
}
+48 -8
View File
@@ -24,7 +24,7 @@ let
};
eval =
class: selected:
class: systemClass: selected:
lib.evalModules {
specialArgs = {
inherit pkgs;
@@ -32,14 +32,17 @@ let
};
modules = [
baseModule
(registry.mkModule { inherit class; })
(registry.mkModule { inherit class systemClass; })
(registry.mkSelectionModule selected)
];
};
nixos = eval "nixos" [ "profiles.interface.test" ];
darwin = eval "darwin" [ "applications.alpha" ];
home = eval "home" [ "profiles.interface.test" ];
nixos = eval "nixos" null [ "profiles.interface.test" ];
darwin = eval "darwin" null [ "applications.alpha" ];
nixosHome = eval "home" "nixos" [ "profiles.interface.test" ];
darwinHome = eval "home" "darwin" [ "applications.alpha" ];
nixosOnlyHome = eval "home" "nixos" [ "applications.gamma" ];
darwinWithoutNixosHome = eval "home" "darwin" [ "applications.gamma" ];
nixosHost = hostLib.mkNixos "registry-test" {
system = "x86_64-linux";
@@ -60,6 +63,19 @@ let
missingUnit = builtins.tryEval (
builtins.deepSeq (registry.validateUnitIds [ "applications.missing" ]) true
);
homeWithoutSystemClass = builtins.tryEval (registry.mkModule { class = "home"; });
homeWithInvalidSystemClass = builtins.tryEval (
registry.mkModule {
class = "home";
systemClass = "linux";
}
);
systemWithHomeSystemClass = builtins.tryEval (
registry.mkModule {
class = "nixos";
systemClass = "nixos";
}
);
invalidDependencyRegistry = import ../libs/registry.nix {
inherit inputs lib;
@@ -73,7 +89,10 @@ let
};
invalidFragmentEvaluation = lib.evalModules {
modules = [
(invalidFragmentRegistry.mkModule { class = "home"; })
(invalidFragmentRegistry.mkModule {
class = "home";
systemClass = "nixos";
})
(invalidFragmentRegistry.mkSelectionModule [ "applications.broken" ])
];
};
@@ -84,6 +103,7 @@ let
registry.unitIds == [
"applications.alpha"
"applications.beta"
"applications.gamma"
"profiles.interface.test"
"services.nested"
"users.test"
@@ -106,10 +126,20 @@ let
"darwin"
];
assert
home.config.test.homeValues == [
nixosHome.config.test.homeValues == [
"home"
"home-common"
"home-nixos"
"beta-home"
];
assert
darwinHome.config.test.homeValues == [
"home"
"home-common"
"home-darwin"
];
assert nixosOnlyHome.config.test.homeValues == [ "gamma-home-nixos" ];
assert darwinWithoutNixosHome.config.test.homeValues == [ ];
assert nixosHost.config.my.profiles.interface.test.enable;
assert nixosHost.config.system.stateVersion == "26.05";
assert nixosHost.config.home-manager.users.test.home.stateVersion == "26.05";
@@ -117,18 +147,28 @@ let
assert
nixosHost.config.home-manager.users.test.test.homeValues == [
"home"
"home-common"
"home-nixos"
"beta-home"
];
assert darwinHost.config.my.applications.alpha.enable;
assert darwinHost.config.home-manager.users.test.home.stateVersion == "26.05";
assert darwinHost.config.home-manager.users.test.my.applications.alpha.enable;
assert darwinHost.config.home-manager.users.test.test.homeValues == [ "home" ];
assert
darwinHost.config.home-manager.users.test.test.homeValues == [
"home"
"home-common"
"home-darwin"
];
assert
darwinHost.config.test.systemValues == [
"common:host"
"darwin"
];
assert !missingUnit.success;
assert !homeWithoutSystemClass.success;
assert !homeWithInvalidSystemClass.success;
assert !systemWithHomeSystemClass.success;
assert !missingDependency.success;
assert !invalidFragment.success;
true;