Files
athenix/docs/EXTERNAL_MODULES.md
UGA Innovation Factory a23ec91c9c feat: Migrate CI to gitea
2025-12-29 17:25:47 -05:00

11 KiB

External Configuration Modules

This guide explains how to use external modules for system and user configurations in nixos-systems.

Table of Contents

Overview

External modules allow you to maintain configurations in separate Git repositories and reference them from inventory.nix (for systems) or users.nix (for users).

Benefits:

  • Separation: Keep configs in separate repositories
  • Versioning: Pin to specific commits for reproducibility
  • Reusability: Share configurations across deployments
  • Flexibility: Mix external modules with local overrides

System Modules

External system modules provide complete NixOS configurations for hosts.

Usage in inventory.nix

nix-lxc = {
  devices = {
    # Traditional inline configuration
    "local-server" = {
      athenix.users.admin.enable = true;
      services.nginx.enable = true;
    };
    
    # External module from Git
    "remote-server" = builtins.fetchGit {
      url = "https://git.factory.uga.edu/org/server-config";
      rev = "abc123...";  # Pin to specific commit
    };
  };
};

External Repository Structure

server-config/
├── default.nix    # Required: NixOS module
└── README.md      # Optional: Documentation

default.nix:

{ inputs, ... }:
{ config, lib, pkgs, ... }:
{
  # Your NixOS configuration
  services.nginx = {
    enable = true;
    virtualHosts."example.com" = {
      root = "/var/www";
    };
  };
  
  # Use athenix namespace options
  athenix.users.admin.enable = true;
  athenix.sw.type = "headless";
}

What External Modules Receive

  • inputs - All flake inputs (nixpkgs, home-manager, etc.)
  • config - Full NixOS configuration
  • lib - Nixpkgs library functions
  • pkgs - Package set

Module Integration Order

When a host is built, modules are loaded in this order:

  1. User NixOS modules (from users.nix - nixos.nix files)
  2. Host type module (from hosts/types/)
  3. Configuration overrides (from inventory.nix)
  4. Hostname assignment
  5. External system module (if using builtins.fetchGit)

Later modules can override earlier ones using standard NixOS module precedence.

Template

Create a new system module:

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

See templates/system/ for the complete template.

User Modules

External user modules provide home-manager configurations (dotfiles, packages, programs).

Usage in users.nix

