Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
64380bc
feat(flake): add empty private-extension flake input stub
GDR Oct 26, 2026
0026768
feat(lib): add mkFeature compiler and typed per-user module options
GDR Oct 26, 2026
272bd9a
fix(lib): ensure dotfilesConfig lambda invocation and platform gating…
GDR Oct 26, 2026
cd4a651
refactor(claude): migrate to mkFeature
GDR Oct 26, 2026
109358a
refactor(codex): migrate to mkFeature
GDR Oct 26, 2026
bc1c410
refactor(gemini-cli): migrate to mkFeature
GDR Oct 26, 2026
e702f94
refactor(chromium): migrate to mkFeature
GDR Oct 26, 2026
c440e0a
refactor(vivaldi): migrate to mkFeature
GDR Oct 26, 2026
d88bd58
refactor(gcloud): migrate to mkFeature
GDR Oct 26, 2026
82d1832
refactor(htop): migrate to mkFeature
GDR Oct 26, 2026
745ed74
refactor(shell-utils): migrate to mkFeature
GDR Oct 26, 2026
0dc6910
refactor(vault): migrate to mkFeature
GDR Oct 26, 2026
1d6734e
refactor(cursor-theme): migrate to mkFeature
GDR Oct 26, 2026
772e7e4
refactor(awesomewm): migrate to mkFeature
GDR Oct 26, 2026
ecb41ac
refactor(gnome): migrate to mkFeature
GDR Oct 26, 2026
95f0087
refactor(hyprland): migrate to mkFeature
GDR Oct 26, 2026
ad469ea
refactor(audio): migrate to mkFeature and absorb user audio packages
GDR Oct 26, 2026
77bbf4a
refactor(brightness): migrate to mkFeature
GDR Oct 26, 2026
7dfca47
refactor(notifications): migrate to mkFeature
GDR Oct 26, 2026
fc485b4
refactor(nautilus): migrate to mkFeature
GDR Oct 26, 2026
f92bfea
refactor(wayland): migrate to mkFeature and fix hyprland check
GDR Oct 26, 2026
67eaaf5
refactor(launcher): migrate to mkFeature
GDR Oct 26, 2026
16f8a64
refactor(network-applet): migrate to mkFeature
GDR Oct 26, 2026
b50e83a
refactor(omarchy-waybar): migrate to mkFeature
GDR Oct 26, 2026
bf1077c
refactor(waybar): migrate to mkFeature
GDR Oct 26, 2026
8a848ce
refactor(tailscale): extract tailscale-systray into home widget feature
GDR Oct 26, 2026
d96892a
refactor(qbittorrent): migrate to mkFeature
GDR Oct 26, 2026
e6c130a
refactor(antigravity): migrate to mkFeature with per-user options
GDR Oct 26, 2026
29a0bcd
refactor(cursor): migrate to mkFeature
GDR Oct 26, 2026
2c6f75b
refactor(ghidra): migrate to mkFeature
GDR Oct 26, 2026
3106c76
refactor(neovim): migrate to mkFeature
GDR Oct 26, 2026
15f9ab1
refactor(vscode-server): migrate to mkFeature
GDR Oct 26, 2026
c9ba832
refactor(lutris): migrate to mkFeature
GDR Oct 26, 2026
35acbb9
refactor(soundpad): migrate to mkFeature
GDR Oct 26, 2026
0ed367c
refactor(steam): migrate to mkFeature
GDR Oct 26, 2026
ecc840b
refactor(iina): migrate to mkFeature
GDR Oct 26, 2026
0b175c0
refactor(spotify): migrate to mkFeature
GDR Oct 26, 2026
405b1bf
refactor(vlc): migrate to mkFeature
GDR Oct 26, 2026
8adebd0
refactor(telegram): migrate to mkFeature
GDR Oct 26, 2026
777f207
refactor(bitwarden): migrate to mkFeature
GDR Oct 26, 2026
37c5a10
refactor(clavis): migrate to mkFeature with per-user launchd agent
GDR Oct 26, 2026
b086eef
refactor(keepassxc): migrate to mkFeature
GDR Oct 26, 2026
92cbcd0
refactor(ssh): migrate to mkFeature with per-user config
GDR Oct 26, 2026
0c659f3
refactor(git): move git config to home shell feature
GDR Oct 26, 2026
cd7a07f
refactor(tmux): migrate to mkFeature
GDR Oct 26, 2026
eec8ecc
refactor(zsh): migrate to mkFeature
GDR Oct 26, 2026
34f1dd7
refactor(ghostty): migrate to mkFeature
GDR Oct 26, 2026
1cc1179
refactor(macfuse): migrate to mkFeature
GDR Oct 26, 2026
a45a4c8
refactor(obsidian): migrate to mkFeature
GDR Oct 26, 2026
2b8aed1
refactor(raycast): migrate to mkFeature
GDR Oct 26, 2026
5f12447
refactor(docker): migrate to mkFeature
GDR Oct 26, 2026
4ba1395
refactor(virtualbox): migrate to mkFeature
GDR Oct 26, 2026
94e07fe
fix(lib): prevent premature pkgs evaluation in feature compiler
GDR Oct 26, 2026
a6f0715
fix(clavis): safely handle null pkgs at import time
GDR Oct 26, 2026
06ad682
fix(shell): pass osConfig as config to isFeatureEnabledForUser
GDR Oct 26, 2026
cb3e36a
fix(hosts): deduplicate nix-goldstar system.all and mac-brightstar an…
GDR Oct 26, 2026
4eb87bd
refactor(lib): remove legacy mkModuleV2 and mkModule helpers
GDR Oct 26, 2026
9818c95
test(checks): add multi-user isolation and per-user option checks
GDR Oct 26, 2026
a05d633
docs(agents): update module documentation for mkFeature
GDR Oct 26, 2026
f3433e5
fmt: format nix files with nixpkgs-fmt
GDR Oct 26, 2026
17073be
fix(lint): restore vivaldi comments and resolve deadnix/statix warnings
GDR Oct 26, 2026
c9c1f26
fixup! fix(lib): prevent premature pkgs evaluation in feature compiler
GDR Oct 26, 2026
ae2ac00
fixup! refactor(virtualbox): migrate to mkFeature
GDR Oct 26, 2026
c169fd3
fixup! refactor(macfuse): migrate to mkFeature
GDR Oct 26, 2026
21e84e4
fixup! refactor(ghostty): migrate to mkFeature
GDR Oct 26, 2026
f5a1290
fixup! refactor(bitwarden): migrate to mkFeature
GDR Oct 26, 2026
0ad93ce
fixup! refactor(gnome): migrate to mkFeature
GDR Oct 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 24 additions & 16 deletions .agents/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ dot/
│ ├── nix-oldstar/ # NixOS host
│ └── mac-brightstar/ # Darwin host
├── lib/
│ ├── default.nix # Core helpers: mkModuleV2, mkSystemModuleV2, mkDotfilesSymlink, etc.
│ ├── default.nix # Core helpers: mkFeature, mkSystemModuleV2, mkDotfilesSymlink, etc.
│ ├── flakeHelpers.nix # mkDarwinConfiguration, mkNixosConfiguration
│ ├── moduleRegistry.nix # Module discovery/registry
│ └── themes/ # Theme system (rose-pine-moon, etc.)
Expand Down Expand Up @@ -80,13 +80,13 @@ Use `lib.recursiveUpdate` (not `//`) to merge nested attrsets without clobbering

