Claude Code Plugins

Community-maintained marketplace

Feedback

Expert guidance on Nix, NixOS, and home-manager best practices. USE WHEN working with Nix expressions, NixOS configuration, home-manager, flakes, or Nix package development.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name Nix
description Expert guidance on Nix, NixOS, and home-manager best practices. USE WHEN working with Nix expressions, NixOS configuration, home-manager, flakes, or Nix package development.

Nix Best Practices

Purpose

Expert guidance on Nix, NixOS, and home-manager following best practices.

Context Detection

This skill activates when:

  • Current directory contains flake.nix, default.nix, shell.nix, or configuration.nix
  • Git repository contains Nix configuration files
  • User is working with .nix files
  • User explicitly mentions NixOS, home-manager, flakes, or Nix packages
  • Commands like nix build, nixos-rebuild, or home-manager are mentioned

Workflow Routing

When the user's request matches specific Nix operations, route to the appropriate workflow:

Workflow Trigger File
Build "build nix package", "nixos-rebuild build", "compile nix" workflows/Build.md
Debug "debug nix", "nix error", "troubleshoot build", "evaluation error" workflows/Debug.md
Develop "development shell", "nix develop", "devShell", "direnv" workflows/Develop.md
Deploy "deploy nixos", "nixos-rebuild switch", "remote deployment" workflows/Deploy.md
Package "create package", "nix derivation", "buildGoModule", "package app" workflows/Package.md
Flakes "create flake", "flake.lock", "update inputs", "flake outputs" workflows/Flakes.md
Secrets "manage secrets", "agenix", "encrypt secrets", "age encryption" workflows/Secrets.md
Security "harden nixos", "apparmor", "firewall", "security hardening" workflows/Security.md
Troubleshoot "hash mismatch", "nix failing", "common errors", "fix nix issue" workflows/Troubleshoot.md

When to use workflows:

  • Route when the user explicitly asks about one of these operations
  • Workflows provide comprehensive, focused guidance for specific Nix tasks
  • For general Nix guidance or module configuration, continue with this main skill

Core Principles

1. Declarative Configuration Over Imperative

# Good: Declarative
services.nginx.enable = true;

# Bad: Imperative
systemd.services.nginx.postStart = "systemctl start nginx";

2. Reproducibility

Same inputs = Same outputs

  • Pin versions explicitly
  • Use flake.lock for consistency
  • Avoid impure operations

3. Modularity

Break configurations into focused, reusable modules

# Good: Modular
imports = [
  ./hardware.nix
  ./networking.nix
  ./services.nix
];

# Bad: Everything in one file

4. Version Control Everything

  • Track all Nix configurations in git
  • Commit flake.lock changes
  • Document why changes were made

5. Use Flakes for Modern Nix

Flakes provide:

  • Hermetic evaluation
  • Standardized structure
  • Dependency locking
  • Better caching

NixOS Configuration Patterns

Host Configuration Structure

# systems/<hostname>/
├── boot.nix       # Bootloader, initrd, kernel modules
├── hardware.nix   # Hardware settings, filesystems, mounts
├── extra.nix      # Optional: additional host-specific config
└── home.nix       # Optional: host-specific home-manager config

Using mkHost Pattern

# In flake.nix
nixosConfigurations = {
  hostname = libx.mkHost {
    hostname = "hostname";
    system = "x86_64-linux";
    hardwareType = "desktop";  # or "rpi4"
    desktop = "sway";          # or "niri", or null
    nixpkgs = nixpkgs;         # or nixpkgs-25_05 for stable
  };
};

Common Module Organization

systems/common/
├── base/          # Essential base configuration
├── desktop/       # Desktop environment configs
├── hardware/      # Hardware-specific modules
├── programs/      # Application configurations
├── services/      # System services
└── users/         # User account definitions

Checking globals.nix

Always check globals.nix for:

  • Machine definitions (IPs, SSH keys)
  • DNS zone configurations
  • VPN settings
  • Syncthing device IDs
  • Network topology

Module Best Practices

Define Options Properly

{ config, lib, pkgs, ... }:

{
  options = {
    services.myservice = {
      enable = lib.mkEnableOption "my service";

      port = lib.mkOption {
        type = lib.types.port;
        default = 8080;
        description = "Port to listen on";
      };

      configFile = lib.mkOption {
        type = lib.types.path;
        description = "Path to configuration file";
      };
    };
  };

  config = lib.mkIf config.services.myservice.enable {
    # Implementation
  };
}

Use Types Correctly

