Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 5 additions & 3 deletions .agents/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,10 @@ Path follows directory structure: `modules/systems/<platform>/<module>` → `mod
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
requires = [ "home.shell.zsh" ]; # Hard dependencies auto-enabled per user
recommends = [ ]; # Soft dependencies (overridable by enable = false)
homeOptions = { ... }; # Per-user typed Home Manager options
systemOptions = { ... }; # System-wide typed options

dotfiles = { # Optional dotfiles symlinking
path = "app";
Expand All @@ -140,7 +142,7 @@ Home-manager configuration is scoped strictly per-user to users who enable the f

## Key Helpers (lib/default.nix)

- `lib.my.mkFeature { description?, platforms?, requires?, extraOptions?, dotfiles?, homeModule?, homeModuleLinux?, homeModuleDarwin?, nixosModule?, darwinModule? }` — User feature descriptors
- `lib.my.mkFeature { description?, platforms?, requires?, recommends?, homeOptions?, systemOptions?, 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
Expand Down
12 changes: 6 additions & 6 deletions .agents/skills/dotfiles-management/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,16 @@ lib.my.mkDotfilesSymlink {
- Custom target: set `target` explicitly (e.g., `"~/.tmux"`)
- Requires `DOTFILES_DIR` env variable in host config

## Using in mkModuleV2
## Using in mkFeature

Preferred way — use the `dotfiles` parameter:
Preferred way — use the `dotfiles` parameter in `lib.my.mkFeature`:

```nix
lib.my.mkModuleV2 args {
lib.my.mkFeature {
description = "Ghostty terminal";

module = {
allSystems.home.packages = [ pkgs.ghostty ];
homeModule = { pkgs, ... }: {
programs.ghostty.enable = true;
};

dotfiles = {
Expand Down Expand Up @@ -87,5 +87,5 @@ Without it, `mkDotfilesSymlink` throws an error.

1. Create `dotfiles/` directory next to the module `.nix` file
2. Put config files inside
3. Add `dotfiles` parameter to `mkModuleV2` call
3. Add `dotfiles` parameter to `mkFeature` call
4. No rebuild needed for future config edits — just edit files in `dotfiles/`
12 changes: 6 additions & 6 deletions .agents/skills/host-configuration/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ in
isDefault = true;
}];
ssh = [
{ host = "*"; identityFile = "~/.ssh/<hostname>_id_ed25519"; extraOptions.AddKeysToAgent = "yes"; }
{ host = "*"; identityFile = "~/.ssh/<hostname>_id_ed25519"; extraConfig.AddKeysToAgent = "yes"; }
{ host = "github.com"; user = "git"; identityFile = "~/.ssh/<hostname>_id_ed25519"; }
] ++ userDefaults.ssh.knownHosts;
modules = lib.recursiveUpdate profiles.userModules {
home.desktop.hyprland.enable = true; # WM choice is host-specific
home.editors.antigravity = userDefaults.antigravity // { enable = true; };
};
sudo.nopasswd = true; # remove if not needed
};
Expand All @@ -52,7 +53,6 @@ in
graphics.nvidia = { enable = true; open = true; };
};

modules.home.editors.antigravity = userDefaults.antigravity;
time.timeZone = "Europe/Moscow";
theme.name = "catppuccin-macchiato";
}
Expand All @@ -78,11 +78,12 @@ in
enable = true;
keys = [{ name = "<hostname>"; type = "ed25519"; purpose = [ "git" "ssh" ]; isDefault = true; }];
ssh = [
{ host = "*"; identityFile = "~/.ssh/<hostname>_id_ed25519"; extraOptions.AddKeysToAgent = "yes"; }
{ host = "*"; identityFile = "~/.ssh/<hostname>_id_ed25519"; extraConfig.AddKeysToAgent = "yes"; }
{ host = "github.com"; user = "git"; identityFile = "~/.ssh/<hostname>_id_ed25519"; }
] ++ userDefaults.ssh.knownHosts;
modules = lib.recursiveUpdate profiles.userModules {
home.ai-tools.enable = true;
home.editors.antigravity = userDefaults.antigravity // { enable = true; };
};
};

Expand All @@ -95,7 +96,6 @@ in
openssh = { enable = true; userMap = { "dgarifullin" = "gdr"; }; };
};

modules.home.editors.antigravity = userDefaults.antigravity;
time.timeZone = "Europe/Moscow";
theme.name = "rose-pine-moon";
}
Expand Down Expand Up @@ -145,9 +145,9 @@ Generate: `nixos-generate-config --show-hardware-config`
2. Create `hosts/machines/<hostname>/hardware-configuration.nix`
3. Add to `flake.nix`:
```nix
nixosConfigurations.<hostname> = flakeHelpers.mkNixosConfiguration ./hosts/machines/<hostname>;
nixosConfigurations.<hostname> = flakeHelpers.mkNixosConfiguration "<hostname>" ./hosts/machines/<hostname>;
# or for Darwin:
darwinConfigurations.<hostname> = flakeHelpers.mkDarwinConfiguration ./hosts/machines/<hostname>;
darwinConfigurations.<hostname> = flakeHelpers.mkDarwinConfiguration "<hostname>" ./hosts/machines/<hostname>;
```
4. Pick profiles — `developer` for any machine, `desktop` for GUI, `server` for headless
5. Add `justfile` target (optional)
Expand Down
42 changes: 26 additions & 16 deletions .agents/skills/nix-module-creation/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,33 +78,45 @@ lib.my.mkFeature {
}
```

### With custom options
### With custom options (`homeOptions` / `systemOptions`)

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

lib.my.mkFeature {
description = "Feature with custom options";
description = "Feature with custom per-user and system options";

extraOptions = {
homeOptions = {
showHostname = lib.mkOption {
default = true;
type = lib.types.bool;
description = "Show hostname in prompt";
};
};

systemOptions = {
openFirewall = lib.mkOption {
default = false;
type = lib.types.bool;
description = "Open firewall ports at system level";
};
};

homeModule = { pkgs, featureCfg, ... }: {
home.packages = [ pkgs.my-tool ];
# Access option: featureCfg.showHostname
# Access per-user option: featureCfg.showHostname
};

nixosModule = { featureCfg, ... }: {
# Access system option: featureCfg.openFirewall
};
}
```

### With dependencies (requires)
### With dependencies (requires / recommends)

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

lib.my.mkFeature {
description = "Feature with dependencies";
Expand All @@ -113,6 +125,9 @@ lib.my.mkFeature {
"home.shell.zsh"
"home.desktop.services.audio"
];
recommends = [
"home.cli.htop"
];

homeModule = { pkgs, ... }: {
home.packages = [ pkgs.my-tool ];
Expand All @@ -126,8 +141,10 @@ lib.my.mkFeature {
|-----------|------|----------|-------------|
| `description` | string | No | Feature description |
| `platforms` | list | No | `["linux" "darwin"]` (default: both) |
| `requires` | list | No | Feature path dependencies (auto-enabled per user) |
| `extraOptions` | attrset | No | Additional typed NixOS/HM options (merged per user) |
| `requires` | list | No | Hard feature path dependencies (cannot be disabled while parent is enabled) |
| `recommends` | list | No | Soft feature path dependencies (overridable by `enable = false`) |
| `homeOptions` | attrset | No | Per-user typed Home Manager options under `hostUsers.<user>.modules` |
| `systemOptions` | attrset | No | System-wide typed options under `modules.home` |
| `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 |
Expand All @@ -152,13 +169,6 @@ lib.my.mkSystemModuleV2 args {
# For namespace = "all", optional platform-specific additions:
# moduleLinux = cfg: { ... };
# moduleDarwin = cfg: { ... };

extraOptions = {
open = lib.mkOption {
default = false;
type = lib.types.bool;
};
};
}
```

Expand Down
34 changes: 17 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ graph TD
end

subgraph Modules["Module System"]
HM["User Modules (mkModuleV2)<br/>modules/home/*"]
HM["User Features (mkFeature)<br/>modules/home/*"]
SM["System Modules (mkSystemModuleV2)<br/>modules/systems/{all,linux,darwin}/*"]
P --> HM
P --> SM
Expand Down Expand Up @@ -247,9 +247,9 @@ nixos-generate-config --show-hardware-config > hosts/machines/my-new-host/hardwa
4. **Add to `flake.nix`:**

```nix
nixosConfigurations.my-new-host = mkNixosConfiguration ./hosts/machines/my-new-host;
nixosConfigurations.my-new-host = flakeHelpers.mkNixosConfiguration "my-new-host" ./hosts/machines/my-new-host;
# or for Darwin:
darwinConfigurations.my-new-host = mkDarwinConfiguration ./hosts/machines/my-new-host;
darwinConfigurations.my-new-host = flakeHelpers.mkDarwinConfiguration "my-new-host" ./hosts/machines/my-new-host;
```

---
Expand All @@ -266,7 +266,7 @@ darwinConfigurations.my-new-host = mkDarwinConfiguration ./hosts/machines/my-new
fullName = lib.mkDefault "New User";
email = lib.mkDefault "newuser@example.com";
github = lib.mkDefault "newuser";
extraGroups = lib.mkDefault [ "wheel" "audio" "video" ];
extraGroups = lib.mkDefault [ "audio" "video" ];
}
```

Expand All @@ -284,7 +284,7 @@ hostUsers.newuser = importUser "newuser" // {
}];
# Hierarchical module enables
modules = {
home.core.enable = true;
home.cli.enable = true;
home.shell.enable = true;
home.media.vlc.enable = true; # specific module
};
Expand Down Expand Up @@ -324,24 +324,24 @@ Module paths follow the directory structure: `home.<category>.<module-name>`

### Create a New Module

#### User Module
#### User Feature (`mkFeature`)

```nix
# modules/home/tools/my-tool.nix
{ lib, pkgs, ... }@args:
{ lib, ... }:

lib.my.mkModuleV2 args {
lib.my.mkFeature {
description = "My awesome tool";
platforms = [ "linux" "darwin" ]; # optional, defaults to both

module = {
# Cross-platform config (goes to home-manager.users.*)
allSystems.home.packages = [ pkgs.my-tool ];

# Or platform-specific
nixosSystems.programs.my-tool.enable = true;
darwinSystems.homebrew.casks = [ "my-tool" ];
homeModule = { pkgs, ... }: {
# Per-user Home Manager config (scoped strictly to users enabling the feature)
home.packages = [ pkgs.my-tool ];
};

# Optional platform-specific Home Manager or system activation:
# homeModuleLinux = { pkgs, ... }: { programs.my-tool.enable = true; };
# darwinModule = { homebrew.casks = [ "my-tool" ]; };
}
```

Expand Down Expand Up @@ -444,15 +444,15 @@ Enable modules hierarchically per-user:
```nix
hostUsers.myuser.modules = {
home.browsers.enable = true; # enables all browsers
home.core.enable = true; # enables htop, shell-utils
home.cli.enable = true; # enables htop, shell-utils
home.shell.enable = true; # enables zsh, tmux
home.editors.neovim.enable = true; # specific module
};
```

| Path | Modules |
|------|---------|
| `home.core` | htop, shell-utils (bat, fzf, wget, direnv) |
| `home.cli` | htop, shell-utils (bat, fzf, wget, direnv) |
| `home.shell` | zsh with oh-my-zsh, zplug, tmux |
| `home.terminal` | ghostty |
| `home.browsers` | chromium, vivaldi |
Expand Down
39 changes: 37 additions & 2 deletions checks.nix
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,53 @@ let
# 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
# bob does NOT receive telegram-desktop, google-antigravity-ide, .gemini/antigravity/mcp_config.json, docker, wheel, or trusted-users
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 !(lib.elem "wheel" bobGroups);
assert !(lib.elem "bob" testHost.config.nix.settings.trusted-users);
# dgarifullin retains all of their packages, configs, groups, and trusted-users
assert hasPackage "htop" dgarifullinPkgs;
assert hasPackage "telegram-desktop" dgarifullinPkgs;
assert (dgarifullinFiles ? ".gemini/antigravity/mcp_config.json");
assert (lib.elem "docker" dgarifullinGroups);
assert (lib.elem "wheel" dgarifullinGroups);
assert (lib.elem user testHost.config.nix.settings.trusted-users);
pkgs.runCommand "multi-user-isolation-check" { } "touch $out";

scoped-options-and-requires =
let
testHost = self.nixosConfigurations.nix-goldstar.extendModules {
modules = [
{
modules.home.games.steam.remotePlayOpenFirewall = lib.mkForce false;
hostUsers.${user}.modules.home.editors.antigravity.rules = lib.mkForce "Custom per-user rules";
}
];
};
rulesText = testHost.config.home-manager.users.${user}.home.file.".gemini/config/AGENTS.md".text;
conflictHost = self.nixosConfigurations.nix-goldstar.extendModules {
modules = [
{
hostUsers.bob = {
enable = true;
modules = {
home.games.soundpad.enable = true;
home.desktop.services.audio.enable = false;
};
};
}
];
};
conflictEval = builtins.tryEval (builtins.deepSeq conflictHost.config.home-manager.users.bob.home.packages true);
in
assert testHost.config.programs.steam.remotePlay.openFirewall == false;
assert rulesText == "Custom per-user rules\n" || rulesText == "Custom per-user rules";
assert !conflictEval.success;
pkgs.runCommand "scoped-options-and-requires-check" { } "touch $out";

cascade-child-opt-out =
let
testDarwin = self.darwinConfigurations.mac-brightstar.extendModules {
Expand Down
6 changes: 3 additions & 3 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,9 @@
);

# Host configurations
darwinConfigurations.mac-brightstar = flakeHelpers.mkDarwinConfiguration ./hosts/machines/mac-brightstar;
nixosConfigurations.nix-oldstar = flakeHelpers.mkNixosConfiguration ./hosts/machines/nix-oldstar;
nixosConfigurations.nix-goldstar = flakeHelpers.mkNixosConfiguration ./hosts/machines/nix-goldstar;
darwinConfigurations.mac-brightstar = flakeHelpers.mkDarwinConfiguration "mac-brightstar" ./hosts/machines/mac-brightstar;
nixosConfigurations.nix-oldstar = flakeHelpers.mkNixosConfiguration "nix-oldstar" ./hosts/machines/nix-oldstar;
nixosConfigurations.nix-goldstar = flakeHelpers.mkNixosConfiguration "nix-goldstar" ./hosts/machines/nix-goldstar;

templates = {
python = {
Expand Down
3 changes: 1 addition & 2 deletions hosts/machines/nix-goldstar/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ in
};
home.editors.neovim.enable = true;
home.editors.ghidra.enable = true;
home.editors.antigravity = {
home.editors.antigravity = userDefaults.antigravity // {
enable = true;
# Global MCP servers — written to ~/.gemini/antigravity/mcp_config.json
# Applies to ALL workspaces opened in Antigravity IDE
Expand All @@ -67,7 +67,6 @@ in
};
context7 = {
url = "https://mcp.context7.com/mcp";
headers.CONTEXT7_API_KEY = builtins.getEnv "CONTEXT7_API_KEY";
};
ghidra = {
command = "bridge-mcp-ghidra";
Expand Down
1 change: 1 addition & 0 deletions hosts/users/dgarifullin.nix
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
email = "gosugdr@gmail.com";
github = "gdr";
extraGroups = [ "wheel" "audio" "libvirtd" "input" ];
trustedNixUser = true;
uid = 1000;
};

Expand Down
Loading
Loading