Files
athenix/docs/EXTERNAL_MODULES.md
UGA Innovation Factory 97646f3229 docs: update documentation for refactored structure
- Update NAMESPACE.md: filesystem.device and swapSize defaults now null
- Update README.md: add 'Using Athenix as a Library' section with lib.mkFleet
- Update copilot-instructions.md: reflect new directory structure and defaults
- Update EXTERNAL_MODULES.md: correct paths from variants/ and glue/ to hw/ and fleet/
- Update PROXMOX_LXC.md: update hardware type path reference
2026-01-07 18:12:39 -05:00

11 KiB

External Configuration Modules

Guide to using external modules for system and user configurations.

Table of Contents

Overview

External modules allow you to maintain configurations in separate Git repositories and reference them from Athenix.

Benefits:

  • Separation - Keep complex configs in separate repositories
  • Reproducibility - Pin specific commits for deterministic builds
  • Reusability - Share configurations across multiple deployments
  • Flexibility - Mix external modules with local configuration
  • Ownership - Users maintain their own dotfiles

System Modules

External system modules provide host-specific NixOS configurations.

Usage

In inventory.nix, reference an external module using the external field:

nix-lxc = {
  devices = {
    # Inline configuration (traditional method)
    "local-server" = {
      athenix.sw.type = "headless";
      services.nginx.enable = true;
    };
    
    # External module (lazy evaluation - fetched only when building this host)
    "remote-server".external = builtins.fetchGit {
      url = "https://git.factory.uga.edu/org/server-config";
      rev = "abc123def456...";  # Must pin to specific commit
    };
    
    # External module with additional local config
    "mixed-server" = {
      external = builtins.fetchGit {
        url = "https://git.factory.uga.edu/org/server-config";
        rev = "abc123def456...";
      };
      # Additional local overrides
      athenix.users.admin.enable = true;
      services.openssh.permitRootLogin = "no";
    };
  };
};

Key Features:

  • Lazy Evaluation: External modules are only fetched when building the specific host
  • Efficient Rebuilds: Other hosts can be rebuilt without fetching unrelated external modules
  • Submodule Support: Works with Git submodules without affecting other hosts

Repository Structure

server-config/
├── default.nix     # Required: NixOS module
├── README.md       # Recommended: Documentation
└── optional/
    ├── config/     # Optional: Configuration files
    └── scripts/    # Optional: Helper scripts

Module Content (default.nix)

# The module receives inputs and standard NixOS module parameters
{ inputs, ... }:
{ config, lib, pkgs, ... }:
{
  # Your NixOS configuration
  # Use any standard NixOS option or athenix.* options
  
  services.nginx = {
    enable = true;
    virtualHosts."example.com" = {
      root = "/var/www";
      forceSSL = true;
      enableACME = true;
    };
  };
  
  # Use athenix options
  athenix.sw.type = "headless";
  athenix.sw.extraPackages = with pkgs; [ git htop ];
  
  # Standard NixOS configuration
  networking.firewall.allowedTCPPorts = [ 80 443 ];
  services.openssh.enable = true;
}

What System Modules Receive

  • inputs - All flake inputs (nixpkgs, home-manager, disko, etc.)
  • config - Current NixOS configuration (read/write)
  • lib - Nixpkgs library functions
  • pkgs - Package set

Configuration Order

When a host is built, modules load in this order:

  1. Hardware type module (from hw/nix-*.nix)
  2. Common system configuration (from fleet/common.nix)
  3. Software type module (from sw/{type}/)
  4. User NixOS modules (from users.nix - nixos.nix files)
  5. Device-specific overrides (from inventory.nix)
  6. External system module (if present)

Each later module can override earlier ones using standard NixOS precedence rules.

User Modules

External user modules provide home-manager configurations (dotfiles, environment setup).

Usage

In users.nix, reference an external user module:

athenix.users = {
  # External user module
  myuser.external = builtins.fetchGit {
    url = "https://git.factory.uga.edu/username/dotfiles";
    rev = "abc123def456...";  # Pin to specific commit
  };

  # Inline user definition
  otheruser = {
    description = "Other User";
    extraGroups = [ "wheel" ];
    shell = pkgs.zsh;
    hashedPassword = "$6$...";
  };
};

Then enable on hosts in inventory.nix:

nix-laptop = {
  devices = 5;
  overrides.athenix.users.myuser.enable = true;
};

Repository Structure

my-dotfiles/
├── user.nix         # Required: User options + home-manager config
├── nixos.nix        # Optional: System-level configuration
├── README.md        # Recommended: Documentation
└── config/          # Optional: Your actual dotfiles
    ├── zshrc
    ├── vimrc
    ├── nvim/
    └── ...

user.nix (Required)

Provides both user account settings AND home-manager configuration:

