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