Common types:

  • types.bool - Boolean values
  • types.int - Integers
  • types.str - Strings
  • types.path - File system paths
  • types.port - Network ports (1-65535)
  • types.listOf types.str - Lists
  • types.attrs - Attribute sets
  • types.package - Nix packages

Leverage mkIf, mkMerge, mkDefault

# Conditional configuration
config = lib.mkIf config.services.myservice.enable {
  # ...
};

# Merge multiple configurations
config = lib.mkMerge [
  { always.present = true; }
  (lib.mkIf condition { conditional.value = true; })
];

# Provide defaults that can be overridden
services.myservice.port = lib.mkDefault 8080;

Package Development

Use callPackage Pattern

# In pkgs/default.nix
{
  mypackage = pkgs.callPackage ./mypackage { };
  mytool = pkgs.callPackage ./mytool { };
}

Package Definition

# pkgs/mypackage/default.nix
{ lib
, stdenv
, fetchFromGitHub
, buildGoModule  # or rustPlatform, python3Packages, etc.
}:

buildGoModule rec {
  pname = "mypackage";
  version = "1.0.0";

  src = fetchFromGitHub {
    owner = "owner";
    repo = "repo";
    rev = "v${version}";
    hash = "sha256-...";
  };

  vendorHash = "sha256-...";

  meta = with lib; {
    description = "Package description";
    homepage = "https://example.com";
    license = licenses.mit;
    maintainers = with maintainers; [ ];
    platforms = platforms.linux;
  };
}

Using Overlays

# overlays/default.nix
{ inputs }:
{
  additions = final: _prev: import ../pkgs { pkgs = final; };

  modifications = final: prev: {
    # Override existing packages
    somepackage = prev.somepackage.overrideAttrs (old: {
      version = "custom";
    });
  };
}

Flake Management

Essential Commands

# Lock dependencies
nix flake lock

# Update all inputs
nix flake update

# Update specific input
nix flake update nixpkgs

# Check flake validity
nix flake check

# Show flake outputs
nix flake show

# Show flake metadata
nix flake metadata

Flake Structure

{
  description = "Flake description";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
  };

  outputs = { self, nixpkgs }: {
    nixosConfigurations = { ... };
    homeConfigurations = { ... };
    packages = { ... };
    devShells = { ... };
  };
}

Home-Manager Patterns

Environment Variables

home.sessionVariables = {
  EDITOR = "vim";
  VISUAL = "vim";
  BROWSER = "firefox";
};

XDG Config Files

# Symlink static files
xdg.configFile."myapp/config.yml".source = ./myapp-config.yml;

# Generate files dynamically
xdg.configFile."myapp/generated.conf".text = ''
  setting1 = ${someValue}
  setting2 = value2
'';

# Make executable
xdg.configFile."bin/script.sh" = {
  source = ./script.sh;
  executable = true;
};

Services

# User-level systemd service
systemd.user.services.myservice = {
  Unit = {
    Description = "My Service";
    After = [ "network.target" ];
  };

  Service = {
    ExecStart = "${pkgs.mypackage}/bin/myservice";
    Restart = "on-failure";
  };

  Install = {
    WantedBy = [ "default.target" ];
  };
};

Secrets Management with agenix

Define Secrets

# secrets.nix
let
  user = "ssh-ed25519 AAAAC3...";
  system = "ssh-ed25519 AAAAC3...";
in {
  "secret.age".publicKeys = [ user system ];
}

Use Secrets in Configuration

{
  age.secrets.mySecret = {
    file = ../secrets/mySecret.age;
    owner = "myuser";
    group = "mygroup";
  };

  # Reference in config
  services.myservice.passwordFile = config.age.secrets.mySecret.path;
}

Encrypt Secrets

# Encrypt a secret
agenix -e secrets/mySecret.age

# Re-key all secrets
agenix -r

Safety and Testing

Build Without Switching

# Build configuration
nixos-rebuild build --flake .#<hostname>

# Dry run (show what would change)
nixos-rebuild dry-build --flake .#<hostname>

# Test without adding to bootloader
nixos-rebuild test --flake .#<hostname>

Rollback Strategy

# List generations
nixos-rebuild list-generations

# Rollback to previous generation
nixos-rebuild switch --rollback

# Switch to specific generation
nixos-rebuild switch --switch-generation <number>

Keep Old Generations

  • Never delete all old generations
  • Keep at least 2-3 recent generations for rollback
  • Clean periodically with: nix-collect-garbage -d

Common Patterns

Conditional Imports

imports = [
  ./base.nix
] ++ lib.optionals (desktop != null) [
  ./desktop/${desktop}
];

String Interpolation

# Simple
message = "Hello ${name}";