### Module dependency requires

Modules can declare `requires = [ "home.other.module" ]` in `mkModuleV2`. Enabling a module auto-enables its full transitive dependency tree — if A requires B and B requires C, enabling A auto-enables B and C. Inspect the registry: `nix eval '.#moduleRegistry.modules' --json`.
Modules can declare `requires = [ "home.other.module" ]` in `mkFeature`. Enabling a module auto-enables its full transitive dependency tree — if A requires B and B requires C, enabling A auto-enables B and C. Inspect the registry: `nix eval '.#moduleRegistry.modules' --json`.

### Two module types

| Type | Location | Created with | Enabled via | Auto-discovered |
|------|----------|-------------|-------------|-----------------|
| User | `modules/home/` | `mkModuleV2` | `hostUsers.<user>.modules.<path>.enable` | Yes |
| User | `modules/home/` | `mkFeature` | `hostUsers.<user>.modules.<path>.enable` | Yes |
| System | `modules/systems/` | `mkSystemModuleV2` | `modules.system.all.*` / `modules.system.linux.*` / `modules.system.darwin.*` | Yes |

### Hierarchical enables — home modules
Expand All @@ -112,34 +112,42 @@ Individual module enables still work as before (and take precedence for opt-outs

Path follows directory structure: `modules/systems/<platform>/<module>` → `modules.system.<platform>.<module>`

### Platform sections in modules
### Feature structure (mkFeature)

```nix
module = {
allSystems.home.packages = [ ... ]; # Both platforms
nixosSystems.home.packages = [ ... ]; # Linux only
darwinSystems.homebrew.casks = [ ... ]; # macOS only
};
lib.my.mkFeature {
description = "Feature description";
platforms = [ "linux" "darwin" ]; # Supported platforms
requires = [ "home.shell.zsh" ]; # Dependencies auto-enabled per user
extraOptions = { ... }; # Per-user typed extra options

dotfiles = { # Optional dotfiles symlinking
path = "app";
source = "modules/home/.../dotfiles";
};

systemModule = {
nixosSystems = { programs.hyprland.enable = true; };
darwinSystems = { ... };
allSystems = { ... };
homeModule = { config, osConfig, pkgs, lib, featureCfg, userName, ... }: {
# Home Manager configuration scoped exclusively to this user
};
homeModuleLinux = { ... }: { ... }; # Linux-only HM config
homeModuleDarwin = { ... }: { ... }; # Darwin-only HM config
nixosModule = { config, pkgs, lib, featureCfg, selectedUsers, ... }: { ... }; # System-level NixOS activation
darwinModule = { config, pkgs, lib, featureCfg, selectedUsers, ... }: { ... }; # System-level Darwin activation
};
```

Home-manager attrs (`home`, `programs`, `xdg`, `services`, etc.) are automatically routed to all enabled `hostUsers`.
Home-manager configuration is scoped strictly per-user to users who enable the feature.

## Key Helpers (lib/default.nix)

- `lib.my.mkModuleV2 args { module, systemModule?, dotfiles?, imports?, extraOptions?, ... }` — User modules
- `lib.my.mkFeature { description?, platforms?, requires?, extraOptions?, dotfiles?, homeModule?, homeModuleLinux?, homeModuleDarwin?, nixosModule?, darwinModule? }` — User feature descriptors
- `lib.my.mkSystemModuleV2 args { namespace, module, moduleLinux?, moduleDarwin?, ... }` — System modules
- `lib.my.shouldEnableModule { config, modulePath }` — Check hierarchical enable
- `lib.my.isFeatureEnabledForUser { config, userName, modulePath }` — Check per-user feature enable
- `lib.my.getUsersWithModule { config, modulePath }` — Users with module enabled
- `lib.my.getUsersWithModuleNames { config, _modulePath }` — Usernames list
- `lib.my.mkDotfilesSymlink { config, self, path, source, target? }` — Live-editable dotfile symlinks
- `lib.my.mkUsersAttrs usernames fn` — Creates `users.users` attrset
- `lib.my.mkModule system config cfg` — Low-level platform-aware config builder
- `lib.my.getTheme config` — Get active theme (set via `config.theme.name`)

## Live-Editable Dotfiles
Expand Down
109 changes: 52 additions & 57 deletions .agents/skills/nix-module-creation/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,42 @@
---
name: nix-module-creation
description: Creating new NixOS user and system modules using mkModuleV2/mkSystemModuleV2
description: Creating new NixOS user features and system modules using mkFeature/mkSystemModuleV2
---

# Creating NixOS Modules
# Creating NixOS Modules and Features

## User Module (mkModuleV2)
## User Feature (mkFeature)

### Simple module
### Simple feature

```nix
# modules/home/<category>/<name>.nix
{ lib, pkgs, ... }@args:
{ lib, pkgs, ... }:

lib.my.mkModuleV2 args {
description = "Description of the module";
lib.my.mkFeature {
description = "Description of the feature";
platforms = [ "linux" "darwin" ]; # optional, defaults to both

module = {
allSystems.home.packages = [ pkgs.my-tool ];
# Or platform-specific:
# nixosSystems.home.packages = [ pkgs.linux-tool ];
# darwinSystems.homebrew.casks = [ "mac-tool" ];
homeModule = { pkgs, ... }: {
home.packages = [ pkgs.my-tool ];
};
# Or platform-specific:
# homeModuleLinux = { pkgs, ... }: { home.packages = [ pkgs.linux-tool ]; };
# darwinModule = { homebrew.casks = [ "mac-tool" ]; };
}
```

### With dotfiles (live-editable symlinks)

```nix
# modules/home/<category>/<name>/<name>.nix
{ lib, pkgs, ... }@args:
{ lib, pkgs, ... }:

lib.my.mkModuleV2 args {
lib.my.mkFeature {
description = "Tool with live-editable config";

module = {
allSystems.home.packages = [ pkgs.my-tool ];
homeModule = { pkgs, ... }: {
home.packages = [ pkgs.my-tool ];
};

dotfiles = {
Expand All @@ -57,20 +57,18 @@ modules/home/<category>/<name>/
### With system-level config

```nix
{ lib, pkgs, config, ... }@args:
{ lib, pkgs, ... }:

lib.my.mkModuleV2 args {
description = "Module with system-level configuration";
lib.my.mkFeature {
description = "Feature with system-level configuration";

systemModule = {
nixosSystems = {
programs.hyprland.enable = true;
services.greetd.enable = true;
};
nixosModule = { ... }: {
programs.hyprland.enable = true;
services.greetd.enable = true;
};

module = {
nixosSystems.home.packages = [ pkgs.hyprland ];
homeModuleLinux = { pkgs, ... }: {
home.packages = [ pkgs.hyprland ];
};

dotfiles = {
Expand All @@ -83,10 +81,10 @@ lib.my.mkModuleV2 args {
### With custom options

```nix
{ lib, pkgs, ... }@args:
{ lib, pkgs, ... }:

lib.my.mkModuleV2 args {
description = "Module with custom options";
lib.my.mkFeature {
description = "Feature with custom options";

extraOptions = {
showHostname = lib.mkOption {
Expand All @@ -96,49 +94,46 @@ lib.my.mkModuleV2 args {
};
};

# When using extraOptions, module becomes a function receiving cfg
module = cfg: {
allSystems.home.packages = [ pkgs.my-tool ];
# Access option: cfg.showHostname
homeModule = { pkgs, featureCfg, ... }: {
home.packages = [ pkgs.my-tool ];
# Access option: featureCfg.showHostname
};
}
```

### With imports (sub-modules)
### With dependencies (requires)

```nix
{ lib, pkgs, ... }@args:
{ lib, pkgs, ... }:

lib.my.mkModuleV2 args {
description = "Module with sub-module imports";
lib.my.mkFeature {
description = "Feature with dependencies";

imports = [
./dotfiles/general.nix
./dotfiles/plugins/airline.nix
requires = [
"home.shell.zsh"
"home.desktop.services.audio"
];

systemModule = {
allSystems.programs.nixvim.enable = true;
};

module = {
allSystems.home.packages = [ pkgs.ripgrep pkgs.fzf ];
homeModule = { pkgs, ... }: {
home.packages = [ pkgs.my-tool ];
};
}
```

## mkModuleV2 Parameters
## mkFeature Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `description` | string | No | Module description |
| `description` | string | No | Feature description |
| `platforms` | list | No | `["linux" "darwin"]` (default: both) |
| `requires` | list | No | Module path dependencies |
| `module` | attrset or `cfg -> attrset` | No | Platform config with `allSystems`/`nixosSystems`/`darwinSystems` sections |
| `systemModule` | attrset or `cfg -> attrset` | No | System-level config (same sections) |
| `dotfiles` | `{ path, source, target? }` | No | Live-editable symlinks |
| `extraOptions` | attrset | No | Additional NixOS options (merged with `enable`) |
| `imports` | list | No | Sub-module imports |
| `requires` | list | No | Feature path dependencies (auto-enabled per user) |
| `extraOptions` | attrset | No | Additional typed NixOS/HM options (merged per user) |
| `dotfiles` | `{ path, source, target?, live? }` | No | Live-editable symlinks |
| `homeModule` | function or attrset | No | Home Manager config for users enabling the feature |
| `homeModuleLinux` | function or attrset | No | Linux-only Home Manager config |
| `homeModuleDarwin` | function or attrset | No | Darwin-only Home Manager config |
| `nixosModule` | function or attrset | No | System-level NixOS config when any user enables the feature |
| `darwinModule` | function or attrset | No | System-level Darwin config when any user enables the feature |

## System Module (mkSystemModuleV2)

Expand Down Expand Up @@ -167,14 +162,14 @@ lib.my.mkSystemModuleV2 args {
}
```

Enable: `systemLinux.<category>.<name>.enable = true` in host config.
Enable: `modules.system.linux.<category>.<name>.enable = true` in host config.

## Checklist

1. Decide scope: System (`systems/*`) or User (`home/*`)
2. Choose/create category subdirectory
3. Create module file using templates above
4. **System modules only**: Add import to `systems/*/default.nix`
5. **User modules**: Auto-discovered — just add enable path to `hostUsers.<user>.modules`
4. **System modules only**: Auto-discovered or added under `modules/systems/{all,linux,darwin}`
5. **User features**: Auto-discovered — just add enable path to `hostUsers.<user>.modules`
6. `git add` new files before `nix flake check`
7. Test: `nix flake check`
97 changes: 89 additions & 8 deletions checks.nix
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,117 @@

let
user = "dgarifullin";
hasPackage = prefix: packages:
lib.any (package: lib.hasPrefix prefix (package.name or "")) packages;
hasPackage = nameOrPrefix: packages:
lib.any
(package:
let
pname = package.pname or "";
name = package.name or "";
in
pname == nameOrPrefix ||
lib.hasPrefix "${nameOrPrefix}-" name ||
lib.hasPrefix nameOrPrefix name ||
name == nameOrPrefix
)
packages;

neovimConfig = inputs.nvim-nix.checks.${system}.neovim-config;

commonChecks = {
mcp-config-generation =
let
goldstarHome = self.nixosConfigurations.nix-goldstar.config.home-manager.users.${user}.home;
mcpFile = goldstarHome.file.".gemini/antigravity/mcp_config.json" or null;
mcpJson = if mcpFile != null then builtins.fromJSON mcpFile.text else { };
in
assert goldstarHome.file ? ".gemini/antigravity/mcp_config.json";
assert mcpJson ? mcpServers.nixos;
assert mcpJson ? mcpServers.ghidra;
pkgs.runCommand "mcp-config-generation-check" { } "touch $out";

multi-user-isolation =
let
testHost = self.nixosConfigurations.nix-goldstar.extendModules {
modules = [
{
hostUsers.bob = {
enable = true;
modules = {
home.cli.htop.enable = true;
home.games.soundpad.enable = true;
};
};
}
];
};
bobPkgs = testHost.config.home-manager.users.bob.home.packages;
bobFiles = testHost.config.home-manager.users.bob.home.file;
bobGroups = testHost.config.users.users.bob.extraGroups or [ ];
dgarifullinPkgs = testHost.config.home-manager.users.${user}.home.packages;
dgarifullinFiles = testHost.config.home-manager.users.${user}.home.file;
dgarifullinGroups = testHost.config.users.users.${user}.extraGroups or [ ];
in
# bob receives htop
assert hasPackage "htop" bobPkgs;
# bob receives pamixer/pavucontrol (via transitive requires audio)
assert hasPackage "pamixer" bobPkgs;
assert hasPackage "pavucontrol" bobPkgs;
# bob does NOT receive telegram-desktop, google-antigravity-ide, .gemini/antigravity/mcp_config.json, or docker group
assert !(hasPackage "telegram-desktop" bobPkgs);
assert !(hasPackage "google-antigravity" bobPkgs);
assert !(bobFiles ? ".gemini/antigravity/mcp_config.json");
assert !(lib.elem "docker" bobGroups);
# dgarifullin retains all of their packages, configs, and groups
assert hasPackage "htop" dgarifullinPkgs;
assert hasPackage "telegram-desktop" dgarifullinPkgs;
assert (dgarifullinFiles ? ".gemini/antigravity/mcp_config.json");
assert (lib.elem "docker" dgarifullinGroups);
pkgs.runCommand "multi-user-isolation-check" { } "touch $out";

cascade-child-opt-out =
let
testDarwin = self.darwinConfigurations.mac-brightstar.extendModules {
modules = [
{
hostUsers.${user}.modules = {
home.utils.enable = lib.mkForce true;
home.utils.raycast.enable = lib.mkForce false;
};
}
];
};
pkgsList = testDarwin.config.home-manager.users.${user}.home.packages;
in
# obsidian is enabled via category cascade
assert hasPackage "obsidian" pkgsList;
# raycast remains disabled via child opt-out
assert !(hasPackage "raycast" pkgsList);
pkgs.runCommand "cascade-child-opt-out-check" { } "touch $out";

neovim-config = neovimConfig;
};
in
if system == "x86_64-linux" then
let
home = self.nixosConfigurations.nix-goldstar.config.home-manager.users.${user}.home;
in
{
commonChecks // {
module-platform-merge =
assert hasPackage "ripgrep-" home.packages;
assert hasPackage "wl-clipboard-" home.packages;
assert hasPackage "google-antigravity-ide-with-basic-store" home.packages;
assert home.file ? ".gemini/config/AGENTS.md";
pkgs.runCommand "module-platform-merge-check" { } "touch $out";

neovim-config = neovimConfig;
}
else if system == "aarch64-darwin" then
let
home = self.darwinConfigurations.mac-brightstar.config.home-manager.users.${user}.home;
in
{
commonChecks // {
module-platform-merge =
assert hasPackage "ripgrep-" home.packages;
assert !(hasPackage "wl-clipboard-" home.packages);
pkgs.runCommand "module-platform-merge-check" { } "touch $out";

neovim-config = neovimConfig;
}
else
{ }
Loading
Loading