# External Configuration Modules

Guide to using external modules for system and user configurations.

## Table of Contents

- [Overview](#overview)
- [System Modules](#system-modules)
- [User Modules](#user-modules)
- [Fetch Methods](#fetch-methods)
- [Creating External Modules](#creating-external-modules)
- [Best Practices](#best-practices)

## 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
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)

```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:

```nix
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
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:

```nix
# 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):

```nix
{ 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:

```nix
# 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

### builtins.fetchGit (Recommended)

Pin to a specific Git revision:

```nix
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:

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

**Get the hash:**
```bash
nix-prefetch-url --unpack https://github.com/user/repo/archive/v1.0.0.tar.gz
```

### Local Path (For Testing)

Use local directories during development:

```nix
# 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:

```bash
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:

```bash
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:

```bash
# 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:
```nix
builtins.fetchGit {
  url = "https://git.factory.uga.edu/username/dotfiles";
  # No rev specified or using "main"
}
```

✅ Correct - using commit hash:
```nix
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:

```bash
git tag v1.0.0
git push origin v1.0.0
```

Reference specific versions:

```nix
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:

```bash
# 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

- [USER_CONFIGURATION.md](USER_CONFIGURATION.md) - User management
- [INVENTORY.md](INVENTORY.md) - Host configuration
- [NAMESPACE.md](NAMESPACE.md) - Configuration options
- [README.md](../README.md) - Main documentation
- [templates/user/](../templates/user/) - User module template
- [templates/system/](../templates/system/) - System module template