athenix.users = {
  # External user module (dotfiles, home-manager, and user options)
  myuser = builtins.fetchGit {
    url = "https://git.factory.uga.edu/username/dotfiles";
    rev = "abc123...";
  };

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

External Repository Structure

dotfiles/
├── user.nix     # Required: User options AND home-manager config
├── nixos.nix    # Optional: System-level config
└── config/      # Optional: Actual dotfiles
    ├── bashrc
    └── vimrc

user.nix (required):

{ inputs, ... }:
{ config, lib, pkgs, osConfig ? null, ... }:
{
  # ========== User Account Configuration ==========
  athenix.users.myusername = {
    description = "Your Full Name";
    shell = pkgs.zsh;
    hashedPassword = "!";
    opensshKeys = [ "ssh-ed25519 AAAA..." ];
    useZshTheme = true;
    useNvimPlugins = true;
  };

  # ========== Home Manager Configuration ==========
  # Packages
  home.packages = with pkgs; [
    vim
    git
    htop
  ] ++ lib.optional (osConfig.athenix.sw.type or null == "desktop") firefox;
  
  programs.git = {
    enable = true;
    userName = "My Name";
    userEmail = "me@example.com";
  };
  
  # Manage dotfiles
  home.file.".bashrc".source = ./dotfiles/bashrc;
}

nixos.nix (optional):

{ inputs, ... }:
{ config, lib, pkgs, ... }:
{
  # System-level configuration for this user
  users.users.myuser.extraGroups = [ "docker" ];
  environment.systemPackages = [ pkgs.docker ];
}

What User Modules Receive

In user.nix:

  • inputs - Flake inputs (nixpkgs, home-manager, etc.)
  • config - Home-manager configuration
  • lib - Nixpkgs library functions
  • pkgs - Package set
  • osConfig - OS-level configuration (read-only)

In nixos.nix:

  • inputs - Flake inputs
  • config - NixOS configuration
  • lib - Nixpkgs library functions
  • pkgs - Package set

User Options in users.nix

username = {
  # Identity
  description = "Full Name";
  
  # External configuration
  external = builtins.fetchGit { ... };
  # System settings
  extraGroups = [ "wheel" "networkmanager" ];
  hashedPassword = "$6$...";
  opensshKeys = [ "ssh-ed25519 ..." ];
  shell = pkgs.zsh;
  
  # Theme integration
  useZshTheme = true;      # Apply system zsh theme (default: true)
  useNvimPlugins = true;   # Apply system nvim config (default: true)
  
  # Enable on specific systems (see docs/INVENTORY.md)
  enable = false;  # Set in inventory.nix via athenix.users.username.enable
};

Template

Create a new user module:

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

See templates/user/ for the complete template.

Fetch Methods

Pin to a specific commit for reproducibility:

builtins.fetchGit {
  url = "https://github.com/user/repo";
  rev = "abc123def456...";  # Full commit hash (40 characters)
  ref = "main";              # Optional: branch name
}

Finding the commit hash:

# Latest commit on main branch
git ls-remote https://github.com/user/repo main

# Or from a local clone
git rev-parse HEAD

fetchGit with Branch (Less Reproducible)

Always fetches latest from branch:

builtins.fetchGit {
  url = "https://github.com/user/repo";
  ref = "develop";
}

⚠️ Warning: Builds may not be reproducible as the branch HEAD can change.

fetchTarball (For Releases)

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:

/home/username/dev/my-config

# Or relative to repository
./my-local-config

⚠️ Warning: Only for testing. Use Git-based methods for production.

Templates

System Module Template

# Initialize in new directory
mkdir my-server-config
cd my-server-config
nix flake init -t git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git#system

See templates/system/README.md for detailed usage.

User Module Template

# Initialize in new directory
mkdir my-dotfiles
cd my-dotfiles
nix flake init -t git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git#user

See templates/user/README.md for detailed usage.

Integration Details

Detection Logic

The system automatically detects external modules when a device or user value is:

  • A path (builtins.isPath)
  • A string starting with / (absolute path)
  • A derivation (lib.isDerivation)
  • An attrset with outPath attribute (result of fetchGit/fetchTarball)

System Module Integration

External system modules are imported and merged into the NixOS configuration:

import externalModulePath { inherit inputs; }

They can use all standard NixOS options plus athenix.* namespace options.

User Module Integration

External user modules are loaded in two contexts:

User options (NixOS module context):

import (externalPath + "/user.nix") { inherit inputs; }
# Evaluated as NixOS module to extract athenix.users.<username> options

Home-manager configuration:

import (externalPath + "/user.nix") { inherit inputs; }
# Imported into home-manager for home.*, programs.*, services.* options

System-level config (optional):

import (externalPath + "/nixos.nix") { inherit inputs; }
# If present, imported as NixOS module for system-level configuration

Combining External and Local Config

You can mix external modules with local overrides:

nix-lxc = {
  devices = {
    "server" = builtins.fetchGit {
      url = "https://git.factory.uga.edu/org/base-config";
      rev = "abc123...";
    };
  };
  overrides = {
    # Apply to all devices, including external ones
    athenix.users.admin.enable = true;
    networking.firewall.allowedTCPPorts = [ 80 443 ];
  };
};

Minimal User Module

user.nix:

{ inputs, ... }:
{ config, lib, pkgs, osConfig ? null, ... }:
{
  # User account options
  athenix.users.myusername = {
    description = "My Name";
    shell = pkgs.zsh;
    hashedPassword = "!";
  };
  
  # Home-manager config
  home.packages = with pkgs; [ vim git ];
}

Full User Module with Dotfiles

dotfiles/
├── user.nix
├── nixos.nix
└── config/
    ├── bashrc
    ├── vimrc
    └── gitconfig

user.nix:

{ inputs, ... }:
{ config, lib, pkgs, osConfig ? null, ... }:
{
  # User account configuration
  athenix.users.myusername = {
    description = "My Full Name";
    shell = pkgs.zsh;
    extraGroups = [ "wheel" "networkmanager" ];
    hashedPassword = "!";
    opensshKeys = [ "ssh-ed25519 AAAA..." ];
    useZshTheme = true;
    useNvimPlugins = true;
  };
  
  # Home-manager configuration
  home.packages = with pkgs; [
    ripgrep
    fd
    bat
  ] ++ lib.optional (osConfig.athenix.sw.type or null == "desktop") firefox;
  
  programs.git = {
    enable = true;
    userName = "My Full Name";
    userEmail = "me@example.com";
    extraConfig.init.defaultBranch = "main";
  };
  
  home.file = {
    ".bashrc".source = ./config/bashrc;
    ".vimrc".source = ./config/vimrc;
    ".gitconfig".source = ./config/gitconfig;
  };
}

See Also