# Receives { inputs } and standard home-manager module parameters
{ inputs, ... }:
{ config, lib, pkgs, osConfig ? null, ... }:
{
  # ========== User Account Configuration ==========
  # These options define the user account itself
  
  athenix.users.myusername = {
    description = "My Full Name";
    extraGroups = [ "wheel" "docker" ];
    shell = pkgs.zsh;
    hashedPassword = "!";  # SSH keys only
    opensshKeys = [
      "ssh-ed25519 AAAA... user@laptop"
    ];
    useZshTheme = true;
    useNvimPlugins = true;
  };

  # ========== Home Manager Configuration ==========
  # User environment, packages, and dotfiles
  
  # Packages
  home.packages = with pkgs; [
    vim
    git
    ripgrep
    fzf
  ] ++ lib.optional (osConfig.athenix.sw.type or null == "desktop") firefox;

  # Programs
  programs.git = {
    enable = true;
    userName = "My Name";
    userEmail = "me@example.com";
    extraConfig = {
      init.defaultBranch = "main";
      core.editor = "vim";
    };
  };

  programs.zsh = {
    enable = true;
    initExtra = ''
      # Your Zsh configuration
      export EDITOR=vim
    '';
  };

  # Manage dotfiles
  home.file.".zshrc".source = ./config/zshrc;
  home.file.".vimrc".source = ./config/vimrc;
  home.file.".config/nvim".source = ./config/nvim;

  # Services
  services.gpg-agent.enable = true;
}

nixos.nix (Optional)

System-level configuration for this user (rarely needed):

{ inputs, ... }:
{ config, lib, pkgs, ... }:
{
  # System-level configuration
  # Only needed if the user requires specific system-wide settings
  
  users.users.myusername.extraGroups = [ "docker" ];
  environment.systemPackages = [ pkgs.docker ];
  
  # Security settings
  security.sudo.extraRules = [{
    users = [ "myusername" ];
    commands = [{
      command = "/usr/bin/something";
      options = [ "NOPASSWD" ];
    }];
  }];
}

What User Modules Receive

In user.nix:

  • inputs - All flake inputs (nixpkgs, home-manager, etc.)
  • config - Home-manager configuration (read/write)
  • lib - Nixpkgs library functions
  • pkgs - Package set
  • osConfig - OS configuration (read-only) - useful for conditional setup

In nixos.nix:

  • inputs - Flake inputs
  • config - NixOS configuration (read/write)
  • lib - Nixpkgs library functions
  • pkgs - Package set

Conditional Setup Example

Use osConfig to conditionally set up dotfiles based on the system type:

# In user.nix
{ inputs, ... }:
{ config, lib, pkgs, osConfig ? null, ... }:
{
  athenix.users.myuser = { /* ... */ };

  # Install Firefox only on desktop systems
  home.packages = with pkgs; [
    ripgrep
  ] ++ lib.optional (osConfig.athenix.sw.type or null == "desktop") firefox;

  # Different shell config per system
  programs.zsh.initExtra = ''
    ${lib.optionalString (osConfig.athenix.sw.type or null == "headless") "
      # Headless-only settings
    "}
  '';
}

Fetch Methods

Pin to a specific Git revision:

builtins.fetchGit {
  url = "https://git.factory.uga.edu/username/dotfiles";
  rev = "abc123def456...";  # Required: specific commit hash
}

Advantages:

  • Reproducible (pinned to exact commit)
  • Works with any Git repository
  • Supports SSH or HTTPS URLs

Important: Always specify rev (commit hash) for reproducibility. Don't use branches which can change.

builtins.fetchTarball

Download specific release archives:

builtins.fetchTarball {
  url = "https://github.com/user/repo/archive/v1.0.0.tar.gz";
  sha256 = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
}

Get the hash:

nix-prefetch-url --unpack https://github.com/user/repo/archive/v1.0.0.tar.gz

Local Path (For Testing)

Use local directories during development:

# users.nix
athenix.users.myuser.external = /home/user/my-dotfiles;

# inventory.nix
nix-laptop = {
  devices = {
    "dev".athenix.users.myuser.enable = true;
  };
};

Note: Only works if the path exists on the machine running nix flake check or nix build.

Creating External Modules

System Module Template

Create a new system module repository from the template:

nix flake init -t git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git#system

This creates:

my-system-config/
├── flake.nix        # Optional: for testing standalone
├── default.nix      # Your NixOS module
└── README.md        # Documentation

User Module Template

Create a new user module repository:

nix flake init -t git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git#user

This creates:

my-dotfiles/
├── flake.nix        # Optional: for testing standalone
├── user.nix         # User options + home-manager config
├── nixos.nix        # Optional: system-level config
└── README.md        # Documentation

Testing External Modules

Test your external module locally before pushing:

# In your module repository
cd /path/to/my-module

# Test the Nix syntax
nix flake check

Best Practices

1. Always Pin to Specific Commits

Wrong - using branch names:

builtins.fetchGit {
  url = "https://git.factory.uga.edu/username/dotfiles";
  # No rev specified or using "main"
}

Correct - using commit hash:

builtins.fetchGit {
  url = "https://git.factory.uga.edu/username/dotfiles";
  rev = "abc123def456789...";
}

2. Keep External Modules Focused

Each external module should have a clear purpose:

  • User dotfiles (one repo per user)
  • System service configuration (one repo per service/cluster)
  • Hardware-specific config (one repo per hardware setup)

3. Document Your Modules

Include a README with:

  • What the module configures
  • Required dependencies
  • Usage examples
  • Configuration options

4. Use Semantic Versioning

Tag releases in Git:

git tag v1.0.0
git push origin v1.0.0

Reference specific versions:

builtins.fetchGit {
  url = "https://git.factory.uga.edu/org/server-config";
  rev = "v1.0.0";  # Can use tags too
}

5. Test Before Updating Pins

When updating commit hashes:

# Test new revision locally
nix flake update

# Validate all configurations
nix flake check --show-trace

# Only commit after validation
git add . && git commit -m "Update module versions"

See Also