# Multi-line
config = ''
  setting1 = ${value1}
  setting2 = ${value2}
'';

# Escape $
script = ''
  echo "Nix variable: ${nixVar}"
  echo "Shell variable: ''${shellVar}"
'';

List Operations

# Concatenation
all = list1 ++ list2;

# Filter
filtered = lib.filter (x: x > 5) list;

# Map
doubled = map (x: x * 2) list;

Attribute Set Operations

# Merge
merged = set1 // set2;

# Recursive merge
merged = lib.recursiveUpdate set1 set2;

# Filter attributes
filtered = lib.filterAttrs (n: v: v != null) attrs;

# Map attributes
mapped = lib.mapAttrs (n: v: v * 2) attrs;

Debugging

Print Values

# Use lib.traceVal for debugging
value = lib.traceVal someExpression;

# Trace with message
value = lib.traceValSeq "message" someExpression;

Evaluate Expressions

# Evaluate Nix expression
nix eval .#nixosConfigurations.hostname.config.services.nginx.enable

# Show derivation
nix show-derivation .#package

# Inspect store path
nix path-info .#package

Common Issues

Hash Mismatch

# Update hash for fetchFromGitHub
nix-prefetch-github owner repo --rev <commit-hash>

# Update vendor hash for Go modules
# Set vendorHash = lib.fakeSha256;
# Build will fail with correct hash

Import Cycles

  • Check for circular imports
  • Use lib.mkIf to break cycles
  • Restructure module organization

Performance

Build Optimization

  • Use binary caches
  • Avoid rebuilding unnecessarily
  • Keep flake.lock updated but stable
  • Use nix-direnv for development shells

Evaluation Speed

  • Minimize use of import
  • Use builtins wisely
  • Avoid expensive list operations in hot paths

Security

Security-First Approach

NixOS provides unique security advantages through its declarative model and immutable store, but requires active hardening for production systems.

Quick Security Wins

{
  # 1. Enable firewall (default deny)
  networking.firewall.enable = true;

  # 2. Harden SSH
  services.openssh.settings = {
    PermitRootLogin = "no";
    PasswordAuthentication = false;
  };

  # 3. Automatic security updates
  system.autoUpgrade = {
    enable = true;
    allowReboot = false;
  };

  # 4. Enable AppArmor
  security.apparmor.enable = true;

  # 5. Use encrypted secrets
  age.secrets.mySecret.file = ../secrets/mySecret.age;
}

Hardened Profile

For security-critical systems, use the hardened profile:

imports = [
  <nixpkgs/nixos/modules/profiles/hardened.nix>
];

See workflows/Security.md for comprehensive hardening guidance.

Security Checklist

  • Firewall enabled with default deny
  • SSH hardened (no root, no passwords)
  • Secrets encrypted with agenix
  • Regular system updates configured
  • AppArmor or systemd service hardening enabled
  • Audit logging configured for critical services
  • Minimal package installation
  • Strong password policies enforced

Resources

Repository-Specific Patterns

For ~/src/home Repository

Adding a New Host

  1. Create /systems/<hostname> with boot.nix, hardware.nix
  2. Add to flake.nix using libx.mkHost
  3. Update globals.nix with machine metadata
  4. Add to secrets.nix if using secrets

Adding a New Package

  1. Create /pkgs/<package-name>/default.nix
  2. Add to /pkgs/default.nix with callPackage
  3. Test with nix build .#<package-name>

Common Commands

  • Build: make switch
  • Format: make fmt
  • Clean: make clean
  • Deploy: make host/<hostname>/switch (ask first!)

Remember: Nix is about reproducibility and declarative configuration. When in doubt, consult the manuals and follow the patterns established in the repository.

Examples

Example 1: Creating a Nix package

User: "Package this Go application for Nix"
→ Creates derivation with buildGoModule
→ Adds proper vendorHash for dependencies
→ Includes meta attributes (description, license, etc.)
→ Tests build with nix-build
→ Adds to home-manager or NixOS configuration
→ Result: Application properly packaged for Nix

Example 2: Debugging Nix build issues

User: "My Nix build is failing, can you help?"
→ Reviews error message from nix-build
→ Checks for common issues (missing dependencies, wrong hash)
→ Uses nix-shell for interactive debugging
→ Tests fix incrementally
→ Updates derivation with correct values
→ Result: Build succeeds, issue resolved

Example 3: Using Nix flakes

User: "Convert this to use flakes"
→ Creates flake.nix with inputs and outputs
→ Migrates configuration to flake structure
→ Updates flake.lock with nix flake update
→ Tests with nix build or nix develop
→ Documents flake usage in README
→ Result: Modern Nix flake structure