Compare commits
84 Commits
3886d6f0b6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f7e95b9f9 | ||
|
|
7c07727150 | ||
|
|
7e6e8d5e0f | ||
|
|
c6e0a0aedf | ||
|
|
4b4e6a2873 | ||
|
|
40a9f9f5a6 | ||
|
|
14a61da9ed | ||
|
|
a3c8e0640a | ||
|
|
01fc5518c1 | ||
|
|
a2d4f71a77 | ||
|
|
e0cafb7f66 | ||
|
|
ffbd7a221d | ||
|
|
d7922247d2 | ||
|
|
31c829f502 | ||
|
|
e3bae02f58 | ||
|
|
aa6d9d5691 | ||
|
|
87045a518f | ||
|
|
dffe817e47 | ||
|
|
23da829033 | ||
|
|
dd19d1488a | ||
|
|
862ae2c864 | ||
|
|
3efba93424 | ||
|
|
2e4602cbf3 | ||
|
|
ab3710b5f6 | ||
|
|
863cd1ea95 | ||
|
|
d8cee7e79b | ||
|
|
063336f736 | ||
|
|
85653e632f | ||
|
|
1533382ff2 | ||
|
|
540f5feb78 | ||
|
|
1a7bf29448 | ||
|
|
13fdc3a7a1 | ||
|
|
01fdfbf913 | ||
|
|
9d0683165f | ||
|
|
b1bc354160 | ||
|
|
f669845bf7 | ||
|
|
bd50f894ae | ||
|
|
92e3940644 | ||
|
|
1c767ed4c8 | ||
|
|
ffa434e720 | ||
|
|
5f5698f608 | ||
|
|
f606ea731c | ||
|
|
b1d4fe8d68 | ||
|
|
cbddecfeb4 | ||
|
|
005207d3e4 | ||
|
|
d34325de53 | ||
|
|
67e7a57402 | ||
|
|
dcc3dde702 | ||
|
|
14fb79231f | ||
|
|
ea4c2df776 | ||
|
|
d89caa8a6b | ||
|
|
4cb8d8ef13 | ||
|
|
97646f3229 | ||
|
|
d3788be951 | ||
|
|
cda1975631 | ||
|
|
7145f5b3d8 | ||
|
|
1ce7334a73 | ||
|
|
775080949d | ||
|
|
d15b4d8067 | ||
|
|
5875725ca2 | ||
|
|
9e066d395b | ||
|
|
825e90c581 | ||
|
|
6a9807a688 | ||
|
|
c4ff0d7fd3 | ||
|
|
cca3e39af0 | ||
|
|
917275409f | ||
|
|
b3e274484f | ||
|
|
55c49d84b5 | ||
|
|
6972a999ca | ||
|
|
faf7bb635e | ||
|
|
c3bbf6f8be | ||
|
|
77cea838a1 | ||
|
|
cb37fad70e | ||
|
|
03f532e867 | ||
|
|
9a2f167efe | ||
|
|
6edf858a4e | ||
|
|
3f1801fd84 | ||
|
|
f68c63590b | ||
|
|
c6f4a39eee | ||
|
|
c2b5e4eafe | ||
|
|
f07ccc071e | ||
|
|
0378268dcc | ||
|
|
cad9cb35ef | ||
|
|
c8c3894e65 |
@@ -26,18 +26,23 @@ jobs:
|
|||||||
format-check:
|
format-check:
|
||||||
name: Format Check
|
name: Format Check
|
||||||
runs-on: [self-hosted, nix-builder]
|
runs-on: [self-hosted, nix-builder]
|
||||||
|
timeout-minutes: 5
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Check formatting
|
- name: Check formatting
|
||||||
|
timeout-minutes: 3
|
||||||
run: |
|
run: |
|
||||||
nix fmt **/*.nix
|
set -euo pipefail
|
||||||
if ! git diff --quiet; then
|
echo "Checking code formatting..."
|
||||||
|
output=$(nix fmt **/*.nix 2>&1)
|
||||||
|
if [ -n "$output" ]; then
|
||||||
echo "::error::Code is not formatted. Please run 'nix fmt **/*.nix' locally."
|
echo "::error::Code is not formatted. Please run 'nix fmt **/*.nix' locally."
|
||||||
git diff
|
echo "$output"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
echo "All files are properly formatted"
|
||||||
|
|
||||||
eval-configs:
|
eval-configs:
|
||||||
name: Evaluate Key Configurations
|
name: Evaluate Key Configurations
|
||||||
@@ -79,3 +84,39 @@ jobs:
|
|||||||
echo "Evaluating artifact ${{ matrix.artifact }}"
|
echo "Evaluating artifact ${{ matrix.artifact }}"
|
||||||
nix eval .#${{ matrix.artifact }}.drvPath \
|
nix eval .#${{ matrix.artifact }}.drvPath \
|
||||||
--show-trace
|
--show-trace
|
||||||
|
|
||||||
|
build-docs:
|
||||||
|
name: Build and Publish Documentation
|
||||||
|
runs-on: [self-hosted, nix-builder]
|
||||||
|
needs: [flake-check]
|
||||||
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Build documentation
|
||||||
|
run: |
|
||||||
|
echo "Building Athenix documentation"
|
||||||
|
nix build .#docs --print-build-logs
|
||||||
|
|
||||||
|
- name: Clone wiki repository
|
||||||
|
run: |
|
||||||
|
git clone git@factory.uga.edu:UGA-Innovation-Factory/athenix.wiki.git wiki
|
||||||
|
cd wiki
|
||||||
|
git config user.name "Athenix CI"
|
||||||
|
git config user.email "ci@athenix.factory.uga.edu"
|
||||||
|
|
||||||
|
- name: Update wiki with documentation
|
||||||
|
run: |
|
||||||
|
# Copy documentation to wiki
|
||||||
|
cp -r result/* wiki/
|
||||||
|
|
||||||
|
# Commit and push changes
|
||||||
|
cd wiki
|
||||||
|
git add .
|
||||||
|
if git diff --staged --quiet; then
|
||||||
|
echo "No documentation changes to commit"
|
||||||
|
else
|
||||||
|
git commit -m "Update documentation from commit ${{ github.sha }}"
|
||||||
|
git push
|
||||||
|
fi
|
||||||
|
|||||||
25
.github/copilot-instructions.md
vendored
25
.github/copilot-instructions.md
vendored
@@ -26,8 +26,9 @@ This is a **NixOS system configuration repository** that uses:
|
|||||||
- **`flake.nix`**: Entry point - inputs and outputs only
|
- **`flake.nix`**: Entry point - inputs and outputs only
|
||||||
- **`inventory.nix`**: Fleet definitions - host configurations
|
- **`inventory.nix`**: Fleet definitions - host configurations
|
||||||
- **`users.nix`**: User account definitions
|
- **`users.nix`**: User account definitions
|
||||||
- **`hosts/`**: Host generation logic and hardware types
|
- **`hw/`**: Hardware type modules (desktop, laptop, surface, lxc, wsl, etc.)
|
||||||
- **`sw/`**: Software configurations organized by system type
|
- **`fleet/`**: Fleet generation logic and common system configuration
|
||||||
|
- **`sw/`**: Software configurations by system type
|
||||||
- **`installer/`**: Build artifact generation (ISO, LXC, etc.)
|
- **`installer/`**: Build artifact generation (ISO, LXC, etc.)
|
||||||
- **`templates/`**: Templates for external configurations
|
- **`templates/`**: Templates for external configurations
|
||||||
|
|
||||||
@@ -44,9 +45,12 @@ All Innovation Factory-specific options MUST use the `athenix` namespace:
|
|||||||
### Host Options (`athenix.host.*`)
|
### Host Options (`athenix.host.*`)
|
||||||
```nix
|
```nix
|
||||||
athenix.host = {
|
athenix.host = {
|
||||||
filesystem.device = "/dev/sda"; # Boot disk
|
filesystem.device = "/dev/nvme0n1"; # Boot disk (default: null)
|
||||||
filesystem.swapSize = "32G"; # Swap size
|
filesystem.swapSize = "32G"; # Swap size (default: null)
|
||||||
buildMethods = [ "iso" ]; # Artifact types
|
buildMethods = [ "installer-iso" ]; # Artifact types (defined in sw/gc.nix)
|
||||||
|
useHostPrefix = true; # Hostname prefix behavior
|
||||||
|
wsl.user = "username"; # WSL default user
|
||||||
|
};
|
||||||
useHostPrefix = true; # Hostname prefix behavior
|
useHostPrefix = true; # Hostname prefix behavior
|
||||||
wsl.user = "username"; # WSL default user
|
wsl.user = "username"; # WSL default user
|
||||||
};
|
};
|
||||||
@@ -113,6 +117,17 @@ athenix.forUser = "username"; # Convenience: enable user + set WSL us
|
|||||||
3. System modules: Provide `default.nix` that accepts `{ inputs, ... }`
|
3. System modules: Provide `default.nix` that accepts `{ inputs, ... }`
|
||||||
4. Reference in `inventory.nix` or `users.nix` using `builtins.fetchGit`
|
4. Reference in `inventory.nix` or `users.nix` using `builtins.fetchGit`
|
||||||
|
|
||||||
|
#### Using Athenix as a Library
|
||||||
|
External flakes can use Athenix's fleet generator:
|
||||||
|
```nix
|
||||||
|
outputs = { athenix, ... }: {
|
||||||
|
nixosConfigurations = athenix.lib.mkFleet {
|
||||||
|
fleet = import ./custom-inventory.nix;
|
||||||
|
hwTypes = import ./custom-hardware.nix;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
## Important Constraints
|
## Important Constraints
|
||||||
|
|
||||||
### What NOT to Do
|
### What NOT to Do
|
||||||
|
|||||||
34
.github/workflows/docs.yml
vendored
Normal file
34
.github/workflows/docs.yml
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
name: Build Documentation
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-docs:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: DeterminateSystems/nix-installer-action@main
|
||||||
|
|
||||||
|
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||||
|
|
||||||
|
- name: Build documentation
|
||||||
|
run: |
|
||||||
|
nix build .#docs
|
||||||
|
nix build .#athenix-options
|
||||||
|
|
||||||
|
- name: Upload documentation
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: athenix-docs
|
||||||
|
path: result/
|
||||||
|
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
if: github.ref == 'refs/heads/main'
|
||||||
|
uses: peaceiris/actions-gh-pages@v4
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
publish_dir: ./result
|
||||||
18
.nixd.json
Normal file
18
.nixd.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"eval": {
|
||||||
|
"target": {
|
||||||
|
"installable": ".#nixosConfigurations.nix-desktop1.options"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"formatting": {
|
||||||
|
"command": ["nixfmt"]
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"nixos": {
|
||||||
|
"expr": "(builtins.getFlake \"/home/engr-ugaif/athenix\").nixosConfigurations.nix-desktop1.options"
|
||||||
|
},
|
||||||
|
"home-manager": {
|
||||||
|
"expr": "(builtins.getFlake \"/home/engr-ugaif/athenix\").nixosConfigurations.nix-desktop1.config.home-manager.users.engr-ugaif.options"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
357
README.md
357
README.md
@@ -1,156 +1,159 @@
|
|||||||
# UGA Innovation Factory - Athenix
|
# Athenix - UGA Innovation Factory NixOS Configuration
|
||||||
|
|
||||||
[](https://git.factory.uga.edu/UGA-Innovation-Factory/athenix/actions)
|
[](https://git.factory.uga.edu/UGA-Innovation-Factory/athenix/actions)
|
||||||
|
|
||||||
This repository contains the NixOS configuration for the Innovation Factory's fleet of laptops, desktops, Surface tablets, and containers. It provides a declarative, reproducible system configuration using Nix flakes.
|
Declarative NixOS configuration management for the Innovation Factory's fleet of workstations, laptops, tablets, and containers using Nix flakes.
|
||||||
|
|
||||||
## Documentation
|
## Quick Navigation
|
||||||
|
|
||||||
- **[Quick Start](#quick-start)** - Get started in 5 minutes
|
- **[docs/INVENTORY.md](docs/INVENTORY.md)** - Define and configure hosts
|
||||||
- **[docs/INVENTORY.md](docs/INVENTORY.md)** - Configure hosts and fleet inventory
|
- **[docs/NAMESPACE.md](docs/NAMESPACE.md)** - All `athenix.*` options reference
|
||||||
- **[docs/NAMESPACE.md](docs/NAMESPACE.md)** - Configuration options reference (`athenix.*`)
|
- **[docs/USER_CONFIGURATION.md](docs/USER_CONFIGURATION.md)** - User accounts and dotfiles
|
||||||
- **[docs/USER_CONFIGURATION.md](docs/USER_CONFIGURATION.md)** - User account management
|
- **[docs/EXTERNAL_MODULES.md](docs/EXTERNAL_MODULES.md)** - External system and user configurations
|
||||||
- **[docs/EXTERNAL_MODULES.md](docs/EXTERNAL_MODULES.md)** - External configuration modules
|
- **[docs/BUILDING.md](docs/BUILDING.md)** - Build ISOs, containers, and artifacts
|
||||||
- **[docs/BUILDING.md](docs/BUILDING.md)** - Build ISOs and container images
|
- **[docs/DEVELOPMENT.md](docs/DEVELOPMENT.md)** - Development workflow and testing
|
||||||
- **[docs/DEVELOPMENT.md](docs/DEVELOPMENT.md)** - Development and testing workflow
|
|
||||||
|
|
||||||
## Quick Start
|
## Getting Started
|
||||||
|
|
||||||
### For End Users
|
### For End Users
|
||||||
|
|
||||||
Update your system to the latest configuration:
|
Update your system:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
update-system
|
update-system
|
||||||
```
|
```
|
||||||
|
|
||||||
This command automatically fetches the latest configuration, rebuilds your system, and uses remote builders on Surface tablets to speed up builds.
|
This automatically rebuilds your system with the latest configuration from the repository.
|
||||||
|
|
||||||
**Note:** If you use external user configurations (personal dotfiles), run:
|
|
||||||
```bash
|
|
||||||
sudo nixos-rebuild switch --flake git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git --impure
|
|
||||||
```
|
|
||||||
|
|
||||||
### For Administrators
|
### For Administrators
|
||||||
|
|
||||||
|
Make configuration changes:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 1. Make changes to configuration files
|
# Edit inventory
|
||||||
vim inventory.nix
|
vim inventory.nix
|
||||||
|
|
||||||
# 2. Test configuration
|
# Validate changes
|
||||||
nix flake check
|
nix flake check
|
||||||
|
|
||||||
# 3. Format code
|
# Format code
|
||||||
nix fmt
|
nix fmt
|
||||||
|
|
||||||
# 4. Commit and push
|
# Commit and push
|
||||||
git add .
|
git add . && git commit -m "Your message" && git push
|
||||||
git commit -m "Description of changes"
|
|
||||||
git push
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Users can now run `update-system` to get the changes.
|
Users automatically get changes when they run `update-system`.
|
||||||
|
|
||||||
**See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for detailed development workflow.**
|
|
||||||
|
|
||||||
## Repository Structure
|
## Repository Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
nixos-systems/
|
flake.nix # Flake entry point (inputs + outputs)
|
||||||
├── flake.nix # Flake entry point
|
inventory.nix # Fleet inventory and host definitions
|
||||||
├── inventory.nix # Fleet inventory - Define hosts here
|
users.nix # User account definitions
|
||||||
├── users.nix # User accounts - Define users here
|
|
||||||
├── hosts/ # Host generation logic
|
flake.lock # Locked dependency versions
|
||||||
│ ├── types/ # Hardware types (desktop, laptop, surface, lxc, wsl, ephemeral)
|
|
||||||
│ └── ...
|
hw/ # Hardware type modules (exportable as nixosModules)
|
||||||
├── sw/ # Software configurations by system type
|
├── default.nix # Auto-exports all variant types
|
||||||
│ ├── desktop/ # Full desktop environment
|
├── nix-desktop.nix # Desktop workstations
|
||||||
│ ├── tablet-kiosk/ # Surface kiosk mode
|
├── nix-laptop.nix # Laptop systems
|
||||||
│ ├── stateless-kiosk/# Diskless PXE kiosks
|
├── nix-surface.nix # Surface Pro tablets
|
||||||
│ ├── headless/ # Servers and containers
|
├── nix-lxc.nix # LXC containers
|
||||||
│ └── ...
|
├── nix-wsl.nix # WSL instances
|
||||||
├── installer/ # ISO and container builds
|
├── nix-zima.nix # ZimaBoard systems
|
||||||
├── templates/ # Templates for external configs
|
└── nix-ephemeral.nix # Diskless/netboot systems
|
||||||
│ ├── system/ # System configuration template
|
|
||||||
│ └── user/ # User configuration template
|
fleet/ # Fleet generation and common configuration
|
||||||
├── docs/ # Documentation
|
├── default.nix # Processes inventory.nix to generate all hosts
|
||||||
│ ├── INVENTORY.md # Host configuration guide
|
├── common.nix # Common NixOS configuration (all hosts)
|
||||||
│ ├── NAMESPACE.md # Option reference
|
├── boot.nix # Boot and filesystem configuration
|
||||||
│ ├── BUILDING.md # Building artifacts
|
└── user-config.nix # User account and home-manager integration
|
||||||
│ └── DEVELOPMENT.md # Development guide
|
|
||||||
└── assets/ # Assets (Plymouth theme, etc.)
|
sw/ # Software configurations by system type
|
||||||
|
├── default.nix # Software module entry point
|
||||||
|
├── python.nix # Python tools (pixi, uv)
|
||||||
|
├── nvim.nix # Neovim configuration
|
||||||
|
├── ghostty.nix # Ghostty terminal
|
||||||
|
├── theme.nix # System theme configuration
|
||||||
|
├── updater.nix # System update scripts
|
||||||
|
├── update-ref.nix # Update reference tracking
|
||||||
|
├── builders/ # Build server configuration
|
||||||
|
├── desktop/ # Desktop environment
|
||||||
|
├── headless/ # Server/container without GUI
|
||||||
|
├── tablet-kiosk/ # Surface tablet kiosk mode
|
||||||
|
└── stateless-kiosk/ # Diskless PXE netboot systems
|
||||||
|
|
||||||
|
installer/ # Build artifacts
|
||||||
|
├── default.nix # Build configuration
|
||||||
|
├── artifacts.nix # ISO/LXC/Proxmox definitions
|
||||||
|
├── auto-install.nix # Installer scripts
|
||||||
|
├── modules.nix # Installer-specific modules
|
||||||
|
├── deploy-proxmox-lxc.sh # Proxmox deployment script
|
||||||
|
└── PROXMOX_LXC.md # Proxmox guide
|
||||||
|
|
||||||
|
templates/ # Templates for external modules
|
||||||
|
├── user/ # User configuration template
|
||||||
|
│ ├── user.nix # User options + home-manager config
|
||||||
|
│ └── README.md
|
||||||
|
└── system/ # System configuration template
|
||||||
|
├── default.nix # NixOS module
|
||||||
|
└── README.md
|
||||||
|
|
||||||
|
docs/ # Documentation
|
||||||
|
├── README.md # This file
|
||||||
|
├── INVENTORY.md # Host configuration guide
|
||||||
|
├── NAMESPACE.md # Option reference
|
||||||
|
├── USER_CONFIGURATION.md # User management
|
||||||
|
├── EXTERNAL_MODULES.md # External module integration
|
||||||
|
├── BUILDING.md # Build and deployment
|
||||||
|
└── DEVELOPMENT.md # Development workflow
|
||||||
|
|
||||||
|
assets/ # Assets
|
||||||
|
└── plymouth-theme/ # Boot splash theme
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration Overview
|
## Configuration Overview
|
||||||
|
|
||||||
All Innovation Factory options use the `athenix.*` namespace. See **[docs/NAMESPACE.md](docs/NAMESPACE.md)** for complete reference.
|
All Innovation Factory-specific options use the `athenix` namespace to avoid conflicts with NixOS options.
|
||||||
|
|
||||||
**Quick examples:**
|
### Common Options
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
# Host configuration
|
# Host filesystem and hardware
|
||||||
athenix.host.filesystem.device = "/dev/nvme0n1";
|
athenix.host = {
|
||||||
athenix.host.filesystem.swapSize = "64G";
|
filesystem.device = "/dev/sda";
|
||||||
|
filesystem.swapSize = "32G";
|
||||||
|
buildMethods = [ "installer-iso" ];
|
||||||
|
useHostPrefix = true;
|
||||||
|
};
|
||||||
|
|
||||||
# Software configuration
|
# System type and packages
|
||||||
athenix.sw.type = "desktop"; # or "headless", "tablet-kiosk"
|
athenix.sw = {
|
||||||
athenix.sw.extraPackages = with pkgs; [ vim docker ];
|
type = "desktop"; # desktop, tablet-kiosk, stateless-kiosk, headless, builders
|
||||||
|
extraPackages = with pkgs; [ vim docker ];
|
||||||
|
};
|
||||||
|
|
||||||
# User management
|
# User management
|
||||||
athenix.users.myuser.enable = true;
|
athenix.users.myuser.enable = true;
|
||||||
athenix.forUser = "myuser"; # Convenience shortcut
|
athenix.forUser = "myuser"; # Convenience shortcut
|
||||||
```
|
```
|
||||||
|
|
||||||
## Prerequisites
|
See [docs/NAMESPACE.md](docs/NAMESPACE.md) for complete option reference.
|
||||||
|
|
||||||
To work with this repository, install Nix with flakes support:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Recommended: Determinate Systems installer (includes flakes)
|
|
||||||
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
|
|
||||||
|
|
||||||
# Alternative: Official installer (requires enabling flakes manually)
|
|
||||||
sh <(curl -L https://nixos.org/nix/install) --daemon
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Tasks
|
## Common Tasks
|
||||||
|
|
||||||
### Adding a New User
|
|
||||||
|
|
||||||
1. Edit `users.nix`:
|
|
||||||
|
|
||||||
```nix
|
|
||||||
myuser = {
|
|
||||||
description = "My Full Name";
|
|
||||||
extraGroups = [ "wheel" "networkmanager" ];
|
|
||||||
shell = pkgs.zsh;
|
|
||||||
hashedPassword = "$6$..."; # Generate with: mkpasswd -m sha-512
|
|
||||||
opensshKeys = [ "ssh-ed25519 AAAA... user@host" ];
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Enable on hosts in `inventory.nix`:
|
|
||||||
|
|
||||||
```nix
|
|
||||||
nix-laptop = {
|
|
||||||
devices = 2;
|
|
||||||
overrides.athenix.users.myuser.enable = true;
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
**See [docs/USER_CONFIGURATION.md](docs/USER_CONFIGURATION.md) for complete user management guide.**
|
|
||||||
|
|
||||||
### Adding Hosts
|
### Adding Hosts
|
||||||
|
|
||||||
Edit `inventory.nix`:
|
Edit `inventory.nix`:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
# Simple: Create 5 laptops
|
# Simple: Create 5 identical laptops
|
||||||
nix-laptop = {
|
nix-laptop = {
|
||||||
devices = 5; # Creates nix-laptop1 through nix-laptop5
|
devices = 5;
|
||||||
};
|
};
|
||||||
|
|
||||||
# With configuration
|
# With custom configuration per device
|
||||||
nix-surface = {
|
nix-surface = {
|
||||||
devices = {
|
devices = {
|
||||||
"1".athenix.sw.kioskUrl = "https://dashboard1.example.com";
|
"1".athenix.sw.kioskUrl = "https://dashboard1.example.com";
|
||||||
@@ -158,107 +161,163 @@ nix-surface = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# With overrides for all devices
|
# With common overrides
|
||||||
nix-desktop = {
|
nix-desktop = {
|
||||||
devices = 3;
|
devices = 3;
|
||||||
overrides = {
|
overrides = {
|
||||||
athenix.users.student.enable = true;
|
athenix.users.student.enable = true;
|
||||||
athenix.sw.extraPackages = with pkgs; [ vim ];
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
**See [docs/INVENTORY.md](docs/INVENTORY.md) for complete host configuration guide.**
|
**See [docs/INVENTORY.md](docs/INVENTORY.md) for complete guide.**
|
||||||
|
|
||||||
|
### Managing Users
|
||||||
|
|
||||||
|
Edit `users.nix`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.users.myuser = {
|
||||||
|
description = "My Name";
|
||||||
|
extraGroups = [ "wheel" "networkmanager" ];
|
||||||
|
shell = pkgs.zsh;
|
||||||
|
hashedPassword = "$6$..."; # mkpasswd -m sha-512
|
||||||
|
opensshKeys = [ "ssh-ed25519 AAAA..." ];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Enable in `inventory.nix`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
nix-laptop = {
|
||||||
|
overrides.athenix.users.myuser.enable = true;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
**See [docs/USER_CONFIGURATION.md](docs/USER_CONFIGURATION.md) for complete guide.**
|
||||||
|
|
||||||
### Using External Configurations
|
### Using External Configurations
|
||||||
|
|
||||||
Users and systems can reference external Git repositories for configuration:
|
Reference external repositories for user dotfiles or system configurations:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
# In users.nix - External dotfiles with user configuration
|
# User dotfiles (in users.nix)
|
||||||
myuser.external = builtins.fetchGit {
|
hdh20267.external = builtins.fetchGit {
|
||||||
url = "https://git.factory.uga.edu/username/dotfiles";
|
url = "https://git.factory.uga.edu/hdh20267/dotfiles";
|
||||||
rev = "abc123...";
|
rev = "abc123...";
|
||||||
};
|
};
|
||||||
# The external user.nix file contains both athenix.users.myuser options
|
|
||||||
# AND home-manager configuration
|
|
||||||
|
|
||||||
# In inventory.nix - External system config
|
# System configuration (in inventory.nix)
|
||||||
nix-lxc = {
|
nix-lxc = {
|
||||||
devices."server" = builtins.fetchGit {
|
devices."special" = builtins.fetchGit {
|
||||||
url = "https://git.factory.uga.edu/org/server-config";
|
url = "https://git.factory.uga.edu/org/server-config";
|
||||||
rev = "abc123...";
|
rev = "abc123...";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
**Create templates:**
|
|
||||||
```bash
|
|
||||||
# User configuration (dotfiles)
|
|
||||||
nix flake init -t git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git#user
|
|
||||||
|
|
||||||
# System configuration
|
|
||||||
nix flake init -t git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git#system
|
|
||||||
```
|
|
||||||
|
|
||||||
**See [docs/EXTERNAL_MODULES.md](docs/EXTERNAL_MODULES.md) for complete guide.**
|
**See [docs/EXTERNAL_MODULES.md](docs/EXTERNAL_MODULES.md) for complete guide.**
|
||||||
|
|
||||||
### Building Installation Media
|
### Building Installation Media
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build installer ISO
|
# Build installer ISO for a specific host
|
||||||
nix build git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git#installer-iso-nix-laptop1
|
nix build .#installer-iso-nix-laptop1
|
||||||
|
|
||||||
# Build LXC container
|
# Build LXC container
|
||||||
nix build .#lxc-nix-builder
|
nix build .#lxc-nix-builder
|
||||||
|
|
||||||
# List all available artifacts
|
# List all available artifacts
|
||||||
nix flake show git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git
|
nix flake show
|
||||||
```
|
```
|
||||||
|
|
||||||
**See [docs/BUILDING.md](docs/BUILDING.md) for complete guide on building ISOs, containers, and using remote builders.**
|
**See [docs/BUILDING.md](docs/BUILDING.md) for complete guide.**
|
||||||
|
|
||||||
|
### Using Athenix as a Library
|
||||||
|
|
||||||
|
Import Athenix in your own flake to use its fleet generation logic with custom inventory:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
inputs.athenix.url = "git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git";
|
||||||
|
|
||||||
|
outputs = { self, athenix, ... }: {
|
||||||
|
# Generate configurations with custom fleet and hardware types
|
||||||
|
nixosConfigurations = athenix.lib.mkFleet {
|
||||||
|
fleet = import ./my-inventory.nix;
|
||||||
|
hwTypes = import ./my-hardware-types.nix;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Or use individual modules
|
||||||
|
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
|
||||||
|
modules = [
|
||||||
|
athenix.nixosModules.hw.nix-desktop # Use Athenix hardware configs
|
||||||
|
athenix.nixosModules.sw # Use Athenix software configs
|
||||||
|
./configuration.nix
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Exported modules:** `nix-desktop`, `nix-laptop`, `nix-surface`, `nix-lxc`, `nix-wsl`, `nix-ephemeral`, `nix-zima`, `sw`, `common`
|
||||||
|
|
||||||
## System Types
|
## System Types
|
||||||
|
|
||||||
|
Set via `athenix.sw.type`:
|
||||||
|
|
||||||
- **`desktop`** - Full GNOME desktop environment
|
- **`desktop`** - Full GNOME desktop environment
|
||||||
- **`tablet-kiosk`** - Surface tablets in kiosk mode
|
- **`tablet-kiosk`** - Surface tablets with Firefox kiosk browser
|
||||||
- **`stateless-kiosk`** - Diskless PXE boot kiosks
|
- **`stateless-kiosk`** - Diskless PXE-booted systems
|
||||||
- **`headless`** - Servers and containers (no GUI)
|
- **`headless`** - Servers and containers without GUI
|
||||||
|
- **`builders`** - Build servers
|
||||||
|
|
||||||
Set via `athenix.sw.type` option. See [docs/NAMESPACE.md](docs/NAMESPACE.md) for all options.
|
## Development Workflow
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
**Quick commands:**
|
|
||||||
```bash
|
```bash
|
||||||
nix flake check # Validate all configurations
|
# Check all configurations
|
||||||
nix fmt # Format code
|
nix flake check
|
||||||
nix flake update # Update dependencies
|
|
||||||
nix build .#installer-iso-nix-laptop1 # Build specific artifact
|
# Format code
|
||||||
|
nix fmt **/*.nix
|
||||||
|
|
||||||
|
# Build specific artifact
|
||||||
|
nix build .#installer-iso-nix-laptop1
|
||||||
|
|
||||||
|
# Update flake inputs
|
||||||
|
nix flake update
|
||||||
```
|
```
|
||||||
|
|
||||||
**See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for complete development guide.**
|
**See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for detailed workflow.**
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
**Common issues:**
|
| Issue | Solution |
|
||||||
|
|-------|----------|
|
||||||
|
| Build errors | Run `nix flake check --show-trace` for details |
|
||||||
|
| Configuration validation | `nix flake check` checks all 50+ hosts |
|
||||||
|
| External modules fail | Verify Git URL accessibility and module structure |
|
||||||
|
| Remote build issues | Test SSH: `ssh engr-ugaif@nix-builder` |
|
||||||
|
| List all hosts | `nix eval .#nixosConfigurations --apply builtins.attrNames` |
|
||||||
|
| Disk space | `nix-collect-garbage -d && nix store optimise` |
|
||||||
|
|
||||||
- **Build errors:** Run `nix flake check --show-trace` for details
|
## Prerequisites
|
||||||
- **External modules not loading:** Check repository access and module structure (see templates)
|
|
||||||
- **Remote build failures:** Test SSH access: `ssh engr-ugaif@nix-builder`
|
Nix with flakes support:
|
||||||
- **Out of disk space:** Run `nix-collect-garbage -d && nix store optimise`
|
|
||||||
|
|
||||||
**Useful commands:**
|
|
||||||
```bash
|
```bash
|
||||||
nix flake show # List all available outputs
|
# Recommended: Determinate Systems installer
|
||||||
nix flake metadata # Show flake info
|
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
|
||||||
nix eval .#nixosConfigurations --apply builtins.attrNames # List all hosts
|
|
||||||
|
# Or enable flakes in existing Nix installation
|
||||||
|
echo 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf
|
||||||
```
|
```
|
||||||
|
|
||||||
**See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) and [docs/BUILDING.md](docs/BUILDING.md) for detailed troubleshooting.**
|
## More Information
|
||||||
|
|
||||||
## Getting Help
|
- [docs/INVENTORY.md](docs/INVENTORY.md) - Host configuration
|
||||||
|
- [docs/NAMESPACE.md](docs/NAMESPACE.md) - All option references
|
||||||
- Review documentation in `docs/` directory
|
- [docs/USER_CONFIGURATION.md](docs/USER_CONFIGURATION.md) - User management
|
||||||
- Check templates: `templates/user/` and `templates/system/`
|
- [docs/EXTERNAL_MODULES.md](docs/EXTERNAL_MODULES.md) - External modules
|
||||||
- Contact Innovation Factory IT team
|
- [docs/BUILDING.md](docs/BUILDING.md) - Building and deployment
|
||||||
|
- [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) - Development guide
|
||||||
|
|||||||
Submodule assets/plymouth-theme deleted from 8658f4fb40
377
docs/BUILDING.md
377
docs/BUILDING.md
@@ -1,11 +1,13 @@
|
|||||||
# Building Installation Media
|
# Building Installation Media and Artifacts
|
||||||
|
|
||||||
This guide covers building installer ISOs, live images, and container artifacts from the nixos-systems flake.
|
Guide to building installer ISOs, live images, and container artifacts.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
- [Quick Start](#quick-start)
|
- [Quick Start](#quick-start)
|
||||||
- [Available Artifacts](#available-artifacts)
|
- [Available Artifacts](#available-artifacts)
|
||||||
|
- [Building Locally](#building-locally)
|
||||||
|
- [Building from Remote](#building-from-remote)
|
||||||
- [Installer ISOs](#installer-isos)
|
- [Installer ISOs](#installer-isos)
|
||||||
- [Live ISOs](#live-isos)
|
- [Live ISOs](#live-isos)
|
||||||
- [Container Images](#container-images)
|
- [Container Images](#container-images)
|
||||||
@@ -15,116 +17,194 @@ This guide covers building installer ISOs, live images, and container artifacts
|
|||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build an installer ISO for a specific host
|
# List all available artifacts
|
||||||
nix build git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git#installer-iso-nix-laptop1
|
nix flake show
|
||||||
|
|
||||||
# Result will be in result/iso/
|
# Build installer ISO for a specific host
|
||||||
|
nix build .#installer-iso-nix-laptop1
|
||||||
|
|
||||||
|
# Result is in result/iso/
|
||||||
ls -lh result/iso/
|
ls -lh result/iso/
|
||||||
```
|
```
|
||||||
|
|
||||||
## Available Artifacts
|
## Available Artifacts
|
||||||
|
|
||||||
List all available build outputs:
|
Athenix can build multiple artifact types for deployment:
|
||||||
|
|
||||||
```bash
|
| Type | Description | Location | Use Case |
|
||||||
nix flake show git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git
|
|------|-------------|----------|----------|
|
||||||
|
| `installer-iso-*` | Auto-install ISO | `result/iso/` | Install NixOS to disk |
|
||||||
|
| `iso-*` | Live ISO | `result/iso/` | Boot without installing |
|
||||||
|
| `ipxe-*` | PXE netboot | `result/` | Diskless netboot systems |
|
||||||
|
| `lxc-*` | LXC container | `result/tarball/` | LXC/Proxmox containers |
|
||||||
|
| `proxmox-*` | Proxmox VMA | `result/` | Proxmox VM templates |
|
||||||
|
|
||||||
|
Set artifact types per-host via `athenix.host.buildMethods` in `inventory.nix`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
nix-laptop = {
|
||||||
|
devices = 5;
|
||||||
|
overrides.athenix.host.buildMethods = [ "installer-iso" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
nix-lxc = {
|
||||||
|
devices.builder = {
|
||||||
|
athenix.host.buildMethods = [ "lxc" "proxmox" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
Common artifact types:
|
## Building Locally
|
||||||
|
|
||||||
| Artifact Type | Description | Example |
|
Build artifacts on your local machine:
|
||||||
|--------------|-------------|---------|
|
|
||||||
| `installer-iso-*` | Auto-install ISO that installs configuration to disk | `installer-iso-nix-laptop1` |
|
|
||||||
| `iso-*` | Live ISO (bootable without installation) | `iso-nix-ephemeral1` |
|
|
||||||
| `ipxe-*` | iPXE netboot artifacts (kernel, initrd, script) | `ipxe-nix-ephemeral1` |
|
|
||||||
| `lxc-*` | LXC container tarball | `lxc-nix-builder` |
|
|
||||||
| `proxmox-*` | Proxmox VMA archive | `proxmox-nix-builder` |
|
|
||||||
|
|
||||||
## Installer ISOs
|
|
||||||
|
|
||||||
Installer ISOs automatically install the NixOS configuration to disk on first boot.
|
|
||||||
|
|
||||||
### Building Locally
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build installer for a specific host
|
# Build installer ISO
|
||||||
nix build .#installer-iso-nix-laptop1
|
nix build .#installer-iso-nix-laptop1
|
||||||
|
|
||||||
# Result location
|
|
||||||
ls -lh result/iso/nixos-*.iso
|
|
||||||
|
|
||||||
# Copy to USB drive (replace /dev/sdX with your USB device)
|
|
||||||
sudo dd if=result/iso/nixos-*.iso of=/dev/sdX bs=4M status=progress
|
|
||||||
```
|
|
||||||
|
|
||||||
### Building from Gitea
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nix build git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git#installer-iso-nix-laptop1
|
|
||||||
```
|
|
||||||
|
|
||||||
### Using the Installer
|
|
||||||
|
|
||||||
1. Boot from the ISO
|
|
||||||
2. The system will automatically partition the disk and install NixOS
|
|
||||||
3. After installation completes, remove the USB drive and reboot
|
|
||||||
4. Log in with the configured user credentials
|
|
||||||
|
|
||||||
**Note:** The installer will **erase all data** on the target disk specified in `athenix.host.filesystem.device`.
|
|
||||||
|
|
||||||
## Live ISOs
|
|
||||||
|
|
||||||
Live ISOs boot into a temporary system without installing to disk. Useful for:
|
|
||||||
- Testing configurations
|
|
||||||
- Recovery operations
|
|
||||||
- Ephemeral/stateless systems
|
|
||||||
|
|
||||||
### Building Live ISOs
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Build live ISO
|
# Build live ISO
|
||||||
nix build .#iso-nix-ephemeral1
|
nix build .#iso-nix-ephemeral1
|
||||||
|
|
||||||
# Result location
|
# Build LXC container
|
||||||
ls -lh result/iso/nixos-*.iso
|
nix build .#lxc-nix-builder
|
||||||
|
|
||||||
|
# Build all available outputs
|
||||||
|
nix build .#
|
||||||
```
|
```
|
||||||
|
|
||||||
### Stateless Kiosk Systems
|
**Result locations:**
|
||||||
|
- ISOs: `result/iso/nixos-*.iso`
|
||||||
|
- LXC: `result/tarball/nixos-*.tar.xz`
|
||||||
|
- Proxmox: `result/`
|
||||||
|
- iPXE: `result/` (kernel, initrd, script)
|
||||||
|
|
||||||
For PXE netboot kiosks, use the `ipxe-*` artifacts:
|
### Build Specific Host
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build iPXE artifacts
|
# Get list of all hosts
|
||||||
nix build .#ipxe-nix-ephemeral1
|
nix eval .#nixosConfigurations --apply builtins.attrNames
|
||||||
|
|
||||||
# Result contains:
|
# Build specific host
|
||||||
# - bzImage (kernel)
|
nix build .#nixosConfigurations.nix-laptop1.config.system.build.toplevel
|
||||||
# - initrd (initial ramdisk)
|
```
|
||||||
# - netboot.ipxe (iPXE script)
|
|
||||||
ls -lh result/
|
## Building from Remote
|
||||||
|
|
||||||
|
Build from the Gitea repository without cloning:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build installer ISO
|
||||||
|
nix build git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git#installer-iso-nix-laptop1
|
||||||
|
|
||||||
|
# Build LXC container
|
||||||
|
nix build git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git#lxc-nix-builder
|
||||||
|
|
||||||
|
# Use specific branch or revision
|
||||||
|
nix build git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git?ref=develop#installer-iso-nix-laptop1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installer ISOs
|
||||||
|
|
||||||
|
Installer ISOs automatically partition and install NixOS on first boot.
|
||||||
|
|
||||||
|
### Building
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix build .#installer-iso-nix-laptop1
|
||||||
|
ls -lh result/iso/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Burning to USB
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Find USB device (be careful!)
|
||||||
|
lsblk
|
||||||
|
|
||||||
|
# Burn ISO to USB (replace sdX with your device)
|
||||||
|
sudo dd if=result/iso/nixos-*.iso of=/dev/sdX bs=4M status=progress
|
||||||
|
|
||||||
|
# Sync and eject
|
||||||
|
sudo sync && sudo eject /dev/sdX
|
||||||
|
```
|
||||||
|
|
||||||
|
### Installation Process
|
||||||
|
|
||||||
|
1. Boot from the USB drive
|
||||||
|
2. System automatically boots into installer
|
||||||
|
3. Installer partitions disk according to `athenix.host.filesystem`
|
||||||
|
4. NixOS is installed and configured
|
||||||
|
5. System reboots automatically
|
||||||
|
6. Log in with configured user
|
||||||
|
|
||||||
|
**Note:** Installer will erase all data on the target disk specified in `athenix.host.filesystem.device`.
|
||||||
|
|
||||||
|
### Installer Configuration
|
||||||
|
|
||||||
|
Customize installer via host configuration:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
nix-laptop = {
|
||||||
|
devices = 5;
|
||||||
|
overrides = {
|
||||||
|
athenix.host.filesystem.device = "/dev/nvme0n1";
|
||||||
|
athenix.host.filesystem.swapSize = "32G";
|
||||||
|
athenix.host.buildMethods = [ "installer-iso" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Live ISOs
|
||||||
|
|
||||||
|
Live ISOs boot into a temporary system without installing to disk.
|
||||||
|
|
||||||
|
### Building
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix build .#iso-nix-ephemeral1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
Live ISOs are useful for:
|
||||||
|
- Testing configurations before installation
|
||||||
|
- Recovery operations
|
||||||
|
- Ephemeral/stateless systems
|
||||||
|
- Booting in kiosk mode
|
||||||
|
|
||||||
|
### Customizing Live ISO
|
||||||
|
|
||||||
|
```nix
|
||||||
|
nix-ephemeral = {
|
||||||
|
devices.live = {
|
||||||
|
athenix.sw.type = "stateless-kiosk";
|
||||||
|
athenix.sw.kioskUrl = "https://dashboard.example.com";
|
||||||
|
athenix.host.buildMethods = [ "iso" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## Container Images
|
## Container Images
|
||||||
|
|
||||||
### LXC Containers
|
### LXC Containers
|
||||||
|
|
||||||
Build LXC container tarballs for Proxmox or other LXC hosts:
|
Build LXC container tarballs for Proxmox or standalone LXC:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build LXC tarball
|
|
||||||
nix build .#lxc-nix-builder
|
nix build .#lxc-nix-builder
|
||||||
|
ls -lh result/tarball/
|
||||||
# Result location
|
|
||||||
ls -lh result/tarball/nixos-*.tar.xz
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Importing to Proxmox:**
|
#### Importing to Proxmox
|
||||||
|
|
||||||
|
1. Copy tarball to Proxmox host:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Copy tarball to Proxmox host
|
|
||||||
scp result/tarball/nixos-*.tar.xz root@proxmox:/var/lib/vz/template/cache/
|
scp result/tarball/nixos-*.tar.xz root@proxmox:/var/lib/vz/template/cache/
|
||||||
|
```
|
||||||
|
|
||||||
# Create container from Proxmox CLI
|
2. Create container:
|
||||||
|
|
||||||
|
```bash
|
||||||
pct create 100 local:vztmpl/nixos-*.tar.xz \
|
pct create 100 local:vztmpl/nixos-*.tar.xz \
|
||||||
--hostname nix-builder \
|
--hostname nix-builder \
|
||||||
--memory 4096 \
|
--memory 4096 \
|
||||||
@@ -132,25 +212,59 @@ pct create 100 local:vztmpl/nixos-*.tar.xz \
|
|||||||
--net0 name=eth0,bridge=vmbr0,ip=dhcp
|
--net0 name=eth0,bridge=vmbr0,ip=dhcp
|
||||||
```
|
```
|
||||||
|
|
||||||
See [installer/PROXMOX_LXC.md](../installer/PROXMOX_LXC.md) for detailed Proxmox deployment instructions.
|
3. Start and log in:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pct start 100
|
||||||
|
pct shell 100
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Proxmox Integration
|
||||||
|
|
||||||
|
For detailed Proxmox deployment instructions, see [installer/PROXMOX_LXC.md](../installer/PROXMOX_LXC.md).
|
||||||
|
|
||||||
### Proxmox VMA
|
### Proxmox VMA
|
||||||
|
|
||||||
Build Proxmox-specific VMA archives:
|
Build Proxmox-specific VMA archives:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build Proxmox VMA
|
|
||||||
nix build .#proxmox-nix-builder
|
nix build .#proxmox-nix-builder
|
||||||
|
|
||||||
# Result location
|
|
||||||
ls -lh result/
|
ls -lh result/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
VMA files can be imported directly into Proxmox for rapid VM creation.
|
||||||
|
|
||||||
|
## iPXE / Network Boot
|
||||||
|
|
||||||
|
Build iPXE artifacts for diskless PXE boot systems:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix build .#ipxe-nix-ephemeral1
|
||||||
|
ls -lh result/
|
||||||
|
```
|
||||||
|
|
||||||
|
Artifacts include:
|
||||||
|
- `bzImage` - Linux kernel
|
||||||
|
- `initrd` - Initial ramdisk
|
||||||
|
- `netboot.ipxe` - iPXE boot script
|
||||||
|
|
||||||
|
### iPXE Setup
|
||||||
|
|
||||||
|
Configure your PXE server to boot from these artifacts:
|
||||||
|
|
||||||
|
```ipxe
|
||||||
|
kernel tftp://server/bzImage
|
||||||
|
initrd tftp://server/initrd
|
||||||
|
boot
|
||||||
|
```
|
||||||
|
|
||||||
|
See [installer/PROXMOX_LXC.md](../installer/PROXMOX_LXC.md) for detailed network boot setup.
|
||||||
|
|
||||||
## Remote Builders
|
## Remote Builders
|
||||||
|
|
||||||
Speed up builds by offloading to build servers.
|
Speed up builds by offloading to build servers.
|
||||||
|
|
||||||
### One-Time Remote Build
|
### One-Time Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
nix build .#installer-iso-nix-laptop1 \
|
nix build .#installer-iso-nix-laptop1 \
|
||||||
@@ -159,7 +273,7 @@ nix build .#installer-iso-nix-laptop1 \
|
|||||||
|
|
||||||
### Persistent Configuration
|
### Persistent Configuration
|
||||||
|
|
||||||
Add to `~/.config/nix/nix.conf` or `/etc/nix/nix.conf`:
|
Add to `~/.config/nix/nix.conf`:
|
||||||
|
|
||||||
```conf
|
```conf
|
||||||
builders = ssh://engr-ugaif@nix-builder x86_64-linux
|
builders = ssh://engr-ugaif@nix-builder x86_64-linux
|
||||||
@@ -171,12 +285,12 @@ Then build normally:
|
|||||||
nix build .#installer-iso-nix-laptop1
|
nix build .#installer-iso-nix-laptop1
|
||||||
```
|
```
|
||||||
|
|
||||||
### SSH Key Setup
|
### SSH Setup
|
||||||
|
|
||||||
For remote builders, ensure SSH keys are configured:
|
Ensure SSH is configured for the builder:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Generate SSH key if needed
|
# Generate key if needed
|
||||||
ssh-keygen -t ed25519
|
ssh-keygen -t ed25519
|
||||||
|
|
||||||
# Copy to builder
|
# Copy to builder
|
||||||
@@ -188,77 +302,86 @@ ssh engr-ugaif@nix-builder
|
|||||||
|
|
||||||
### Multiple Builders
|
### Multiple Builders
|
||||||
|
|
||||||
Configure multiple build servers:
|
|
||||||
|
|
||||||
```conf
|
```conf
|
||||||
builders = ssh://engr-ugaif@nix-builder x86_64-linux ; ssh://engr-ugaif@nix-builder2 x86_64-linux
|
builders = ssh://engr-ugaif@nix-builder1 x86_64-linux ; ssh://engr-ugaif@nix-builder2 x86_64-linux
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Automatic Remote Build (Tablets)
|
||||||
|
|
||||||
|
Surface tablets are configured to automatically use remote builders:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.sw.remoteBuild = {
|
||||||
|
enable = true;
|
||||||
|
hosts = [ "nix-builder" ];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
This speeds up builds on resource-constrained devices.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### Build Errors
|
### Build Errors
|
||||||
|
|
||||||
**Check configuration validity:**
|
Get detailed error information:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
# Verbose error traces
|
||||||
|
nix build .#installer-iso-nix-laptop1 --show-trace
|
||||||
|
|
||||||
|
# Check all configurations first
|
||||||
nix flake check --show-trace
|
nix flake check --show-trace
|
||||||
```
|
```
|
||||||
|
|
||||||
**Test specific host build:**
|
|
||||||
```bash
|
|
||||||
nix build .#nixosConfigurations.nix-laptop1.config.system.build.toplevel
|
|
||||||
```
|
|
||||||
|
|
||||||
### Remote Builder Issues
|
|
||||||
|
|
||||||
**Test SSH access:**
|
|
||||||
```bash
|
|
||||||
ssh engr-ugaif@nix-builder
|
|
||||||
```
|
|
||||||
|
|
||||||
**Check builder disk space:**
|
|
||||||
```bash
|
|
||||||
ssh engr-ugaif@nix-builder df -h
|
|
||||||
```
|
|
||||||
|
|
||||||
**Temporarily disable remote builds:**
|
|
||||||
|
|
||||||
In `inventory.nix`:
|
|
||||||
```nix
|
|
||||||
athenix.sw.remoteBuild.enable = false;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Out of Disk Space
|
### Out of Disk Space
|
||||||
|
|
||||||
**Clean up Nix store:**
|
|
||||||
```bash
|
```bash
|
||||||
|
# Clean up Nix store
|
||||||
nix-collect-garbage -d
|
nix-collect-garbage -d
|
||||||
|
|
||||||
|
# Optimize store
|
||||||
nix store optimise
|
nix store optimise
|
||||||
```
|
```
|
||||||
|
|
||||||
**Check space:**
|
### Build Hangs
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
df -h /nix
|
# List processes
|
||||||
|
ps aux | grep nix
|
||||||
|
|
||||||
|
# Cancel build
|
||||||
|
Ctrl+C
|
||||||
```
|
```
|
||||||
|
|
||||||
### ISO Won't Boot
|
### Finding Artifact Outputs
|
||||||
|
|
||||||
**Verify ISO integrity:**
|
|
||||||
```bash
|
```bash
|
||||||
sha256sum result/iso/nixos-*.iso
|
# List all buildable outputs
|
||||||
|
nix flake show
|
||||||
|
|
||||||
|
# Check specific output exists
|
||||||
|
nix flake show | grep installer-iso-nix-laptop1
|
||||||
|
|
||||||
|
# Get path to output
|
||||||
|
nix build .#installer-iso-nix-laptop1 --no-link
|
||||||
```
|
```
|
||||||
|
|
||||||
**Check USB write:**
|
### Build Not Creating Expected File
|
||||||
```bash
|
|
||||||
# Use correct block size and sync
|
|
||||||
sudo dd if=result/iso/nixos-*.iso of=/dev/sdX bs=4M status=progress && sync
|
|
||||||
```
|
|
||||||
|
|
||||||
**Try alternative boot mode:**
|
```bash
|
||||||
- UEFI systems: Try legacy BIOS mode
|
# Check build log
|
||||||
- Legacy BIOS: Try UEFI mode
|
nix build .#installer-iso-nix-laptop1 -L
|
||||||
|
|
||||||
|
# Check what's in result
|
||||||
|
ls -la result/
|
||||||
|
|
||||||
|
# Inspect NixOS build structure
|
||||||
|
nix build .#nixosConfigurations.nix-laptop1.config.system.build.toplevel -L
|
||||||
|
```
|
||||||
|
|
||||||
## See Also
|
## See Also
|
||||||
|
|
||||||
|
- [DEVELOPMENT.md](DEVELOPMENT.md) - Development workflow
|
||||||
|
- [INVENTORY.md](INVENTORY.md) - Host configuration
|
||||||
|
- [installer/PROXMOX_LXC.md](../installer/PROXMOX_LXC.md) - Proxmox deployment
|
||||||
- [README.md](../README.md) - Main documentation
|
- [README.md](../README.md) - Main documentation
|
||||||
- [INVENTORY.md](INVENTORY.md) - Host configuration guide
|
|
||||||
- [installer/PROXMOX_LXC.md](../installer/PROXMOX_LXC.md) - Proxmox deployment guide
|
|
||||||
|
|||||||
@@ -1,464 +1,456 @@
|
|||||||
# Development Guide
|
# Development Guide
|
||||||
|
|
||||||
This guide covers development workflows for maintaining and extending the nixos-systems repository.
|
Comprehensive guide for maintaining and extending Athenix.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
- [Prerequisites](#prerequisites)
|
- [Prerequisites](#prerequisites)
|
||||||
|
- [Development Workflow](#development-workflow)
|
||||||
- [Testing Changes](#testing-changes)
|
- [Testing Changes](#testing-changes)
|
||||||
- [Continuous Integration](#continuous-integration)
|
- [Continuous Integration](#continuous-integration)
|
||||||
- [System Rebuilds](#system-rebuilds)
|
- [Common Tasks](#common-tasks)
|
||||||
- [Updating Dependencies](#updating-dependencies)
|
- [Debugging](#debugging)
|
||||||
- [Adding Packages](#adding-packages)
|
- [Troubleshooting](#troubleshooting)
|
||||||
- [Python Development](#python-development)
|
|
||||||
- [Contributing](#contributing)
|
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
Install Nix with flakes support:
|
### Install Nix with Flakes
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Recommended: Determinate Systems installer (includes flakes)
|
# Recommended: Determinate Systems installer
|
||||||
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
|
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
|
||||||
|
|
||||||
# Alternative: Official installer (requires enabling flakes manually)
|
# Or official installer
|
||||||
sh <(curl -L https://nixos.org/nix/install) --daemon
|
sh <(curl -L https://nixos.org/nix/install) --daemon
|
||||||
|
|
||||||
|
# Enable flakes in existing installation
|
||||||
|
mkdir -p ~/.config/nix
|
||||||
|
echo 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
### Clone Repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git
|
||||||
|
cd athenix
|
||||||
|
|
||||||
|
# Optional: enable direnv for automatic Nix environment
|
||||||
|
direnv allow
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
### Making Changes
|
||||||
|
|
||||||
|
1. **Edit configuration files** - Modify `inventory.nix`, `users.nix`, or host/software config
|
||||||
|
|
||||||
|
2. **Validate** - Check syntax and configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix flake check
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Format code** - Apply consistent formatting
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix fmt
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Test** - Build specific artifacts or configurations
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test specific host
|
||||||
|
nix build .#nixosConfigurations.nix-laptop1.config.system.build.toplevel
|
||||||
|
|
||||||
|
# Or build an artifact
|
||||||
|
nix build .#installer-iso-nix-laptop1
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Commit and push**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .
|
||||||
|
git commit -m "Brief description of changes"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example: Adding a New User
|
||||||
|
|
||||||
|
1. Define user in `users.nix`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.users.newuser = {
|
||||||
|
description = "New User";
|
||||||
|
extraGroups = [ "wheel" "networkmanager" ];
|
||||||
|
shell = pkgs.zsh;
|
||||||
|
hashedPassword = "..."; # mkpasswd -m sha-512
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Enable on fleet in `inventory.nix`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
nix-laptop = {
|
||||||
|
devices = 5;
|
||||||
|
overrides.athenix.users.newuser.enable = true;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Validate and commit:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix flake check
|
||||||
|
nix fmt
|
||||||
|
git add . && git commit -m "Add newuser account"
|
||||||
|
git push
|
||||||
```
|
```
|
||||||
|
|
||||||
## Testing Changes
|
## Testing Changes
|
||||||
|
|
||||||
Always test configuration changes before committing.
|
### Validate Configuration Syntax
|
||||||
|
|
||||||
### Validate All Configurations
|
Always run before committing:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check all configurations build correctly
|
|
||||||
nix flake check
|
nix flake check
|
||||||
|
```
|
||||||
|
|
||||||
# Check with verbose error traces
|
Shows any configuration errors across all ~50+ hosts. Output:
|
||||||
nix flake check --show-trace
|
|
||||||
|
```
|
||||||
|
checking 50 configurations...
|
||||||
|
✓ All checks passed
|
||||||
```
|
```
|
||||||
|
|
||||||
### Test Specific Host Build
|
### Test Specific Host Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Build a specific host's configuration
|
# Build specific host (shows if config actually compiles)
|
||||||
nix build .#nixosConfigurations.nix-laptop1.config.system.build.toplevel
|
nix build .#nixosConfigurations.nix-laptop1.config.system.build.toplevel
|
||||||
|
|
||||||
# Build installer for specific host
|
# Shorter form
|
||||||
nix build .#installer-iso-nix-laptop1
|
nix build .#nixosConfigurations.nix-laptop1.config.system.build.toplevel -L
|
||||||
```
|
```
|
||||||
|
|
||||||
### Test Local Changes
|
### Test Installer Build
|
||||||
|
|
||||||
If you're on a NixOS system managed by this flake:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Test changes without committing (temporary, doesn't survive reboot)
|
# Test that installer ISO builds
|
||||||
|
nix build .#installer-iso-nix-laptop1 -L
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test on Running NixOS System
|
||||||
|
|
||||||
|
If you're on a NixOS system managed by Athenix:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test changes temporarily (won't survive reboot)
|
||||||
sudo nixos-rebuild test --flake .
|
sudo nixos-rebuild test --flake .
|
||||||
|
|
||||||
# Apply and switch to new configuration
|
# Apply and switch (persistent)
|
||||||
sudo nixos-rebuild switch --flake .
|
sudo nixos-rebuild switch --flake .
|
||||||
|
|
||||||
# Build without switching
|
# Build without switching
|
||||||
sudo nixos-rebuild build --flake .
|
sudo nixos-rebuild build --flake .
|
||||||
|
|
||||||
|
# Show what will change
|
||||||
|
sudo nixos-rebuild dry-activate --flake .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rollback
|
||||||
|
|
||||||
|
If a build breaks your system:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List recent generations
|
||||||
|
nix-env --list-generations
|
||||||
|
|
||||||
|
# Rollback to previous generation
|
||||||
|
nix-env --rollback
|
||||||
|
|
||||||
|
# Or switch to specific generation
|
||||||
|
nix-env --switch-generation 42
|
||||||
```
|
```
|
||||||
|
|
||||||
## Continuous Integration
|
## Continuous Integration
|
||||||
|
|
||||||
The repository uses Gitea Actions for automated testing and validation. CI jobs run on the self-hosted `nix-builder` machine.
|
### CI Pipeline
|
||||||
|
|
||||||
### CI Workflow
|
All pushes and pull requests trigger automated tests on the self-hosted `nix-builder`:
|
||||||
|
|
||||||
All pull requests and pushes to main trigger the CI pipeline, which includes:
|
1. **Flake Check** - `nix flake check` validates all 50+ configurations
|
||||||
|
2. **Format Check** - Verifies code formatted with `nix fmt`
|
||||||
1. **Flake Check** - Validates all NixOS configurations
|
3. **Build Key Hosts** - Builds `nix-builder`, `nix-laptop1`, `nix-desktop1`
|
||||||
- Runs `nix flake check` to ensure all systems build correctly
|
4. **Build Artifacts** - Tests `lxc-nix-builder` and `installer-iso-nix-laptop1`
|
||||||
- Catches configuration errors early
|
|
||||||
|
|
||||||
2. **Format Check** - Ensures code formatting consistency
|
|
||||||
- Verifies code is formatted with `nix fmt`
|
|
||||||
- Automatically fails if formatting is incorrect
|
|
||||||
|
|
||||||
3. **Build Key Configurations** - Tests critical system builds
|
|
||||||
- Builds: `nix-builder`, `nix-laptop1`, `nix-desktop1`
|
|
||||||
- Ensures core configurations compile successfully
|
|
||||||
|
|
||||||
4. **Build Artifacts** - Validates installer and container builds
|
|
||||||
- Builds: `lxc-nix-builder`, `installer-iso-nix-laptop1`
|
|
||||||
- Verifies deployment artifacts are buildable
|
|
||||||
|
|
||||||
### Viewing CI Status
|
### Viewing CI Status
|
||||||
|
|
||||||
Check the CI status badge at the top of the README or view detailed logs:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# View workflow status
|
# Web interface
|
||||||
https://git.factory.uga.edu/UGA-Innovation-Factory/athenix/actions
|
https://git.factory.uga.edu/UGA-Innovation-Factory/athenix/actions
|
||||||
|
|
||||||
|
# Or check locally
|
||||||
|
git log --oneline -n 5
|
||||||
|
# Look for ✓ or ✗ next to commits
|
||||||
```
|
```
|
||||||
|
|
||||||
### Running CI Checks Locally
|
### Running CI Checks Locally
|
||||||
|
|
||||||
Before pushing changes, run the same checks that CI performs:
|
Test before pushing:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run all checks
|
# Flake check
|
||||||
nix flake check --show-trace
|
nix flake check --show-trace
|
||||||
|
|
||||||
# Check formatting
|
# Format check
|
||||||
nix fmt
|
nix fmt --check
|
||||||
git diff --exit-code # Should return no changes
|
|
||||||
|
|
||||||
# Build specific configuration
|
# Format code
|
||||||
nix build .#nixosConfigurations.nix-builder.config.system.build.toplevel
|
nix fmt **/*.nix
|
||||||
|
|
||||||
# Build artifacts
|
# Build key configurations
|
||||||
nix build .#lxc-nix-builder
|
nix build .#nixosConfigurations.nix-builder.config.system.build.toplevel -L
|
||||||
|
nix build .#nixosConfigurations.nix-laptop1.config.system.build.toplevel -L
|
||||||
```
|
```
|
||||||
|
|
||||||
### Self-Hosted Runner
|
## Common Tasks
|
||||||
|
|
||||||
CI jobs run on the `nix-builder` host as a self-hosted Gitea Actions runner. This provides:
|
### Adding a New Host
|
||||||
|
|
||||||
- Native Nix environment without installation overhead
|
Edit `inventory.nix`:
|
||||||
- Access to local Nix store for faster builds
|
|
||||||
- Consistent build environment matching deployment targets
|
|
||||||
- Direct access to build caching infrastructure
|
|
||||||
|
|
||||||
#### Setting Up the Gitea Actions Runner
|
|
||||||
|
|
||||||
The nix-builder host is configured with a Gitea Actions self-hosted runner in `inventory.nix`. To complete the setup:
|
|
||||||
|
|
||||||
1. **Generate a Gitea Runner Token**:
|
|
||||||
- Go to https://git.factory.uga.edu/UGA-Innovation-Factory/athenix/settings/actions/runners
|
|
||||||
- Click "Create new Runner"
|
|
||||||
- Copy the registration token
|
|
||||||
|
|
||||||
2. **Create the token file on nix-builder**:
|
|
||||||
```bash
|
|
||||||
ssh engr-ugaif@nix-builder
|
|
||||||
echo "YOUR_TOKEN_HERE" | sudo tee /var/lib/gitea-runner-token > /dev/null
|
|
||||||
sudo chmod 600 /var/lib/gitea-runner-token
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Rebuild the system** to start the runner:
|
|
||||||
```bash
|
|
||||||
sudo nixos-rebuild switch --flake git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git#nix-builder
|
|
||||||
```
|
|
||||||
|
|
||||||
4. **Verify the runner is registered**:
|
|
||||||
- Check https://git.factory.uga.edu/UGA-Innovation-Factory/athenix/settings/actions/runners
|
|
||||||
- The runner should appear with the `nix-builder` label
|
|
||||||
|
|
||||||
The runner service is configured in the nix-builder device configuration and will automatically:
|
|
||||||
- Register with the repository on first start
|
|
||||||
- Use the `nix-builder` label for workflow targeting
|
|
||||||
- Run as the `engr-ugaif` user
|
|
||||||
- Store work in `/var/lib/gitea-runner`
|
|
||||||
|
|
||||||
### Troubleshooting CI Failures
|
|
||||||
|
|
||||||
If CI fails:
|
|
||||||
|
|
||||||
1. **Check the error logs** in the Gitea Actions tab
|
|
||||||
2. **Run the same command locally** to reproduce the issue
|
|
||||||
3. **Use `--show-trace`** for detailed error information
|
|
||||||
4. **Verify formatting** with `nix fmt` if format check fails
|
|
||||||
5. **Check for external dependencies** that might be unavailable
|
|
||||||
|
|
||||||
Common CI issues:
|
|
||||||
|
|
||||||
- **Flake check fails**: Configuration error in a host definition
|
|
||||||
- **Format check fails**: Run `nix fmt` locally and commit changes
|
|
||||||
- **Build fails**: Missing dependency or syntax error in Nix expressions
|
|
||||||
- **Cache issues**: Usually self-resolving; can retry the workflow
|
|
||||||
|
|
||||||
## System Rebuilds
|
|
||||||
|
|
||||||
### From Local Directory
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Rebuild current host from local directory
|
|
||||||
sudo nixos-rebuild switch --flake .
|
|
||||||
|
|
||||||
# Rebuild specific host
|
|
||||||
sudo nixos-rebuild switch --flake .#nix-laptop1
|
|
||||||
|
|
||||||
# Test without switching (temporary, doesn't persist reboot)
|
|
||||||
sudo nixos-rebuild test --flake .#nix-laptop1
|
|
||||||
|
|
||||||
# Build a new generation without activating it
|
|
||||||
sudo nixos-rebuild build --flake .
|
|
||||||
```
|
|
||||||
|
|
||||||
### From GitHub
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Rebuild from GitHub main branch
|
|
||||||
sudo nixos-rebuild switch --flake git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git
|
|
||||||
|
|
||||||
# Use --impure for external user configurations with fetchGit
|
|
||||||
sudo nixos-rebuild switch --flake git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git --impure
|
|
||||||
|
|
||||||
# Rebuild specific host from GitHub
|
|
||||||
sudo nixos-rebuild switch --flake git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git#nix-laptop1
|
|
||||||
```
|
|
||||||
|
|
||||||
### Boot into Previous Generation
|
|
||||||
|
|
||||||
If something breaks:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# List generations
|
|
||||||
sudo nixos-rebuild list-generations
|
|
||||||
|
|
||||||
# Rollback to previous generation
|
|
||||||
sudo nixos-rebuild switch --rollback
|
|
||||||
|
|
||||||
# Or select specific generation at boot (GRUB menu)
|
|
||||||
# Reboot and select "NixOS - Configuration X" from boot menu
|
|
||||||
```
|
|
||||||
|
|
||||||
## Updating Dependencies
|
|
||||||
|
|
||||||
### Update All Inputs
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Update all flake inputs (nixpkgs, home-manager, etc.)
|
|
||||||
nix flake update
|
|
||||||
|
|
||||||
# Review changes
|
|
||||||
git diff flake.lock
|
|
||||||
|
|
||||||
# Test the updates
|
|
||||||
nix flake check
|
|
||||||
|
|
||||||
# Commit if successful
|
|
||||||
git add flake.lock
|
|
||||||
git commit -m "Update flake inputs"
|
|
||||||
git push
|
|
||||||
```
|
|
||||||
|
|
||||||
### Update Specific Input
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Update only nixpkgs
|
|
||||||
nix flake lock --update-input nixpkgs
|
|
||||||
|
|
||||||
# Update home-manager
|
|
||||||
nix flake lock --update-input home-manager
|
|
||||||
|
|
||||||
# Update multiple specific inputs
|
|
||||||
nix flake lock --update-input nixpkgs --update-input home-manager
|
|
||||||
```
|
|
||||||
|
|
||||||
### Check for Security Updates
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# After updating, check for known vulnerabilities
|
|
||||||
nix flake check
|
|
||||||
|
|
||||||
# Review nixpkgs changelog
|
|
||||||
git log HEAD..nixpkgs/nixos-25.11 --oneline | head -20
|
|
||||||
```
|
|
||||||
|
|
||||||
## Adding Packages
|
|
||||||
|
|
||||||
### System-Wide Packages by Type
|
|
||||||
|
|
||||||
Add packages based on system type:
|
|
||||||
|
|
||||||
**Desktop systems:**
|
|
||||||
```bash
|
|
||||||
# Edit sw/desktop/programs.nix
|
|
||||||
vim sw/desktop/programs.nix
|
|
||||||
```
|
|
||||||
|
|
||||||
**Tablet kiosks:**
|
|
||||||
```bash
|
|
||||||
# Edit sw/tablet-kiosk/programs.nix
|
|
||||||
vim sw/tablet-kiosk/programs.nix
|
|
||||||
```
|
|
||||||
|
|
||||||
**Headless systems:**
|
|
||||||
```bash
|
|
||||||
# Edit sw/headless/programs.nix
|
|
||||||
vim sw/headless/programs.nix
|
|
||||||
```
|
|
||||||
|
|
||||||
### Packages for Specific Hosts
|
|
||||||
|
|
||||||
Add to `athenix.sw.extraPackages` in `inventory.nix`:
|
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
nix-laptop = {
|
nix-surface = {
|
||||||
devices = 2;
|
devices = 3; # Creates nix-surface1, nix-surface2, nix-surface3
|
||||||
overrides = {
|
overrides = {
|
||||||
athenix.sw.extraPackages = with pkgs; [
|
athenix.sw.type = "tablet-kiosk";
|
||||||
vim
|
athenix.sw.kioskUrl = "https://dashboard.example.com";
|
||||||
docker
|
|
||||||
kubernetes-helm
|
|
||||||
];
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### User-Specific Packages
|
Test:
|
||||||
|
|
||||||
Add to user's home-manager configuration in their external `user.nix`:
|
|
||||||
|
|
||||||
```nix
|
|
||||||
# In external user.nix
|
|
||||||
home.packages = with pkgs; [
|
|
||||||
ripgrep
|
|
||||||
fd
|
|
||||||
bat
|
|
||||||
];
|
|
||||||
```
|
|
||||||
|
|
||||||
### Search for Packages
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Search nixpkgs
|
|
||||||
nix search nixpkgs firefox
|
|
||||||
nix search nixpkgs python3
|
|
||||||
|
|
||||||
# Show package details
|
|
||||||
nix eval nixpkgs#firefox.meta.description
|
|
||||||
```
|
|
||||||
|
|
||||||
## Python Development
|
|
||||||
|
|
||||||
All systems include modern Python tools: `pixi` and `uv`.
|
|
||||||
|
|
||||||
### Pixi (Recommended for Projects)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Initialize new project
|
|
||||||
pixi init my-project
|
|
||||||
cd my-project
|
|
||||||
|
|
||||||
# Add dependencies
|
|
||||||
pixi add pandas numpy matplotlib jupyter
|
|
||||||
|
|
||||||
# Run Python
|
|
||||||
pixi run python
|
|
||||||
|
|
||||||
# Run Jupyter
|
|
||||||
pixi run jupyter notebook
|
|
||||||
|
|
||||||
# Run scripts
|
|
||||||
pixi run python script.py
|
|
||||||
|
|
||||||
# Shell with dependencies
|
|
||||||
pixi shell
|
|
||||||
```
|
|
||||||
|
|
||||||
### uv (Quick Virtual Environments)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Create virtual environment
|
|
||||||
uv venv
|
|
||||||
|
|
||||||
# Activate
|
|
||||||
source .venv/bin/activate
|
|
||||||
|
|
||||||
# Install packages
|
|
||||||
uv pip install requests pandas
|
|
||||||
|
|
||||||
# Freeze requirements
|
|
||||||
uv pip freeze > requirements.txt
|
|
||||||
|
|
||||||
# Install from requirements
|
|
||||||
uv pip install -r requirements.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
### System Python
|
|
||||||
|
|
||||||
Python development tools are configured in `sw/python.nix` and can be controlled via:
|
|
||||||
|
|
||||||
```nix
|
|
||||||
athenix.sw.python.enable = true; # Default: enabled
|
|
||||||
```
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
### Code Style
|
|
||||||
|
|
||||||
- Run formatter before committing: `nix fmt`
|
|
||||||
- Follow existing code structure and conventions
|
|
||||||
- Add comments for complex logic
|
|
||||||
- Use the `athenix.*` namespace for all custom options
|
|
||||||
|
|
||||||
### Testing Workflow
|
|
||||||
|
|
||||||
1. Make changes
|
|
||||||
2. Run formatter: `nix fmt`
|
|
||||||
3. Test locally: `nix flake check`
|
|
||||||
4. Test specific builds if needed
|
|
||||||
5. Commit changes
|
|
||||||
6. Push to GitHub
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Full workflow
|
|
||||||
nix fmt
|
|
||||||
nix flake check
|
nix flake check
|
||||||
git add .
|
nix build .#installer-iso-nix-surface1 -L
|
||||||
git commit -m "Description of changes"
|
|
||||||
git push
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Documentation
|
### Modifying Software Configuration
|
||||||
|
|
||||||
Update relevant documentation when making changes:
|
Edit appropriate file in `sw/`:
|
||||||
|
|
||||||
- `README.md` - Overview and quick start
|
|
||||||
- `docs/INVENTORY.md` - Inventory configuration
|
|
||||||
- `docs/NAMESPACE.md` - Configuration options
|
|
||||||
- `USER_CONFIGURATION.md` - User management
|
|
||||||
- `EXTERNAL_MODULES.md` - External modules
|
|
||||||
|
|
||||||
### Creating Issues
|
|
||||||
|
|
||||||
When reporting bugs or requesting features:
|
|
||||||
|
|
||||||
1. Check existing issues first
|
|
||||||
2. Provide clear description
|
|
||||||
3. Include error messages and traces
|
|
||||||
4. Specify which hosts are affected
|
|
||||||
5. Include `flake.lock` info if relevant
|
|
||||||
|
|
||||||
## Useful Commands
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Show all available outputs
|
# Desktop software
|
||||||
nix flake show
|
vim sw/desktop/programs.nix
|
||||||
|
|
||||||
# Evaluate specific option
|
# Or for all systems
|
||||||
nix eval .#nixosConfigurations.nix-laptop1.config.networking.hostName
|
vim sw/default.nix
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `athenix.sw.extraPackages` for host-specific additions:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
nix-laptop = {
|
||||||
|
devices = 5;
|
||||||
|
overrides.athenix.sw.extraPackages = with pkgs; [ special-tool ];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding a System Type
|
||||||
|
|
||||||
|
Create new type in `sw/`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p sw/my-type
|
||||||
|
touch sw/my-type/{default.nix,programs.nix,services.nix}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then reference in `sw/default.nix`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./my-type/default.nix
|
||||||
|
# ... other types
|
||||||
|
];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using External Configurations
|
||||||
|
|
||||||
|
For user dotfiles:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# users.nix
|
||||||
|
athenix.users.myuser.external = builtins.fetchGit {
|
||||||
|
url = "https://git.factory.uga.edu/username/dotfiles";
|
||||||
|
rev = "abc123..."; # Pin to commit
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
For system config:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# inventory.nix
|
||||||
|
nix-lxc = {
|
||||||
|
devices."server".external = builtins.fetchGit {
|
||||||
|
url = "https://git.factory.uga.edu/org/server-config";
|
||||||
|
rev = "abc123...";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Updating Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Update all flake inputs
|
||||||
|
nix flake update
|
||||||
|
|
||||||
|
# Update specific input
|
||||||
|
nix flake update nixpkgs
|
||||||
|
|
||||||
|
# Show what changed
|
||||||
|
git diff flake.lock
|
||||||
|
|
||||||
|
# Test after update
|
||||||
|
nix flake check --show-trace
|
||||||
|
|
||||||
|
# If tests pass, commit
|
||||||
|
git add flake.lock && git commit -m "Update dependencies"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
### Verbose Output
|
||||||
|
|
||||||
|
Get detailed error messages:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Show full error traces
|
||||||
|
nix flake check --show-trace
|
||||||
|
|
||||||
|
# With maximum verbosity
|
||||||
|
nix build .#installer-iso-nix-laptop1 -vvv
|
||||||
|
|
||||||
|
# Show build log
|
||||||
|
nix build .#installer-iso-nix-laptop1 -L
|
||||||
|
```
|
||||||
|
|
||||||
|
### Inspect Configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Evaluate configuration for specific host
|
||||||
|
nix eval .#nixosConfigurations.nix-laptop1.config.athenix.sw --json
|
||||||
|
|
||||||
|
# Get all host names
|
||||||
|
nix eval .#nixosConfigurations --apply builtins.attrNames
|
||||||
|
|
||||||
|
# Check specific option
|
||||||
|
nix eval .#nixosConfigurations.nix-laptop1.config.users.users
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Module Loading
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Evaluate specific module
|
||||||
|
nix-build -A nixosConfigurations.nix-laptop1.config.system.build.toplevel
|
||||||
|
|
||||||
|
# Or with flakes
|
||||||
|
nix build .#nixosConfigurations.nix-laptop1.config.system.build.toplevel --verbose
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Derivation Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Show what dependencies a build needs
|
||||||
|
nix show-derivation .#installer-iso-nix-laptop1
|
||||||
|
|
||||||
|
# Or human-readable
|
||||||
|
nix build .#installer-iso-nix-laptop1 --dry-run
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Errors
|
||||||
|
|
||||||
|
#### "Evaluation error"
|
||||||
|
|
||||||
|
```
|
||||||
|
error: evaluation aborted with the following error message: '...'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution:** Check syntax in modified files. Use `nix fmt` and `nix flake check --show-trace`.
|
||||||
|
|
||||||
|
#### "Unknown variable" or "Option does not exist"
|
||||||
|
|
||||||
|
```
|
||||||
|
error: The option `athenix.xyz' does not exist.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution:** Check NAMESPACE.md for available options. Options must be in `athenix.*` namespace.
|
||||||
|
|
||||||
|
#### "Hash mismatch" (for external modules)
|
||||||
|
|
||||||
|
```
|
||||||
|
error: Hash mismatch in fetched input
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution:** Update the pin. For `builtins.fetchGit`, use actual commit hash. Or:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix flake update
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Build runs out of memory
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Reduce parallel jobs
|
||||||
|
nix build . --max-jobs 1
|
||||||
|
```
|
||||||
|
|
||||||
|
#### "No such file or directory" in build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check path exists
|
||||||
|
ls -la /path/to/file
|
||||||
|
|
||||||
|
# Or check relative to repo
|
||||||
|
ls -la sw/my-file.nix
|
||||||
|
```
|
||||||
|
|
||||||
|
### Helpful Diagnostics
|
||||||
|
|
||||||
|
```bash
|
||||||
# List all hosts
|
# List all hosts
|
||||||
nix eval .#nixosConfigurations --apply builtins.attrNames
|
nix eval .#nixosConfigurations --apply builtins.attrNames
|
||||||
|
|
||||||
# Check flake metadata
|
# Show flake structure
|
||||||
nix flake metadata
|
nix flake show | head -50
|
||||||
|
|
||||||
# Show evaluation trace
|
# Check Nix store size
|
||||||
nix eval --show-trace .#nixosConfigurations.nix-laptop1
|
du -sh /nix/store
|
||||||
|
|
||||||
# Build and enter debug shell
|
# List top space users in store
|
||||||
nix develop
|
nix store du --human-readable | head -20
|
||||||
|
|
||||||
# Clean up old generations
|
# Find store paths for a package
|
||||||
nix-collect-garbage -d
|
nix store path-info -rS $(which some-package)
|
||||||
|
|
||||||
# Optimize Nix store
|
|
||||||
nix store optimise
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Getting Help
|
||||||
|
|
||||||
|
1. **Check documentation** - Review relevant doc file
|
||||||
|
2. **Look at existing examples** - Check `inventory.nix` or `users.nix`
|
||||||
|
3. **Search for similar patterns** - `grep -r "athenix.option" .`
|
||||||
|
4. **Run tests locally** - `nix flake check --show-trace` with full output
|
||||||
|
5. **Review git history** - `git log --patch -- filename.nix`
|
||||||
|
|
||||||
## See Also
|
## See Also
|
||||||
|
|
||||||
- [README.md](../README.md) - Main documentation
|
- [BUILDING.md](BUILDING.md) - Building artifacts
|
||||||
- [INVENTORY.md](INVENTORY.md) - Host inventory configuration
|
- [INVENTORY.md](INVENTORY.md) - Host configuration
|
||||||
- [BUILDING.md](BUILDING.md) - Building installation media
|
- [NAMESPACE.md](NAMESPACE.md) - Configuration options
|
||||||
- [USER_CONFIGURATION.md](USER_CONFIGURATION.md) - User management
|
- [USER_CONFIGURATION.md](USER_CONFIGURATION.md) - User management
|
||||||
|
- [EXTERNAL_MODULES.md](EXTERNAL_MODULES.md) - External modules
|
||||||
|
- [README.md](../README.md) - Main documentation
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# External Configuration Modules
|
# External Configuration Modules
|
||||||
|
|
||||||
This guide explains how to use external modules for system and user configurations in nixos-systems.
|
Guide to using external modules for system and user configurations.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
@@ -8,116 +8,141 @@ This guide explains how to use external modules for system and user configuratio
|
|||||||
- [System Modules](#system-modules)
|
- [System Modules](#system-modules)
|
||||||
- [User Modules](#user-modules)
|
- [User Modules](#user-modules)
|
||||||
- [Fetch Methods](#fetch-methods)
|
- [Fetch Methods](#fetch-methods)
|
||||||
- [Templates](#templates)
|
- [Creating External Modules](#creating-external-modules)
|
||||||
- [Integration Details](#integration-details)
|
- [Best Practices](#best-practices)
|
||||||
|
|
||||||
## Overview
|
## 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).
|
External modules allow you to maintain configurations in separate Git repositories and reference them from Athenix.
|
||||||
|
|
||||||
**Benefits:**
|
**Benefits:**
|
||||||
- **Separation:** Keep configs in separate repositories
|
- **Separation** - Keep complex configs in separate repositories
|
||||||
- **Versioning:** Pin to specific commits for reproducibility
|
- **Reproducibility** - Pin specific commits for deterministic builds
|
||||||
- **Reusability:** Share configurations across deployments
|
- **Reusability** - Share configurations across multiple deployments
|
||||||
- **Flexibility:** Mix external modules with local overrides
|
- **Flexibility** - Mix external modules with local configuration
|
||||||
|
- **Ownership** - Users maintain their own dotfiles
|
||||||
|
|
||||||
## System Modules
|
## System Modules
|
||||||
|
|
||||||
External system modules provide complete NixOS configurations for hosts.
|
External system modules provide host-specific NixOS configurations.
|
||||||
|
|
||||||
### Usage in inventory.nix
|
### Usage
|
||||||
|
|
||||||
|
In `inventory.nix`, reference an external module using the `external` field:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
nix-lxc = {
|
nix-lxc = {
|
||||||
devices = {
|
devices = {
|
||||||
# Traditional inline configuration
|
# Inline configuration (traditional method)
|
||||||
"local-server" = {
|
"local-server" = {
|
||||||
athenix.users.admin.enable = true;
|
athenix.sw.type = "headless";
|
||||||
services.nginx.enable = true;
|
services.nginx.enable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
# External module from Git
|
# External module (lazy evaluation - fetched only when building this host)
|
||||||
"remote-server" = builtins.fetchGit {
|
"remote-server".external = builtins.fetchGit {
|
||||||
url = "https://git.factory.uga.edu/org/server-config";
|
url = "https://git.factory.uga.edu/org/server-config";
|
||||||
rev = "abc123..."; # Pin to specific commit
|
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";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### External Repository Structure
|
**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/
|
server-config/
|
||||||
├── default.nix # Required: NixOS module
|
├── default.nix # Required: NixOS module
|
||||||
└── README.md # Optional: Documentation
|
├── README.md # Recommended: Documentation
|
||||||
|
└── optional/
|
||||||
|
├── config/ # Optional: Configuration files
|
||||||
|
└── scripts/ # Optional: Helper scripts
|
||||||
```
|
```
|
||||||
|
|
||||||
**default.nix:**
|
### Module Content (default.nix)
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
|
# The module receives inputs and standard NixOS module parameters
|
||||||
{ inputs, ... }:
|
{ inputs, ... }:
|
||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
{
|
{
|
||||||
# Your NixOS configuration
|
# Your NixOS configuration
|
||||||
|
# Use any standard NixOS option or athenix.* options
|
||||||
|
|
||||||
services.nginx = {
|
services.nginx = {
|
||||||
enable = true;
|
enable = true;
|
||||||
virtualHosts."example.com" = {
|
virtualHosts."example.com" = {
|
||||||
root = "/var/www";
|
root = "/var/www";
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
# Use athenix namespace options
|
# Use athenix options
|
||||||
athenix.users.admin.enable = true;
|
|
||||||
athenix.sw.type = "headless";
|
athenix.sw.type = "headless";
|
||||||
|
athenix.sw.extraPackages = with pkgs; [ git htop ];
|
||||||
|
|
||||||
|
# Standard NixOS configuration
|
||||||
|
networking.firewall.allowedTCPPorts = [ 80 443 ];
|
||||||
|
services.openssh.enable = true;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### What External Modules Receive
|
### What System Modules Receive
|
||||||
|
|
||||||
- **`inputs`** - All flake inputs (nixpkgs, home-manager, etc.)
|
- **`inputs`** - All flake inputs (nixpkgs, home-manager, disko, etc.)
|
||||||
- **`config`** - Full NixOS configuration
|
- **`config`** - Current NixOS configuration (read/write)
|
||||||
- **`lib`** - Nixpkgs library functions
|
- **`lib`** - Nixpkgs library functions
|
||||||
- **`pkgs`** - Package set
|
- **`pkgs`** - Package set
|
||||||
|
|
||||||
### Module Integration Order
|
### Configuration Order
|
||||||
|
|
||||||
When a host is built, modules are loaded in this order:
|
When a host is built, modules load in this order:
|
||||||
|
|
||||||
1. User NixOS modules (from `users.nix` - `nixos.nix` files)
|
1. Hardware type module (from `hw/nix-*.nix`)
|
||||||
2. Host type module (from `hosts/types/`)
|
2. Common system configuration (from `fleet/common.nix`)
|
||||||
3. Configuration overrides (from `inventory.nix`)
|
3. Software type module (from `sw/{type}/`)
|
||||||
4. Hostname assignment
|
4. User NixOS modules (from `users.nix` - `nixos.nix` files)
|
||||||
5. External system module (if using `builtins.fetchGit`)
|
5. Device-specific overrides (from `inventory.nix`)
|
||||||
|
6. External system module (if present)
|
||||||
|
|
||||||
Later modules can override earlier ones using standard NixOS module precedence.
|
Each later module can override earlier ones using standard NixOS precedence rules.
|
||||||
|
|
||||||
### Template
|
|
||||||
|
|
||||||
Create a new system module:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nix flake init -t git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git#system
|
|
||||||
```
|
|
||||||
|
|
||||||
See [templates/system/](../templates/system/) for the complete template.
|
|
||||||
|
|
||||||
## User Modules
|
## User Modules
|
||||||
|
|
||||||
External user modules provide home-manager configurations (dotfiles, packages, programs).
|
External user modules provide home-manager configurations (dotfiles, environment setup).
|
||||||
|
|
||||||
### Usage in users.nix
|
### Usage
|
||||||
|
|
||||||
|
In `users.nix`, reference an external user module:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
athenix.users = {
|
athenix.users = {
|
||||||
# External user module (dotfiles, home-manager, and user options)
|
# External user module
|
||||||
myuser = builtins.fetchGit {
|
myuser.external = builtins.fetchGit {
|
||||||
url = "https://git.factory.uga.edu/username/dotfiles";
|
url = "https://git.factory.uga.edu/username/dotfiles";
|
||||||
rev = "abc123...";
|
rev = "abc123def456..."; # Pin to specific commit
|
||||||
};
|
};
|
||||||
|
|
||||||
# Inline user definition
|
# Inline user definition
|
||||||
inlineuser = {
|
otheruser = {
|
||||||
description = "Inline User";
|
description = "Other User";
|
||||||
extraGroups = [ "wheel" ];
|
extraGroups = [ "wheel" ];
|
||||||
shell = pkgs.zsh;
|
shell = pkgs.zsh;
|
||||||
hashedPassword = "$6$...";
|
hashedPassword = "$6$...";
|
||||||
@@ -125,148 +150,179 @@ athenix.users = {
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### External Repository Structure
|
Then enable on hosts in `inventory.nix`:
|
||||||
|
|
||||||
```
|
|
||||||
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):**
|
|
||||||
```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, ... }:
|
{ inputs, ... }:
|
||||||
{ config, lib, pkgs, osConfig ? null, ... }:
|
{ config, lib, pkgs, osConfig ? null, ... }:
|
||||||
{
|
{
|
||||||
# ========== User Account Configuration ==========
|
# ========== User Account Configuration ==========
|
||||||
|
# These options define the user account itself
|
||||||
|
|
||||||
athenix.users.myusername = {
|
athenix.users.myusername = {
|
||||||
description = "Your Full Name";
|
description = "My Full Name";
|
||||||
|
extraGroups = [ "wheel" "docker" ];
|
||||||
shell = pkgs.zsh;
|
shell = pkgs.zsh;
|
||||||
hashedPassword = "!";
|
hashedPassword = "!"; # SSH keys only
|
||||||
opensshKeys = [ "ssh-ed25519 AAAA..." ];
|
opensshKeys = [
|
||||||
|
"ssh-ed25519 AAAA... user@laptop"
|
||||||
|
];
|
||||||
useZshTheme = true;
|
useZshTheme = true;
|
||||||
useNvimPlugins = true;
|
useNvimPlugins = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
# ========== Home Manager Configuration ==========
|
# ========== Home Manager Configuration ==========
|
||||||
|
# User environment, packages, and dotfiles
|
||||||
|
|
||||||
# Packages
|
# Packages
|
||||||
home.packages = with pkgs; [
|
home.packages = with pkgs; [
|
||||||
vim
|
vim
|
||||||
git
|
git
|
||||||
htop
|
ripgrep
|
||||||
|
fzf
|
||||||
] ++ lib.optional (osConfig.athenix.sw.type or null == "desktop") firefox;
|
] ++ lib.optional (osConfig.athenix.sw.type or null == "desktop") firefox;
|
||||||
|
|
||||||
|
# Programs
|
||||||
programs.git = {
|
programs.git = {
|
||||||
enable = true;
|
enable = true;
|
||||||
userName = "My Name";
|
userName = "My Name";
|
||||||
userEmail = "me@example.com";
|
userEmail = "me@example.com";
|
||||||
|
extraConfig = {
|
||||||
|
init.defaultBranch = "main";
|
||||||
|
core.editor = "vim";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
programs.zsh = {
|
||||||
|
enable = true;
|
||||||
|
initExtra = ''
|
||||||
|
# Your Zsh configuration
|
||||||
|
export EDITOR=vim
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
# Manage dotfiles
|
# Manage dotfiles
|
||||||
home.file.".bashrc".source = ./dotfiles/bashrc;
|
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):**
|
### nixos.nix (Optional)
|
||||||
|
|
||||||
|
System-level configuration for this user (rarely needed):
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
{ inputs, ... }:
|
{ inputs, ... }:
|
||||||
{ config, lib, pkgs, ... }:
|
{ config, lib, pkgs, ... }:
|
||||||
{
|
{
|
||||||
# System-level configuration for this user
|
# System-level configuration
|
||||||
users.users.myuser.extraGroups = [ "docker" ];
|
# Only needed if the user requires specific system-wide settings
|
||||||
|
|
||||||
|
users.users.myusername.extraGroups = [ "docker" ];
|
||||||
environment.systemPackages = [ pkgs.docker ];
|
environment.systemPackages = [ pkgs.docker ];
|
||||||
|
|
||||||
|
# Security settings
|
||||||
|
security.sudo.extraRules = [{
|
||||||
|
users = [ "myusername" ];
|
||||||
|
commands = [{
|
||||||
|
command = "/usr/bin/something";
|
||||||
|
options = [ "NOPASSWD" ];
|
||||||
|
}];
|
||||||
|
}];
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### What User Modules Receive
|
### What User Modules Receive
|
||||||
|
|
||||||
**In user.nix:**
|
**In user.nix:**
|
||||||
- **`inputs`** - Flake inputs (nixpkgs, home-manager, etc.)
|
- **`inputs`** - All flake inputs (nixpkgs, home-manager, etc.)
|
||||||
- **`config`** - Home-manager configuration
|
- **`config`** - Home-manager configuration (read/write)
|
||||||
- **`lib`** - Nixpkgs library functions
|
- **`lib`** - Nixpkgs library functions
|
||||||
- **`pkgs`** - Package set
|
- **`pkgs`** - Package set
|
||||||
- **`osConfig`** - OS-level configuration (read-only)
|
- **`osConfig`** - OS configuration (read-only) - useful for conditional setup
|
||||||
|
|
||||||
**In nixos.nix:**
|
**In nixos.nix:**
|
||||||
- **`inputs`** - Flake inputs
|
- **`inputs`** - Flake inputs
|
||||||
- **`config`** - NixOS configuration
|
- **`config`** - NixOS configuration (read/write)
|
||||||
- **`lib`** - Nixpkgs library functions
|
- **`lib`** - Nixpkgs library functions
|
||||||
- **`pkgs`** - Package set
|
- **`pkgs`** - Package set
|
||||||
|
|
||||||
### User Options in users.nix
|
### Conditional Setup Example
|
||||||
|
|
||||||
|
Use `osConfig` to conditionally set up dotfiles based on the system type:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
username = {
|
# In user.nix
|
||||||
# Identity
|
{ inputs, ... }:
|
||||||
description = "Full Name";
|
{ config, lib, pkgs, osConfig ? null, ... }:
|
||||||
|
{
|
||||||
# External configuration
|
athenix.users.myuser = { /* ... */ };
|
||||||
external = builtins.fetchGit { ... };
|
|
||||||
# System settings
|
# Install Firefox only on desktop systems
|
||||||
extraGroups = [ "wheel" "networkmanager" ];
|
home.packages = with pkgs; [
|
||||||
hashedPassword = "$6$...";
|
ripgrep
|
||||||
opensshKeys = [ "ssh-ed25519 ..." ];
|
] ++ lib.optional (osConfig.athenix.sw.type or null == "desktop") firefox;
|
||||||
shell = pkgs.zsh;
|
|
||||||
|
# Different shell config per system
|
||||||
# Theme integration
|
programs.zsh.initExtra = ''
|
||||||
useZshTheme = true; # Apply system zsh theme (default: true)
|
${lib.optionalString (osConfig.athenix.sw.type or null == "headless") "
|
||||||
useNvimPlugins = true; # Apply system nvim config (default: true)
|
# Headless-only settings
|
||||||
|
"}
|
||||||
# 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:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nix flake init -t git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git#user
|
|
||||||
```
|
|
||||||
|
|
||||||
See [templates/user/](../templates/user/) for the complete template.
|
|
||||||
|
|
||||||
## Fetch Methods
|
## Fetch Methods
|
||||||
|
|
||||||
### Recommended: fetchGit with Revision
|
### builtins.fetchGit (Recommended)
|
||||||
|
|
||||||
Pin to a specific commit for reproducibility:
|
Pin to a specific Git revision:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
builtins.fetchGit {
|
builtins.fetchGit {
|
||||||
url = "https://github.com/user/repo";
|
url = "https://git.factory.uga.edu/username/dotfiles";
|
||||||
rev = "abc123def456..."; # Full commit hash (40 characters)
|
rev = "abc123def456..."; # Required: specific commit hash
|
||||||
ref = "main"; # Optional: branch name
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Finding the commit hash:**
|
**Advantages:**
|
||||||
```bash
|
- Reproducible (pinned to exact commit)
|
||||||
# Latest commit on main branch
|
- Works with any Git repository
|
||||||
git ls-remote https://github.com/user/repo main
|
- Supports SSH or HTTPS URLs
|
||||||
|
|
||||||
# Or from a local clone
|
**Important:** Always specify `rev` (commit hash) for reproducibility. Don't use branches which can change.
|
||||||
git rev-parse HEAD
|
|
||||||
```
|
|
||||||
|
|
||||||
### fetchGit with Branch (Less Reproducible)
|
### builtins.fetchTarball
|
||||||
|
|
||||||
Always fetches latest from branch:
|
|
||||||
|
|
||||||
```nix
|
|
||||||
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:
|
Download specific release archives:
|
||||||
|
|
||||||
@@ -287,174 +343,141 @@ nix-prefetch-url --unpack https://github.com/user/repo/archive/v1.0.0.tar.gz
|
|||||||
Use local directories during development:
|
Use local directories during development:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
/home/username/dev/my-config
|
# users.nix
|
||||||
|
athenix.users.myuser.external = /home/user/my-dotfiles;
|
||||||
|
|
||||||
# Or relative to repository
|
# inventory.nix
|
||||||
./my-local-config
|
nix-laptop = {
|
||||||
```
|
|
||||||
|
|
||||||
⚠️ **Warning:** Only for testing. Use Git-based methods for production.
|
|
||||||
|
|
||||||
## Templates
|
|
||||||
|
|
||||||
### System Module Template
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 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](../templates/system/README.md) for detailed usage.
|
|
||||||
|
|
||||||
### User Module Template
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 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](../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:
|
|
||||||
|
|
||||||
```nix
|
|
||||||
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):**
|
|
||||||
```nix
|
|
||||||
import (externalPath + "/user.nix") { inherit inputs; }
|
|
||||||
# Evaluated as NixOS module to extract athenix.users.<username> options
|
|
||||||
```
|
|
||||||
|
|
||||||
**Home-manager configuration:**
|
|
||||||
```nix
|
|
||||||
import (externalPath + "/user.nix") { inherit inputs; }
|
|
||||||
# Imported into home-manager for home.*, programs.*, services.* options
|
|
||||||
```
|
|
||||||
|
|
||||||
**System-level config (optional):**
|
|
||||||
```nix
|
|
||||||
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
|
|
||||||
nix-lxc = {
|
|
||||||
devices = {
|
devices = {
|
||||||
"server" = builtins.fetchGit {
|
"dev".athenix.users.myuser.enable = true;
|
||||||
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
|
**Note:** Only works if the path exists on the machine running `nix flake check` or `nix build`.
|
||||||
|
|
||||||
**user.nix:**
|
## 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
|
```nix
|
||||||
{ inputs, ... }:
|
builtins.fetchGit {
|
||||||
{ config, lib, pkgs, osConfig ? null, ... }:
|
url = "https://git.factory.uga.edu/username/dotfiles";
|
||||||
{
|
# No rev specified or using "main"
|
||||||
# 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
|
✅ Correct - using commit hash:
|
||||||
|
|
||||||
```
|
|
||||||
dotfiles/
|
|
||||||
├── user.nix
|
|
||||||
├── nixos.nix
|
|
||||||
└── config/
|
|
||||||
├── bashrc
|
|
||||||
├── vimrc
|
|
||||||
└── gitconfig
|
|
||||||
```
|
|
||||||
|
|
||||||
**user.nix:**
|
|
||||||
```nix
|
```nix
|
||||||
{ inputs, ... }:
|
builtins.fetchGit {
|
||||||
{ config, lib, pkgs, osConfig ? null, ... }:
|
url = "https://git.factory.uga.edu/username/dotfiles";
|
||||||
{
|
rev = "abc123def456789...";
|
||||||
# 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;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 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
|
## See Also
|
||||||
|
|
||||||
- [INVENTORY.md](INVENTORY.md) - Host configuration guide
|
- [USER_CONFIGURATION.md](USER_CONFIGURATION.md) - User management
|
||||||
- [USER_CONFIGURATION.md](USER_CONFIGURATION.md) - User management guide
|
- [INVENTORY.md](INVENTORY.md) - Host configuration
|
||||||
- [NAMESPACE.md](NAMESPACE.md) - Configuration options reference
|
- [NAMESPACE.md](NAMESPACE.md) - Configuration options
|
||||||
- [templates/system/](../templates/system/) - System module template
|
|
||||||
- [templates/user/](../templates/user/) - User module template
|
|
||||||
- [README.md](../README.md) - Main documentation
|
- [README.md](../README.md) - Main documentation
|
||||||
|
- [templates/user/](../templates/user/) - User module template
|
||||||
|
- [templates/system/](../templates/system/) - System module template
|
||||||
|
|||||||
@@ -1,101 +1,272 @@
|
|||||||
# Host Inventory Configuration
|
# Host Inventory Configuration
|
||||||
|
|
||||||
This guide explains how to configure hosts in `inventory.nix` to define your fleet of devices.
|
This document explains the `inventory.nix` file, which defines all hosts in your fleet.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
- [Understanding Inventory Structure](#understanding-inventory-structure)
|
- [Overview](#overview)
|
||||||
- [Hostname Generation Rules](#hostname-generation-rules)
|
- [Structure](#structure)
|
||||||
- [Adding Hosts](#adding-hosts)
|
- [Hostname Generation](#hostname-generation)
|
||||||
- [Device Configuration Options](#device-configuration-options)
|
- [Configuration Methods](#configuration-methods)
|
||||||
|
- [Options](#options)
|
||||||
- [Examples](#examples)
|
- [Examples](#examples)
|
||||||
|
|
||||||
## Understanding Inventory Structure
|
## Overview
|
||||||
|
|
||||||
The `inventory.nix` file defines all hosts in the fleet using a flexible system. Top-level keys are always hostname **prefixes**, and actual hostnames are generated from device configurations.
|
`inventory.nix` defines your fleet of hosts. Top-level keys are hostname **prefixes**, and actual hostnames are generated from device specifications. This allows you to manage large fleets with minimal repetition.
|
||||||
|
|
||||||
## Hostname Generation Rules
|
**Key concepts:**
|
||||||
|
- Each top-level key generates one or more NixOS configurations
|
||||||
|
- Host type defaults to the prefix name (can be overridden)
|
||||||
|
- System architecture defaults to `x86_64-linux`
|
||||||
|
- Common configuration can be applied to all devices in a group via `overrides`
|
||||||
|
|
||||||
- **Numeric suffixes**: no dash (e.g., `nix-laptop1`, `nix-laptop2`)
|
## Structure
|
||||||
- **Non-numeric suffixes**: with dash (e.g., `nix-laptop-alpha`, `nix-laptop-beta`)
|
|
||||||
- **Custom hostnames**: Set `athenix.host.useHostPrefix = false` to use suffix as full hostname
|
|
||||||
|
|
||||||
## Adding Hosts
|
|
||||||
|
|
||||||
### Method 1: Quick Count (Simplest)
|
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
nix-laptop = {
|
{
|
||||||
devices = 5; # Creates: nix-laptop1, nix-laptop2, ..., nix-laptop5
|
"prefix-name" = {
|
||||||
};
|
# Optional: Device count or explicit device map
|
||||||
|
devices = 5; # or { "1" = { ... }; "alpha" = { ... }; }
|
||||||
|
|
||||||
|
# Optional: Hardware type (defaults to prefix name)
|
||||||
|
type = "nix-desktop";
|
||||||
|
|
||||||
|
# Optional: System architecture
|
||||||
|
system = "x86_64-linux";
|
||||||
|
|
||||||
|
# Optional: Configuration applied to ALL devices in this group
|
||||||
|
overrides = {
|
||||||
|
athenix.users.student.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Optional: Per-device configuration
|
||||||
|
"device-suffix" = { ... };
|
||||||
|
};
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Method 2: Explicit Count with Overrides
|
## Hostname Generation
|
||||||
|
|
||||||
|
Hostnames are generated automatically based on the device key:
|
||||||
|
|
||||||
|
- **Numeric keys** (`"1"`, `"2"`, `"42"`) → no dash: `prefix1`, `prefix2`, `prefix42`
|
||||||
|
- **Non-numeric keys** (`"alpha"`, `"special"`) → with dash: `prefix-alpha`, `prefix-special`
|
||||||
|
- **Custom hostnames** → Set `athenix.host.useHostPrefix = false` to use the suffix as the full hostname (no prefix)
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
nix-laptop = {
|
nix-laptop = {
|
||||||
devices = 5;
|
devices = 3; # Generates: nix-laptop1, nix-laptop2, nix-laptop3
|
||||||
overrides = {
|
};
|
||||||
# Applied to ALL nix-laptop hosts
|
|
||||||
athenix.users.student.enable = true;
|
nix-surface = {
|
||||||
athenix.sw.extraPackages = with pkgs; [ vim git ];
|
devices = {
|
||||||
|
"1" = { }; # → nix-surface1
|
||||||
|
"special" = { }; # → nix-surface-special
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
custom-machine = {
|
||||||
|
devices."lab-machine" = {
|
||||||
|
athenix.host.useHostPrefix = false; # → lab-machine (not custom-machine-lab-machine)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### Method 3: Individual Device Configuration
|
## Configuration Methods
|
||||||
|
|
||||||
|
### Method 1: Simple Count
|
||||||
|
|
||||||
|
Create N identical hosts:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
nix-laptop = {
|
||||||
|
devices = 5;
|
||||||
|
};
|
||||||
|
# Generates: nix-laptop1, nix-laptop2, nix-laptop3, nix-laptop4, nix-laptop5
|
||||||
|
```
|
||||||
|
|
||||||
|
### Method 2: Simple Count with Overrides
|
||||||
|
|
||||||
|
Create N hosts with common configuration:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
nix-desktop = {
|
||||||
|
devices = 3;
|
||||||
|
overrides = {
|
||||||
|
athenix.users.student.enable = true;
|
||||||
|
athenix.sw.extraPackages = with pkgs; [ vim git ];
|
||||||
|
services.openssh.enable = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
# All three hosts get the overrides configuration
|
||||||
|
```
|
||||||
|
|
||||||
|
### Method 3: Explicit Device Map
|
||||||
|
|
||||||
|
Configure each device individually:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
nix-surface = {
|
nix-surface = {
|
||||||
devices = {
|
devices = {
|
||||||
"1".athenix.sw.kioskUrl = "https://dashboard1.example.com";
|
"1".athenix.sw.kioskUrl = "https://dashboard1.example.com";
|
||||||
"2".athenix.sw.kioskUrl = "https://dashboard2.example.com";
|
"2".athenix.sw.kioskUrl = "https://dashboard2.example.com";
|
||||||
"3".athenix.sw.kioskUrl = "https://dashboard3.example.com";
|
"3" = {
|
||||||
|
athenix.sw.kioskUrl = "https://dashboard3.example.com";
|
||||||
|
services.openssh.enable = false;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### Method 4: Mixed (Default Count + Custom Devices)
|
### Method 4: External Module
|
||||||
|
|
||||||
|
Reference a Git repository using the `external` field (lazy evaluation):
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
nix-surface = {
|
nix-lxc = {
|
||||||
defaultCount = 2; # Creates nix-surface1, nix-surface2
|
devices."builder".external = builtins.fetchGit {
|
||||||
|
url = "https://git.factory.uga.edu/org/builder-config";
|
||||||
|
rev = "abc123...";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Method 5: Mixed Approach
|
||||||
|
|
||||||
|
Combine default count, custom devices, and overrides:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
nix-lab = {
|
||||||
|
defaultCount = 5; # Creates nix-lab1 through nix-lab5
|
||||||
devices = {
|
devices = {
|
||||||
"special" = { # Creates nix-surface-special
|
"special" = {
|
||||||
athenix.sw.kioskUrl = "https://special-dashboard.example.com";
|
athenix.sw.extraPackages = with pkgs; [ special-software ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
overrides = {
|
overrides = {
|
||||||
# Applied to all devices (including "special")
|
# Applied to all devices (default count + custom)
|
||||||
athenix.sw.kioskUrl = "https://default-dashboard.example.com";
|
athenix.users.lab-admin.enable = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## Device Configuration Options
|
## Options
|
||||||
|
|
||||||
### Direct Configuration (Recommended)
|
### Top-Level Device Options
|
||||||
|
|
||||||
Use any NixOS or `athenix.*` option:
|
#### `devices`
|
||||||
|
|
||||||
|
Specify hosts to create. Can be:
|
||||||
|
- **Number**: Create N hosts with keys `"1"`, `"2"`, ..., `"N"`
|
||||||
|
- **Attribute set**: Map of device names to configurations
|
||||||
|
|
||||||
|
**Type**: `int | attrs`
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
```nix
|
||||||
|
devices = 5; # Creates 5 hosts
|
||||||
|
|
||||||
|
devices = {
|
||||||
|
"1" = { };
|
||||||
|
"alpha" = { };
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `defaultCount`
|
||||||
|
|
||||||
|
When using a device map, also create N numbered hosts.
|
||||||
|
|
||||||
|
**Type**: `int` (optional)
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```nix
|
||||||
|
defaultCount = 3; # Creates "1", "2", "3" in addition to devices map
|
||||||
|
devices = {
|
||||||
|
"special" = { };
|
||||||
|
};
|
||||||
|
# Result: hosts "1", "2", "3", and "special"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `type`
|
||||||
|
|
||||||
|
Hardware type module to use. Defaults to the prefix name (inferred from top-level key).
|
||||||
|
|
||||||
|
**Type**: `string` (optional)
|
||||||
|
|
||||||
|
**Options**: `nix-desktop`, `nix-laptop`, `nix-surface`, `nix-lxc`, `nix-wsl`, `nix-ephemeral`
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```nix
|
||||||
|
lab-machines = {
|
||||||
|
type = "nix-desktop"; # Use desktop hardware configuration
|
||||||
|
devices = 5;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `system`
|
||||||
|
|
||||||
|
System architecture. Defaults to `x86_64-linux`.
|
||||||
|
|
||||||
|
**Type**: `string` (optional)
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```nix
|
||||||
|
arm-devices = {
|
||||||
|
system = "aarch64-linux";
|
||||||
|
devices = 2;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `overrides`
|
||||||
|
|
||||||
|
Configuration applied to all devices in this group. Useful for fleet-wide settings.
|
||||||
|
|
||||||
|
**Type**: `attrs` (optional)
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```nix
|
||||||
|
nix-laptop = {
|
||||||
|
devices = 10;
|
||||||
|
overrides = {
|
||||||
|
# Applied to all 10 laptops
|
||||||
|
athenix.users.staff.enable = true;
|
||||||
|
services.openssh.enable = true;
|
||||||
|
boot.loader.timeout = 10;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Per-Device Options
|
||||||
|
|
||||||
|
Any NixOS or `athenix.*` option can be set per-device:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
"1" = {
|
nix-surface = {
|
||||||
# Athenix options
|
devices = {
|
||||||
athenix.users.myuser.enable = true;
|
"1" = {
|
||||||
athenix.host.filesystem.swapSize = "64G";
|
# athenix.* namespace options
|
||||||
athenix.sw.extraPackages = with pkgs; [ docker ];
|
athenix.users.student.enable = true;
|
||||||
athenix.sw.kioskUrl = "https://example.com";
|
athenix.host.filesystem.device = "/dev/sda";
|
||||||
|
athenix.host.filesystem.swapSize = "16G";
|
||||||
# Standard NixOS options
|
athenix.sw.kioskUrl = "https://dashboard1.example.com";
|
||||||
networking.firewall.enable = false;
|
athenix.sw.extraPackages = with pkgs; [ firefox ];
|
||||||
services.openssh.enable = true;
|
|
||||||
time.timeZone = "America/New_York";
|
# Standard NixOS options
|
||||||
|
networking.firewall.enable = false;
|
||||||
|
services.openssh.enable = true;
|
||||||
|
time.timeZone = "America/New_York";
|
||||||
|
boot.kernelPackages = pkgs.linuxPackages_latest;
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### Convenience: `athenix.forUser`
|
### Convenience: `athenix.forUser`
|
||||||
|
|
||||||
Quick setup for single-user systems (especially WSL):
|
Quick setup for single-user systems (especially WSL). This automatically enables a user and sets the WSL default user:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
nix-wsl = {
|
nix-wsl = {
|
||||||
|
|||||||
@@ -1,53 +1,69 @@
|
|||||||
# Configuration Namespace Reference
|
# Configuration Namespace Reference
|
||||||
|
|
||||||
All UGA Innovation Factory-specific options are under the `athenix` namespace to avoid conflicts with standard NixOS options.
|
All UGA Innovation Factory-specific options are in the `athenix` namespace to avoid conflicts with standard NixOS options.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
- [Host Configuration (`athenix.host`)](#host-configuration-athenixhost)
|
- [Host Configuration (`athenix.host`)](#host-configuration-athenixhost)
|
||||||
- [Software Configuration (`athenix.sw`)](#software-configuration-athenixsw)
|
- [Software Configuration (`athenix.sw`)](#software-configuration-athenixsw)
|
||||||
- [User Management (`athenix.users`)](#user-management-athenixusers)
|
- [User Management (`athenix.users`)](#user-management-athenixusers)
|
||||||
- [System Configuration (`athenix.system`)](#system-configuration-athenixsystem)
|
|
||||||
- [Convenience Options](#convenience-options)
|
- [Convenience Options](#convenience-options)
|
||||||
|
|
||||||
## Host Configuration (`athenix.host`)
|
## Host Configuration (`athenix.host`)
|
||||||
|
|
||||||
Hardware and host-specific settings.
|
Hardware and boot-related settings.
|
||||||
|
|
||||||
### `athenix.host.filesystem`
|
### `athenix.host.filesystem.device`
|
||||||
|
|
||||||
Disk and storage configuration.
|
Boot disk device path.
|
||||||
|
|
||||||
**Options:**
|
**Type:** String or null
|
||||||
- `athenix.host.filesystem.device` - Boot disk device (default: `/dev/sda`)
|
|
||||||
- `athenix.host.filesystem.swapSize` - Swap file size (default: `"32G"`)
|
**Default:** `null` (must be set by hardware type or per-host)
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
```nix
|
```nix
|
||||||
athenix.host.filesystem = {
|
athenix.host.filesystem.device = "/dev/nvme0n1";
|
||||||
device = "/dev/nvme0n1";
|
```
|
||||||
swapSize = "64G";
|
|
||||||
};
|
### `athenix.host.filesystem.swapSize`
|
||||||
|
|
||||||
|
Swap partition size.
|
||||||
|
|
||||||
|
**Type:** String (size with unit, e.g., `"32G"`, `"2G"`) or null
|
||||||
|
|
||||||
|
**Default:** `null` (must be set by hardware type or per-host)
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```nix
|
||||||
|
athenix.host.filesystem.swapSize = "64G";
|
||||||
```
|
```
|
||||||
|
|
||||||
### `athenix.host.buildMethods`
|
### `athenix.host.buildMethods`
|
||||||
|
|
||||||
List of supported build artifact types for this host.
|
Artifact types to build for this host.
|
||||||
|
|
||||||
**Type:** List of strings
|
**Type:** List of strings
|
||||||
|
|
||||||
**Options:** `"installer-iso"`, `"iso"`, `"ipxe"`, `"lxc"`, `"proxmox"`
|
**Options:** `"installer-iso"`, `"iso"`, `"ipxe"`, `"lxc"`, `"proxmox"`
|
||||||
|
|
||||||
**Default:** `["installer-iso"]`
|
**Default:** `[ "installer-iso" ]`
|
||||||
|
|
||||||
|
**Description:**
|
||||||
|
- `"installer-iso"` - Installer ISO with auto-install
|
||||||
|
- `"iso"` - Live ISO (boot without installation)
|
||||||
|
- `"ipxe"` - iPXE netboot artifacts
|
||||||
|
- `"lxc"` - LXC container tarball
|
||||||
|
- `"proxmox"` - Proxmox VMA template
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
```nix
|
```nix
|
||||||
athenix.host.buildMethods = [ "lxc" "proxmox" ];
|
athenix.host.buildMethods = [ "installer-iso" "lxc" ];
|
||||||
```
|
```
|
||||||
|
|
||||||
### `athenix.host.useHostPrefix`
|
### `athenix.host.useHostPrefix`
|
||||||
|
|
||||||
Whether to prepend the host type prefix to the hostname (used in inventory generation).
|
Whether to prepend the host type prefix to the generated hostname.
|
||||||
|
|
||||||
**Type:** Boolean
|
**Type:** Boolean
|
||||||
|
|
||||||
@@ -55,15 +71,19 @@ Whether to prepend the host type prefix to the hostname (used in inventory gener
|
|||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
```nix
|
```nix
|
||||||
athenix.host.useHostPrefix = false; # "builder" instead of "nix-lxc-builder"
|
# With useHostPrefix = true (default)
|
||||||
|
# Device "1" under "nix-laptop" → "nix-laptop1"
|
||||||
|
|
||||||
|
# With useHostPrefix = false
|
||||||
|
# Device "builder" under "nix-lxc" → "builder" (not "nix-lxc-builder")
|
||||||
|
athenix.host.useHostPrefix = false;
|
||||||
```
|
```
|
||||||
|
|
||||||
### `athenix.host.wsl`
|
### `athenix.host.wsl.user`
|
||||||
|
|
||||||
WSL-specific configuration options.
|
Default WSL user account (only for `nix-wsl` type).
|
||||||
|
|
||||||
**Options:**
|
**Type:** String (username)
|
||||||
- `athenix.host.wsl.user` - Default WSL user for this instance
|
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
```nix
|
```nix
|
||||||
@@ -72,11 +92,11 @@ athenix.host.wsl.user = "myusername";
|
|||||||
|
|
||||||
## Software Configuration (`athenix.sw`)
|
## Software Configuration (`athenix.sw`)
|
||||||
|
|
||||||
System software and application configuration.
|
System type, packages, and application configuration.
|
||||||
|
|
||||||
### `athenix.sw.enable`
|
### `athenix.sw.enable`
|
||||||
|
|
||||||
Enable the software configuration module.
|
Enable software configuration.
|
||||||
|
|
||||||
**Type:** Boolean
|
**Type:** Boolean
|
||||||
|
|
||||||
@@ -84,28 +104,32 @@ Enable the software configuration module.
|
|||||||
|
|
||||||
### `athenix.sw.type`
|
### `athenix.sw.type`
|
||||||
|
|
||||||
System type that determines the software profile.
|
System profile/type. Determines which software packages and services are installed.
|
||||||
|
|
||||||
**Type:** Enum
|
**Type:** String or list of strings
|
||||||
|
|
||||||
**Options:**
|
**Options:**
|
||||||
- `"desktop"` - Full desktop environment (GNOME)
|
- `"desktop"` - Full GNOME desktop environment with development tools
|
||||||
- `"tablet-kiosk"` - Surface tablets with kiosk mode browser
|
- `"tablet-kiosk"` - Surface tablets with Firefox kiosk browser
|
||||||
- `"stateless-kiosk"` - Diskless PXE boot kiosks
|
- `"stateless-kiosk"` - Diskless PXE-booted ephemeral systems
|
||||||
- `"headless"` - Servers and containers without GUI
|
- `"headless"` - Servers and containers without GUI
|
||||||
|
- `"builders"` - Build servers with build dependencies
|
||||||
|
|
||||||
**Default:** `"desktop"`
|
**Default:** `"desktop"`
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
```nix
|
```nix
|
||||||
athenix.sw.type = "headless";
|
athenix.sw.type = "desktop";
|
||||||
|
|
||||||
|
# Multiple types supported
|
||||||
|
athenix.sw.type = [ "desktop" "headless" ];
|
||||||
```
|
```
|
||||||
|
|
||||||
### `athenix.sw.kioskUrl`
|
### `athenix.sw.kioskUrl`
|
||||||
|
|
||||||
URL to display in kiosk mode browsers (for `tablet-kiosk` and `stateless-kiosk` types).
|
URL to display in kiosk browser (for `tablet-kiosk` and `stateless-kiosk` types).
|
||||||
|
|
||||||
**Type:** String
|
**Type:** String (URL)
|
||||||
|
|
||||||
**Default:** `"https://ha.factory.uga.edu"`
|
**Default:** `"https://ha.factory.uga.edu"`
|
||||||
|
|
||||||
@@ -114,12 +138,13 @@ URL to display in kiosk mode browsers (for `tablet-kiosk` and `stateless-kiosk`
|
|||||||
athenix.sw.kioskUrl = "https://dashboard.example.com";
|
athenix.sw.kioskUrl = "https://dashboard.example.com";
|
||||||
```
|
```
|
||||||
|
|
||||||
### `athenix.sw.python`
|
### `athenix.sw.python.enable`
|
||||||
|
|
||||||
Python development tools configuration.
|
Enable Python development tools (pixi, uv, etc.).
|
||||||
|
|
||||||
**Options:**
|
**Type:** Boolean
|
||||||
- `athenix.sw.python.enable` - Enable Python tools (pixi, uv) (default: `true`)
|
|
||||||
|
**Default:** `true`
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
```nix
|
```nix
|
||||||
@@ -128,11 +153,13 @@ athenix.sw.python.enable = true;
|
|||||||
|
|
||||||
### `athenix.sw.remoteBuild`
|
### `athenix.sw.remoteBuild`
|
||||||
|
|
||||||
Remote build server configuration for offloading builds.
|
Configure remote build servers for offloading builds.
|
||||||
|
|
||||||
|
**Type:** Attribute set
|
||||||
|
|
||||||
**Options:**
|
**Options:**
|
||||||
- `athenix.sw.remoteBuild.enable` - Use remote builders (default: enabled on tablets)
|
- `enable` - Enable remote builders (Boolean, default: `true` for tablets)
|
||||||
- `athenix.sw.remoteBuild.hosts` - List of build server hostnames
|
- `hosts` - List of remote builder hostnames (List of strings)
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
```nix
|
```nix
|
||||||
@@ -144,104 +171,187 @@ athenix.sw.remoteBuild = {
|
|||||||
|
|
||||||
### `athenix.sw.extraPackages`
|
### `athenix.sw.extraPackages`
|
||||||
|
|
||||||
Additional system packages to install beyond the type defaults.
|
Additional system packages beyond the type defaults.
|
||||||
|
|
||||||
**Type:** List of packages
|
**Type:** List of packages
|
||||||
|
|
||||||
**Default:** `[]`
|
**Default:** `[ ]`
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
```nix
|
```nix
|
||||||
athenix.sw.extraPackages = with pkgs; [
|
athenix.sw.extraPackages = with pkgs; [
|
||||||
vim
|
vim
|
||||||
htop
|
|
||||||
docker
|
docker
|
||||||
|
htop
|
||||||
|
ripgrep
|
||||||
];
|
];
|
||||||
```
|
```
|
||||||
|
|
||||||
### `athenix.sw.excludePackages`
|
### `athenix.sw.excludePackages`
|
||||||
|
|
||||||
Packages to exclude from the default list for this system type.
|
Packages to remove from the default list for this system type.
|
||||||
|
|
||||||
**Type:** List of packages
|
**Type:** List of packages
|
||||||
|
|
||||||
**Default:** `[]`
|
**Default:** `[ ]`
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
```nix
|
```nix
|
||||||
athenix.sw.excludePackages = with pkgs; [
|
athenix.sw.excludePackages = with pkgs; [
|
||||||
firefox # Remove Firefox from default desktop packages
|
firefox # Don't install Firefox on this system
|
||||||
];
|
];
|
||||||
```
|
```
|
||||||
|
|
||||||
## User Management (`athenix.users`)
|
## User Management (`athenix.users`)
|
||||||
|
|
||||||
User account configuration and management.
|
User account configuration and access control.
|
||||||
|
|
||||||
### `athenix.users.<username>.enable`
|
### `athenix.users.<username>.enable`
|
||||||
|
|
||||||
Enable a specific user account on this system.
|
Enable a user account on this system.
|
||||||
|
|
||||||
**Type:** Boolean
|
**Type:** Boolean
|
||||||
|
|
||||||
**Default:** `false` (except `root` and `engr-ugaif` which default to `true`)
|
**Default:** `false` (except `root` and `engr-ugaif` which are `true`)
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
```nix
|
```nix
|
||||||
athenix.users = {
|
# In inventory.nix
|
||||||
myuser.enable = true;
|
nix-laptop = {
|
||||||
student.enable = true;
|
devices = 5;
|
||||||
|
overrides.athenix.users.myuser.enable = true;
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### User Account Options
|
### User Account Options (in `users.nix`)
|
||||||
|
|
||||||
Each user in `users.nix` can be configured with:
|
Define user accounts in `users.nix` with these options:
|
||||||
|
|
||||||
|
#### `description`
|
||||||
|
|
||||||
|
Full name or description of the user.
|
||||||
|
|
||||||
|
**Type:** String
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
# Option 1: Define inline in users.nix
|
athenix.users.myuser.description = "John Doe";
|
||||||
athenix.users.myuser = {
|
```
|
||||||
description = "Full Name";
|
|
||||||
isNormalUser = true; # Default: true
|
|
||||||
extraGroups = [ "wheel" "docker" ]; # Additional groups
|
|
||||||
shell = pkgs.zsh; # Login shell
|
|
||||||
hashedPassword = "$6$..."; # Hashed password
|
|
||||||
opensshKeys = [ "ssh-ed25519 ..." ]; # SSH public keys
|
|
||||||
useZshTheme = true; # Use system Zsh theme
|
|
||||||
useNvimPlugins = true; # Use system Neovim config
|
|
||||||
|
|
||||||
enable = false; # Enable per-system in inventory.nix
|
|
||||||
};
|
|
||||||
|
|
||||||
# Option 2: Use external configuration (recommended)
|
#### `extraGroups`
|
||||||
# The external user.nix can set athenix.users.myuser options directly
|
|
||||||
athenix.users.anotheruser.external = builtins.fetchGit {
|
Additional Unix groups for the user.
|
||||||
|
|
||||||
|
**Type:** List of strings
|
||||||
|
|
||||||
|
**Common groups:**
|
||||||
|
- `"wheel"` - Sudo access
|
||||||
|
- `"networkmanager"` - Network configuration
|
||||||
|
- `"docker"` - Docker access
|
||||||
|
- `"video"` - Video device access
|
||||||
|
- `"audio"` - Audio device access
|
||||||
|
- `"input"` - Input device access (keyboards, mice)
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.users.myuser.extraGroups = [ "wheel" "docker" "networkmanager" ];
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `shell`
|
||||||
|
|
||||||
|
Login shell for the user.
|
||||||
|
|
||||||
|
**Type:** Package
|
||||||
|
|
||||||
|
**Default:** `pkgs.bash`
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.users.myuser.shell = pkgs.zsh;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `hashedPassword`
|
||||||
|
|
||||||
|
Password hash for the user.
|
||||||
|
|
||||||
|
**Type:** String (SHA-512 hash)
|
||||||
|
|
||||||
|
**Generation:**
|
||||||
|
```bash
|
||||||
|
mkpasswd -m sha-512
|
||||||
|
```
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.users.myuser.hashedPassword = "$6$...";
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `opensshKeys`
|
||||||
|
|
||||||
|
SSH public keys for this user.
|
||||||
|
|
||||||
|
**Type:** List of strings
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.users.myuser.opensshKeys = [
|
||||||
|
"ssh-ed25519 AAAA... user@host"
|
||||||
|
"ssh-rsa AAAA... user@other"
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `useZshTheme`
|
||||||
|
|
||||||
|
Apply system Zsh theme configuration to this user.
|
||||||
|
|
||||||
|
**Type:** Boolean
|
||||||
|
|
||||||
|
**Default:** `true`
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.users.myuser.useZshTheme = true;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `useNvimPlugins`
|
||||||
|
|
||||||
|
Apply system Neovim configuration to this user.
|
||||||
|
|
||||||
|
**Type:** Boolean
|
||||||
|
|
||||||
|
**Default:** `true`
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.users.myuser.useNvimPlugins = true;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `external`
|
||||||
|
|
||||||
|
Reference external user configuration (dotfiles, home-manager).
|
||||||
|
|
||||||
|
**Type:** Path or Git reference
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```nix
|
||||||
|
athenix.users.myuser.external = builtins.fetchGit {
|
||||||
url = "https://git.factory.uga.edu/username/dotfiles";
|
url = "https://git.factory.uga.edu/username/dotfiles";
|
||||||
rev = "abc123...";
|
rev = "abc123...";
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## System Configuration (`athenix.system`)
|
See [EXTERNAL_MODULES.md](EXTERNAL_MODULES.md) for detailed external module usage.
|
||||||
|
|
||||||
System-wide settings and services.
|
### Enabling Users on Systems
|
||||||
|
|
||||||
### `athenix.system.gc`
|
Users defined in `users.nix` are **not enabled by default**. Enable them in `inventory.nix`:
|
||||||
|
|
||||||
Automatic garbage collection configuration.
|
|
||||||
|
|
||||||
**Options:**
|
|
||||||
- `athenix.system.gc.enable` - Enable automatic garbage collection (default: `true`)
|
|
||||||
- `athenix.system.gc.frequency` - How often to run (default: `"weekly"`)
|
|
||||||
- `athenix.system.gc.retentionDays` - Days to keep old generations (default: `30`)
|
|
||||||
- `athenix.system.gc.optimise` - Optimize Nix store automatically (default: `true`)
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```nix
|
```nix
|
||||||
athenix.system.gc = {
|
# Option 1: Enable on all devices in a group
|
||||||
enable = true;
|
nix-laptop = {
|
||||||
frequency = "daily";
|
devices = 5;
|
||||||
retentionDays = 14;
|
overrides.athenix.users.student.enable = true;
|
||||||
optimise = true;
|
};
|
||||||
|
|
||||||
|
# Option 2: Enable on specific devices
|
||||||
|
nix-surface = {
|
||||||
|
devices = {
|
||||||
|
"1".athenix.users.admin.enable = true;
|
||||||
|
"2".athenix.users.admin.enable = true;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -249,7 +359,7 @@ athenix.system.gc = {
|
|||||||
|
|
||||||
### `athenix.forUser`
|
### `athenix.forUser`
|
||||||
|
|
||||||
Quick setup option that enables a user account in one line.
|
Quick setup for single-user systems. Automatically enables a user and sets it as the default.
|
||||||
|
|
||||||
**Type:** String (username) or null
|
**Type:** String (username) or null
|
||||||
|
|
||||||
@@ -257,11 +367,7 @@ Quick setup option that enables a user account in one line.
|
|||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
```nix
|
```nix
|
||||||
athenix.forUser = "myusername"; # Equivalent to athenix.users.myusername.enable = true
|
# In inventory.nix - enables the user automatically
|
||||||
```
|
|
||||||
|
|
||||||
**Usage in inventory.nix:**
|
|
||||||
```nix
|
|
||||||
nix-wsl = {
|
nix-wsl = {
|
||||||
devices = {
|
devices = {
|
||||||
"alice".athenix.forUser = "alice-uga";
|
"alice".athenix.forUser = "alice-uga";
|
||||||
@@ -269,9 +375,17 @@ nix-wsl = {
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Equivalent to:
|
||||||
|
```nix
|
||||||
|
"alice" = {
|
||||||
|
athenix.users.alice-uga.enable = true;
|
||||||
|
athenix.host.wsl.user = "alice-uga";
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
## See Also
|
## See Also
|
||||||
|
|
||||||
- [INVENTORY.md](INVENTORY.md) - Host inventory configuration guide
|
- [INVENTORY.md](INVENTORY.md) - Host configuration examples
|
||||||
- [USER_CONFIGURATION.md](USER_CONFIGURATION.md) - User management guide
|
- [USER_CONFIGURATION.md](USER_CONFIGURATION.md) - User account management guide
|
||||||
- [EXTERNAL_MODULES.md](EXTERNAL_MODULES.md) - External configuration modules
|
- [EXTERNAL_MODULES.md](EXTERNAL_MODULES.md) - External module integration
|
||||||
- [README.md](../README.md) - Main documentation
|
- [README.md](../README.md) - Main documentation
|
||||||
|
|||||||
@@ -1,103 +1,585 @@
|
|||||||
# User Configuration Guide
|
# User Configuration Guide
|
||||||
|
|
||||||
Complete guide to managing user accounts in nixos-systems.
|
Comprehensive guide to managing user accounts in Athenix.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
- [Overview](#overview)
|
- [Overview](#overview)
|
||||||
- [Quick Start](#quick-start)
|
- [Quick Start](#quick-start)
|
||||||
- [User Account Options](#user-account-options)
|
- [Defining Users](#defining-users)
|
||||||
- [External User Configurations](#external-user-configurations)
|
|
||||||
- [Enabling Users on Hosts](#enabling-users-on-hosts)
|
- [Enabling Users on Hosts](#enabling-users-on-hosts)
|
||||||
|
- [External User Configurations](#external-user-configurations)
|
||||||
- [Password Management](#password-management)
|
- [Password Management](#password-management)
|
||||||
- [SSH Keys](#ssh-keys)
|
- [SSH Keys](#ssh-keys)
|
||||||
|
- [User Groups](#user-groups)
|
||||||
- [Examples](#examples)
|
- [Examples](#examples)
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Users are defined in `users.nix` but are **not enabled by default** on all systems. Each system must explicitly enable users in `inventory.nix`.
|
User accounts are defined in `users.nix` but are **not enabled by default**. Each host must explicitly enable users in `inventory.nix`.
|
||||||
|
|
||||||
**Default enabled users:**
|
**Always-enabled users:**
|
||||||
- `root` - System administrator
|
- `root` - System administrator (enable: true)
|
||||||
- `engr-ugaif` - Innovation Factory default account
|
- `engr-ugaif` - Innovation Factory default account (enable: true)
|
||||||
|
|
||||||
|
All other users are disabled by default and must be explicitly enabled per-host.
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
### 1. Define User in users.nix
|
### 1. Define User in users.nix
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
athenix.users = {
|
athenix.users.myuser = {
|
||||||
# Option 1: Inline definition
|
description = "John Doe";
|
||||||
myuser = {
|
extraGroups = [ "wheel" "networkmanager" ];
|
||||||
description = "My Full Name";
|
shell = pkgs.zsh;
|
||||||
extraGroups = [ "wheel" "networkmanager" ];
|
hashedPassword = "$6$..."; # Generate with: mkpasswd -m sha-512
|
||||||
shell = pkgs.zsh;
|
opensshKeys = [ "ssh-ed25519 AAAA..." ];
|
||||||
hashedPassword = "$6$..."; # Generate with: mkpasswd -m sha-512
|
|
||||||
opensshKeys = [
|
|
||||||
"ssh-ed25519 AAAA... user@machine"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
# Option 2: External configuration (recommended for personalization)
|
|
||||||
myuser.external = builtins.fetchGit {
|
|
||||||
url = "https://git.factory.uga.edu/username/dotfiles";
|
|
||||||
rev = "abc123..."; # Pin to specific commit
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Enable User on Hosts
|
### 2. Enable on Hosts in inventory.nix
|
||||||
|
|
||||||
In `inventory.nix`:
|
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
nix-laptop = {
|
nix-laptop = {
|
||||||
devices = 2;
|
devices = 5;
|
||||||
overrides.athenix.users.myuser.enable = true; # Enables on all nix-laptop hosts
|
overrides.athenix.users.myuser.enable = true;
|
||||||
};
|
|
||||||
|
|
||||||
# Or for specific devices
|
|
||||||
nix-desktop = {
|
|
||||||
devices = {
|
|
||||||
"1".athenix.users.myuser.enable = true;
|
|
||||||
"2".athenix.users.otheruser.enable = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Or use convenience option
|
|
||||||
nix-wsl = {
|
|
||||||
devices."alice".athenix.forUser = "alice-user"; # Automatically enables user
|
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
## User Account Options
|
### 3. Users can now log in
|
||||||
|
|
||||||
Each user in `users.nix` can have the following options:
|
Users defined and enabled this way are automatically created on the system.
|
||||||
|
|
||||||
|
## Defining Users
|
||||||
|
|
||||||
|
Define users in `users.nix` under `athenix.users`:
|
||||||
|
|
||||||
|
### Inline User Definition
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
username = {
|
athenix.users.myuser = {
|
||||||
# === Identity ===
|
description = "My Full Name";
|
||||||
description = "Full Name"; # User's full name
|
extraGroups = [ "wheel" "networkmanager" ];
|
||||||
|
shell = pkgs.zsh;
|
||||||
|
hashedPassword = "$6$...";
|
||||||
|
opensshKeys = [ "ssh-ed25519 AAAA..." ];
|
||||||
|
useZshTheme = true;
|
||||||
|
useNvimPlugins = true;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### External User Configuration
|
||||||
|
|
||||||
|
Reference an external Git repository (recommended for personal dotfiles):
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.users.myuser.external = builtins.fetchGit {
|
||||||
|
url = "https://git.factory.uga.edu/username/dotfiles";
|
||||||
|
rev = "abc123..."; # Pin to specific commit
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
The external repository should contain:
|
||||||
|
- `user.nix` (required) - User account options AND home-manager configuration
|
||||||
|
- `nixos.nix` (optional) - System-level configuration
|
||||||
|
|
||||||
|
See [External User Configurations](#external-user-configurations) section below.
|
||||||
|
|
||||||
|
## User Account Options
|
||||||
|
|
||||||
|
### `description`
|
||||||
|
|
||||||
|
Full name or description of the user.
|
||||||
|
|
||||||
|
**Type:** String
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.users.myuser.description = "John Doe";
|
||||||
|
```
|
||||||
|
|
||||||
|
### `extraGroups`
|
||||||
|
|
||||||
|
Additional Unix groups for the user. Default is empty.
|
||||||
|
|
||||||
|
**Type:** List of strings
|
||||||
|
|
||||||
|
**Common groups:**
|
||||||
|
- `"wheel"` - Sudo access
|
||||||
|
- `"networkmanager"` - Network configuration
|
||||||
|
- `"docker"` - Docker and Podman access
|
||||||
|
- `"video"` - Video device access (GPU, displays)
|
||||||
|
- `"audio"` - Audio device access
|
||||||
|
- `"input"` - Input devices (keyboards, mice)
|
||||||
|
- `"kvm"` - KVM virtual machine access
|
||||||
|
- `"libvirtd"` - Libvirt daemon access
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.users.myuser.extraGroups = [
|
||||||
|
"wheel"
|
||||||
|
"networkmanager"
|
||||||
|
"docker"
|
||||||
|
"video"
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
### `shell`
|
||||||
|
|
||||||
|
Login shell for the user.
|
||||||
|
|
||||||
|
**Type:** Package
|
||||||
|
|
||||||
|
**Default:** `pkgs.bash`
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.users.myuser.shell = pkgs.zsh;
|
||||||
|
# or
|
||||||
|
athenix.users.myuser.shell = pkgs.fish;
|
||||||
|
```
|
||||||
|
|
||||||
|
### `hashedPassword`
|
||||||
|
|
||||||
|
Password hash for the user. Use `!` to disable password login (SSH keys only).
|
||||||
|
|
||||||
|
**Type:** String (SHA-512 hash)
|
||||||
|
|
||||||
|
**Generation:**
|
||||||
|
```bash
|
||||||
|
# Generate a hashed password
|
||||||
|
mkpasswd -m sha-512
|
||||||
|
|
||||||
|
# Or interactively
|
||||||
|
mkpasswd -m sha-512 -c
|
||||||
|
```
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.users.myuser.hashedPassword = "$6$...";
|
||||||
|
|
||||||
|
# Disable password login (require SSH keys)
|
||||||
|
athenix.users.myuser.hashedPassword = "!";
|
||||||
|
```
|
||||||
|
|
||||||
|
### `opensshKeys`
|
||||||
|
|
||||||
|
SSH public keys for remote access. Users without SSH keys require password login.
|
||||||
|
|
||||||
|
**Type:** List of strings
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.users.myuser.opensshKeys = [
|
||||||
|
"ssh-ed25519 AAAA... user@laptop"
|
||||||
|
"ssh-rsa AAAA... user@desktop"
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
**Getting your SSH public key:**
|
||||||
|
```bash
|
||||||
|
# Print your public key
|
||||||
|
cat ~/.ssh/id_ed25519.pub
|
||||||
|
|
||||||
|
# Generate a new key if needed
|
||||||
|
ssh-keygen -t ed25519 -C "user@host"
|
||||||
|
```
|
||||||
|
|
||||||
|
### `useZshTheme`
|
||||||
|
|
||||||
|
Apply system Zsh theme configuration to this user (if using Zsh as shell).
|
||||||
|
|
||||||
|
**Type:** Boolean
|
||||||
|
|
||||||
|
**Default:** `true`
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.users.myuser.useZshTheme = true;
|
||||||
|
```
|
||||||
|
|
||||||
|
### `useNvimPlugins`
|
||||||
|
|
||||||
|
Apply system Neovim configuration and plugins to this user.
|
||||||
|
|
||||||
|
**Type:** Boolean
|
||||||
|
|
||||||
|
**Default:** `true`
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.users.myuser.useNvimPlugins = true;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Enabling Users on Hosts
|
||||||
|
|
||||||
|
Users are **not enabled by default**. Enable them in `inventory.nix`:
|
||||||
|
|
||||||
|
### Enable on All Devices in a Group
|
||||||
|
|
||||||
|
```nix
|
||||||
|
nix-laptop = {
|
||||||
|
devices = 5;
|
||||||
|
overrides.athenix.users.myuser.enable = true;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Enable on Specific Devices
|
||||||
|
|
||||||
|
```nix
|
||||||
|
nix-desktop = {
|
||||||
|
devices = {
|
||||||
|
"1".athenix.users.admin.enable = true;
|
||||||
|
"2".athenix.users.staff.enable = true;
|
||||||
|
"3".athenix.users.staff.enable = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Enable Multiple Users
|
||||||
|
|
||||||
|
```nix
|
||||||
|
nix-laptop = {
|
||||||
|
devices = 5;
|
||||||
|
overrides = {
|
||||||
|
athenix.users.student.enable = true;
|
||||||
|
athenix.users.teacher.enable = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using `athenix.forUser` Convenience
|
||||||
|
|
||||||
|
Quick setup for single-user systems (especially WSL):
|
||||||
|
|
||||||
|
```nix
|
||||||
|
nix-wsl = {
|
||||||
|
devices = {
|
||||||
|
"alice".athenix.forUser = "alice-uga";
|
||||||
|
"bob".athenix.forUser = "bob-uga";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
This automatically enables the user and sets it as the default WSL user.
|
||||||
|
|
||||||
|
## External User Configurations
|
||||||
|
|
||||||
|
External user configurations (dotfiles) allow users to maintain their own home-manager setup in separate repositories.
|
||||||
|
|
||||||
|
### Repository Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
my-dotfiles/
|
||||||
|
├── user.nix # Required: User options + home-manager config
|
||||||
|
├── nixos.nix # Optional: System-level configuration
|
||||||
|
└── config/ # Optional: Your actual dotfiles
|
||||||
|
├── bashrc
|
||||||
|
├── zshrc
|
||||||
|
├── vimrc
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### user.nix (Required)
|
||||||
|
|
||||||
|
This file must provide BOTH user account options AND home-manager configuration:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{ 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" "networkmanager" ];
|
||||||
|
shell = pkgs.zsh;
|
||||||
|
hashedPassword = "!"; # SSH keys only
|
||||||
|
opensshKeys = [
|
||||||
|
"ssh-ed25519 AAAA... user@host"
|
||||||
|
];
|
||||||
|
useZshTheme = true;
|
||||||
|
useNvimPlugins = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# ========== Home Manager Configuration ==========
|
||||||
|
# User environment, packages, and dotfiles
|
||||||
|
|
||||||
# === System Access ===
|
home.packages = with pkgs; [
|
||||||
isNormalUser = true; # Default: true (false for root)
|
vim
|
||||||
extraGroups = [ # Additional Unix groups
|
ripgrep
|
||||||
"wheel" # Sudo access
|
fzf
|
||||||
"networkmanager" # Network configuration
|
] ++ lib.optional (osConfig.athenix.sw.type or null == "desktop") firefox;
|
||||||
"docker" # Docker access
|
|
||||||
"video" # Video device access
|
programs.git = {
|
||||||
"audio" # Audio device access
|
enable = true;
|
||||||
|
userName = "My Name";
|
||||||
|
userEmail = "me@example.com";
|
||||||
|
extraConfig = {
|
||||||
|
init.defaultBranch = "main";
|
||||||
|
core.editor = "vim";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
programs.zsh = {
|
||||||
|
enable = true;
|
||||||
|
initExtra = ''
|
||||||
|
# Your Zsh configuration
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Manage dotfiles
|
||||||
|
home.file.".config/zshrc".source = ./config/zshrc;
|
||||||
|
home.file.".config/bashrc".source = ./config/bashrc;
|
||||||
|
home.file.".vimrc".source = ./config/vimrc;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### nixos.nix (Optional)
|
||||||
|
|
||||||
|
System-level configuration for this user (rarely needed):
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{ inputs, ... }:
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
{
|
||||||
|
# System-level configuration for this user
|
||||||
|
users.users.myusername.extraGroups = [ "docker" ];
|
||||||
|
environment.systemPackages = [ pkgs.docker ];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using External User Configuration
|
||||||
|
|
||||||
|
In `users.nix`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.users.myuser.external = builtins.fetchGit {
|
||||||
|
url = "https://git.factory.uga.edu/username/dotfiles";
|
||||||
|
rev = "abc123..."; # Pin to specific commit
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Then enable on hosts in `inventory.nix`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
nix-laptop = {
|
||||||
|
devices = 5;
|
||||||
|
overrides.athenix.users.myuser.enable = true;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### External Module Parameters
|
||||||
|
|
||||||
|
The `user.nix` module receives:
|
||||||
|
|
||||||
|
- **`inputs`** - All flake inputs (nixpkgs, home-manager, etc.)
|
||||||
|
- **`config`** - Home-manager configuration
|
||||||
|
- **`lib`** - Nixpkgs library functions
|
||||||
|
- **`pkgs`** - Package set
|
||||||
|
- **`osConfig`** - OS-level configuration (read-only, can be used for conditional setup)
|
||||||
|
|
||||||
|
### Creating External User Configuration
|
||||||
|
|
||||||
|
Use the template:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
nix flake init -t git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git#user
|
||||||
|
```
|
||||||
|
|
||||||
|
## Password Management
|
||||||
|
|
||||||
|
### Generate Password Hash
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Interactive (won't echo)
|
||||||
|
mkpasswd -m sha-512 -c
|
||||||
|
|
||||||
|
# From string
|
||||||
|
echo "mypassword" | mkpasswd -m sha-512 -s
|
||||||
|
```
|
||||||
|
|
||||||
|
### Disable Password Login
|
||||||
|
|
||||||
|
Set `hashedPassword = "!"` and provide SSH keys:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.users.myuser = {
|
||||||
|
description = "SSH-only user";
|
||||||
|
hashedPassword = "!";
|
||||||
|
opensshKeys = [ "ssh-ed25519 AAAA..." ];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Update User Password on Running System
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# As the user
|
||||||
|
passwd
|
||||||
|
|
||||||
|
# As root (to change another user's password)
|
||||||
|
sudo passwd username
|
||||||
|
```
|
||||||
|
|
||||||
|
## SSH Keys
|
||||||
|
|
||||||
|
### Add SSH Keys to a User
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.users.myuser.opensshKeys = [
|
||||||
|
"ssh-ed25519 AAAA... user@laptop"
|
||||||
|
"ssh-ed25519 BBBB... user@desktop"
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get Your SSH Public Key
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Display your public key
|
||||||
|
cat ~/.ssh/id_ed25519.pub
|
||||||
|
|
||||||
|
# Or for RSA
|
||||||
|
cat ~/.ssh/id_rsa.pub
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generate New SSH Key
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ed25519 (recommended)
|
||||||
|
ssh-keygen -t ed25519 -C "user@host"
|
||||||
|
|
||||||
|
# RSA (older systems)
|
||||||
|
ssh-keygen -t rsa -b 4096 -C "user@host"
|
||||||
|
```
|
||||||
|
|
||||||
|
## User Groups
|
||||||
|
|
||||||
|
### wheel
|
||||||
|
|
||||||
|
Allows passwordless sudo access.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.users.myuser.extraGroups = [ "wheel" ];
|
||||||
|
```
|
||||||
|
|
||||||
|
### networkmanager
|
||||||
|
|
||||||
|
Configure network connections (requires `networkmanager` to be enabled):
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.users.myuser.extraGroups = [ "networkmanager" ];
|
||||||
|
```
|
||||||
|
|
||||||
|
### docker
|
||||||
|
|
||||||
|
Access Docker daemon (must have Docker enabled on system):
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.users.myuser.extraGroups = [ "docker" ];
|
||||||
|
```
|
||||||
|
|
||||||
|
### video and audio
|
||||||
|
|
||||||
|
Access GPU and audio devices:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
athenix.users.myuser.extraGroups = [ "video" "audio" ];
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Example 1: Basic Lab User
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# users.nix
|
||||||
|
athenix.users.student = {
|
||||||
|
description = "Student Account";
|
||||||
|
extraGroups = [ "networkmanager" ];
|
||||||
|
shell = pkgs.bash;
|
||||||
|
hashedPassword = "$6$...";
|
||||||
|
opensshKeys = []; # Password login only
|
||||||
|
};
|
||||||
|
|
||||||
|
# inventory.nix
|
||||||
|
nix-laptop = {
|
||||||
|
devices = 20;
|
||||||
|
overrides.athenix.users.student.enable = true;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 2: Developer with SSH Keys
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# users.nix
|
||||||
|
athenix.users.developer = {
|
||||||
|
description = "Developer";
|
||||||
|
extraGroups = [ "wheel" "docker" "networkmanager" ];
|
||||||
|
shell = pkgs.zsh;
|
||||||
|
hashedPassword = "!";
|
||||||
|
opensshKeys = [
|
||||||
|
"ssh-ed25519 AAAA... dev@laptop"
|
||||||
];
|
];
|
||||||
shell = pkgs.zsh; # Login shell (default: pkgs.bash)
|
useZshTheme = true;
|
||||||
hashedPassword = "$6$..."; # Hashed password (see below)
|
useNvimPlugins = true;
|
||||||
|
};
|
||||||
# === SSH Access ===
|
|
||||||
opensshKeys = [ # SSH public keys
|
# inventory.nix
|
||||||
"ssh-ed25519 AAAA... user@host"
|
nix-desktop = {
|
||||||
"ssh-rsa AAAA... user@otherhost"
|
devices = 3;
|
||||||
];
|
overrides.athenix.users.developer.enable = true;
|
||||||
|
};
|
||||||
# === External Configuration ===
|
```
|
||||||
|
|
||||||
|
### Example 3: WSL User with Dotfiles
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# users.nix
|
||||||
|
athenix.users.alice.external = builtins.fetchGit {
|
||||||
|
url = "https://git.factory.uga.edu/alice/dotfiles";
|
||||||
|
rev = "abc123...";
|
||||||
|
};
|
||||||
|
|
||||||
|
# inventory.nix
|
||||||
|
nix-wsl = {
|
||||||
|
devices = {
|
||||||
|
"alice".athenix.forUser = "alice-uga";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 4: Multiple Users on Single System
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# users.nix
|
||||||
|
athenix.users = {
|
||||||
|
admin = {
|
||||||
|
description = "System Administrator";
|
||||||
|
extraGroups = [ "wheel" ];
|
||||||
|
shell = pkgs.bash;
|
||||||
|
hashedPassword = "!";
|
||||||
|
opensshKeys = [ "ssh-ed25519 AAAA..." ];
|
||||||
|
};
|
||||||
|
guest = {
|
||||||
|
description = "Guest User";
|
||||||
|
extraGroups = [];
|
||||||
|
shell = pkgs.bash;
|
||||||
|
hashedPassword = "$6$...";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# inventory.nix
|
||||||
|
nix-desktop = {
|
||||||
|
devices = {
|
||||||
|
"admin-station" = {
|
||||||
|
athenix.users.admin.enable = true;
|
||||||
|
};
|
||||||
|
"guest-station" = {
|
||||||
|
athenix.users.guest.enable = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [INVENTORY.md](INVENTORY.md) - Host configuration
|
||||||
|
- [NAMESPACE.md](NAMESPACE.md) - All configuration options
|
||||||
|
- [EXTERNAL_MODULES.md](EXTERNAL_MODULES.md) - External modules in detail
|
||||||
|
- [README.md](../README.md) - Main documentation
|
||||||
external = builtins.fetchGit { ... }; # External user module (see below)
|
external = builtins.fetchGit { ... }; # External user module (see below)
|
||||||
|
|
||||||
# === Theme Integration ===
|
# === Theme Integration ===
|
||||||
|
|||||||
110
flake.lock
generated
110
flake.lock
generated
@@ -115,11 +115,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1765794845,
|
"lastModified": 1766150702,
|
||||||
"narHash": "sha256-YD5QWlGnusNbZCqR3pxG8tRxx9yUXayLZfAJRWspq2s=",
|
"narHash": "sha256-P0kM+5o+DKnB6raXgFEk3azw8Wqg5FL6wyl9jD+G5a4=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "disko",
|
"repo": "disko",
|
||||||
"rev": "7194cfe5b7a3660726b0fe7296070eaef601cae9",
|
"rev": "916506443ecd0d0b4a0f4cf9d40a3c22ce39b378",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -159,6 +159,26 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"flake-parts": {
|
"flake-parts": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs-lib": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1768135262,
|
||||||
|
"narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-parts_2": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs-lib": [
|
"nixpkgs-lib": [
|
||||||
"lazyvim-nixvim",
|
"lazyvim-nixvim",
|
||||||
@@ -219,6 +239,24 @@
|
|||||||
"inputs": {
|
"inputs": {
|
||||||
"systems": "systems_4"
|
"systems": "systems_4"
|
||||||
},
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils_4": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems_5"
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1681202837,
|
"lastModified": 1681202837,
|
||||||
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||||
@@ -318,11 +356,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1765979862,
|
"lastModified": 1767910483,
|
||||||
"narHash": "sha256-/r9/1KamvbHJx6I40H4HsSXnEcBAkj46ZwibhBx9kg0=",
|
"narHash": "sha256-MOU5YdVu4DVwuT5ztXgQpPuRRBjSjUGIdUzOQr9iQOY=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "d3135ab747fd9dac250ffb90b4a7e80634eacbe9",
|
"rev": "82fb7dedaad83e5e279127a38ef410bcfac6d77c",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -386,7 +424,7 @@
|
|||||||
},
|
},
|
||||||
"lazyvim-nixvim": {
|
"lazyvim-nixvim": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-parts": "flake-parts",
|
"flake-parts": "flake-parts_2",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
"nixvim": "nixvim"
|
"nixvim": "nixvim"
|
||||||
},
|
},
|
||||||
@@ -464,11 +502,11 @@
|
|||||||
},
|
},
|
||||||
"nixos-hardware": {
|
"nixos-hardware": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1764440730,
|
"lastModified": 1767185284,
|
||||||
"narHash": "sha256-ZlJTNLUKQRANlLDomuRWLBCH5792x+6XUJ4YdFRjtO4=",
|
"narHash": "sha256-ljDBUDpD1Cg5n3mJI81Hz5qeZAwCGxon4kQW3Ho3+6Q=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixos-hardware",
|
"repo": "nixos-hardware",
|
||||||
"rev": "9154f4569b6cdfd3c595851a6ba51bfaa472d9f3",
|
"rev": "40b1a28dce561bea34858287fbb23052c3ee63fe",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -518,11 +556,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs-old-kernel": {
|
"nixpkgs-old-kernel": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1765687488,
|
"lastModified": 1767313136,
|
||||||
"narHash": "sha256-7YAJ6xgBAQ/Nr+7MI13Tui1ULflgAdKh63m1tfYV7+M=",
|
"narHash": "sha256-16KkgfdYqjaeRGBaYsNrhPRRENs0qzkQVUooNHtoy2w=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "d02bcc33948ca19b0aaa0213fe987ceec1f4ebe1",
|
"rev": "ac62194c3917d5f474c1a844b6fd6da2db95077d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -534,11 +572,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1765838191,
|
"lastModified": 1768242861,
|
||||||
"narHash": "sha256-m5KWt1nOm76ILk/JSCxBM4MfK3rYY7Wq9/TZIIeGnT8=",
|
"narHash": "sha256-F4IIxa5xDHjtrmMcayM8lHctUq1oGltfBQu2+oqDWP4=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "c6f52ebd45e5925c188d1a20119978aa4ffd5ef6",
|
"rev": "1327e798cb055f96f92685df444e9a2c326ab5ed",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -608,6 +646,7 @@
|
|||||||
"inputs": {
|
"inputs": {
|
||||||
"agenix": "agenix",
|
"agenix": "agenix",
|
||||||
"disko": "disko",
|
"disko": "disko",
|
||||||
|
"flake-parts": "flake-parts",
|
||||||
"home-manager": "home-manager_2",
|
"home-manager": "home-manager_2",
|
||||||
"lazyvim-nixvim": "lazyvim-nixvim",
|
"lazyvim-nixvim": "lazyvim-nixvim",
|
||||||
"nixos-generators": "nixos-generators",
|
"nixos-generators": "nixos-generators",
|
||||||
@@ -615,6 +654,7 @@
|
|||||||
"nixos-wsl": "nixos-wsl",
|
"nixos-wsl": "nixos-wsl",
|
||||||
"nixpkgs": "nixpkgs_2",
|
"nixpkgs": "nixpkgs_2",
|
||||||
"nixpkgs-old-kernel": "nixpkgs-old-kernel",
|
"nixpkgs-old-kernel": "nixpkgs-old-kernel",
|
||||||
|
"usda-vision": "usda-vision",
|
||||||
"vscode-server": "vscode-server"
|
"vscode-server": "vscode-server"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -699,6 +739,21 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"systems_5": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"treefmt-nix": {
|
"treefmt-nix": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
@@ -721,13 +776,34 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vscode-server": {
|
"usda-vision": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-utils": "flake-utils_3",
|
"flake-utils": "flake-utils_3",
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1769814438,
|
||||||
|
"narHash": "sha256-DEZrmqpqbrd996W5p1r4GA1C8Jmo31n3N642ccS0deY=",
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"rev": "78bfcf02612817a2cee1edbf92deeac9bf657613",
|
||||||
|
"revCount": 126,
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.factory.uga.edu/MODEL/usda-vision.git"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.factory.uga.edu/MODEL/usda-vision.git"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vscode-server": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils_4",
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1753541826,
|
"lastModified": 1753541826,
|
||||||
"narHash": "sha256-foGgZu8+bCNIGeuDqQ84jNbmKZpd+JvnrL2WlyU4tuU=",
|
"narHash": "sha256-foGgZu8+bCNIGeuDqQ84jNbmKZpd+JvnrL2WlyU4tuU=",
|
||||||
|
|||||||
68
flake.nix
68
flake.nix
@@ -4,7 +4,7 @@
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
# This file defines the inputs (dependencies) and outputs (configurations)
|
# This file defines the inputs (dependencies) and outputs (configurations)
|
||||||
# for Athenix. It ties together the hardware, software, and user
|
# for Athenix. It ties together the hardware, software, and user
|
||||||
# configurations into deployable systems.
|
# configurations into deployable systems using flake-parts.
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
# Core NixOS package repository (Release 25.11)
|
# Core NixOS package repository (Release 25.11)
|
||||||
@@ -13,6 +13,12 @@
|
|||||||
# Older kernel packages for Surface compatibility if needed
|
# Older kernel packages for Surface compatibility if needed
|
||||||
nixpkgs-old-kernel.url = "github:NixOS/nixpkgs/nixos-25.05";
|
nixpkgs-old-kernel.url = "github:NixOS/nixpkgs/nixos-25.05";
|
||||||
|
|
||||||
|
# Flake-parts for modular flake organization
|
||||||
|
flake-parts = {
|
||||||
|
url = "github:hercules-ci/flake-parts";
|
||||||
|
inputs.nixpkgs-lib.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
# Home Manager for user environment management
|
# Home Manager for user environment management
|
||||||
home-manager = {
|
home-manager = {
|
||||||
url = "github:nix-community/home-manager/release-25.11";
|
url = "github:nix-community/home-manager/release-25.11";
|
||||||
@@ -53,49 +59,37 @@
|
|||||||
url = "github:nix-community/NixOS-WSL/main";
|
url = "github:nix-community/NixOS-WSL/main";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# USDA Vision Dashboard application
|
||||||
|
usda-vision = {
|
||||||
|
url = "git+https://git.factory.uga.edu/MODEL/usda-vision.git";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
inputs@{
|
inputs@{ self, flake-parts, ... }:
|
||||||
self,
|
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||||
nixpkgs,
|
# Support all common systems
|
||||||
nixpkgs-old-kernel,
|
systems = [
|
||||||
home-manager,
|
|
||||||
disko,
|
|
||||||
agenix,
|
|
||||||
lazyvim-nixvim,
|
|
||||||
nixos-hardware,
|
|
||||||
vscode-server,
|
|
||||||
nixos-generators,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
hosts = import ./hosts { inherit inputs; };
|
|
||||||
linuxSystem = "x86_64-linux";
|
|
||||||
artifacts = import ./installer/artifacts.nix {
|
|
||||||
inherit inputs hosts self;
|
|
||||||
system = linuxSystem;
|
|
||||||
};
|
|
||||||
forAllSystems = nixpkgs.lib.genAttrs [
|
|
||||||
"x86_64-linux"
|
"x86_64-linux"
|
||||||
"aarch64-linux"
|
"aarch64-linux"
|
||||||
"x86_64-darwin"
|
"x86_64-darwin"
|
||||||
"aarch64-darwin"
|
"aarch64-darwin"
|
||||||
];
|
];
|
||||||
in
|
|
||||||
{
|
|
||||||
# Formatter for 'nix fmt'
|
|
||||||
formatter = forAllSystems (system: nixpkgs.legacyPackages.${system}.nixfmt-rfc-style);
|
|
||||||
|
|
||||||
# Generate NixOS configurations from hosts/default.nix
|
# Import flake-parts modules
|
||||||
nixosConfigurations = hosts.nixosConfigurations;
|
imports = [
|
||||||
|
./parts/formatter.nix
|
||||||
# Expose artifacts to all systems, but they are always built for x86_64-linux
|
./parts/lib.nix
|
||||||
packages = forAllSystems (_: artifacts);
|
./parts/nixos-configurations.nix
|
||||||
|
./parts/nixos-modules.nix
|
||||||
# Expose modules for external use
|
./parts/packages.nix
|
||||||
nixosModules = import ./installer/modules.nix { inherit inputs; };
|
./parts/templates.nix
|
||||||
|
./parts/docs.nix
|
||||||
# Templates for external configurations
|
./inventory.nix
|
||||||
templates = import ./templates;
|
./users.nix
|
||||||
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
64
fleet/boot.nix
Normal file
64
fleet/boot.nix
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# Boot configuration module
|
||||||
|
# ============================================================================
|
||||||
|
# This module defines:
|
||||||
|
# - Bootloader configuration (systemd-boot with Plymouth)
|
||||||
|
# - Timezone and locale settings
|
||||||
|
# - Systemd sleep configuration
|
||||||
|
#
|
||||||
|
# Only applies to:
|
||||||
|
# - Linux systems (not Darwin/macOS)
|
||||||
|
# - Systems with actual boot hardware (not containers/WSL)
|
||||||
|
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
# Check if this is a bootable system (not container, not WSL)
|
||||||
|
isBootable = !(config.boot.isContainer or false) && (pkgs.stdenv.isLinux);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = lib.mkIf isBootable {
|
||||||
|
boot = {
|
||||||
|
loader.systemd-boot.enable = lib.mkDefault true;
|
||||||
|
loader.efi.canTouchEfiVariables = lib.mkDefault true;
|
||||||
|
plymouth.enable = lib.mkDefault true;
|
||||||
|
|
||||||
|
# Enable "Silent boot"
|
||||||
|
consoleLogLevel = 3;
|
||||||
|
initrd.verbose = false;
|
||||||
|
|
||||||
|
# Hide the OS choice for bootloaders.
|
||||||
|
# It's still possible to open the bootloader list by pressing any key
|
||||||
|
# It will just not appear on screen unless a key is pressed
|
||||||
|
loader.timeout = lib.mkDefault 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Set your time zone.
|
||||||
|
time.timeZone = "America/New_York";
|
||||||
|
|
||||||
|
# Select internationalisation properties.
|
||||||
|
i18n.defaultLocale = "en_US.UTF-8";
|
||||||
|
|
||||||
|
i18n.extraLocaleSettings = {
|
||||||
|
LC_ADDRESS = "en_US.UTF-8";
|
||||||
|
LC_IDENTIFICATION = "en_US.UTF-8";
|
||||||
|
LC_MEASUREMENT = "en_US.UTF-8";
|
||||||
|
LC_MONETARY = "en_US.UTF-8";
|
||||||
|
LC_NAME = "en_US.UTF-8";
|
||||||
|
LC_NUMERIC = "en_US.UTF-8";
|
||||||
|
LC_PAPER = "en_US.UTF-8";
|
||||||
|
LC_TELEPHONE = "en_US.UTF-8";
|
||||||
|
LC_TIME = "en_US.UTF-8";
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.sleep.extraConfig = ''
|
||||||
|
SuspendState=freeze
|
||||||
|
HibernateDelaySec=2h
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
196
fleet/common.nix
Normal file
196
fleet/common.nix
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# Common Host Module
|
||||||
|
# ============================================================================
|
||||||
|
# This module contains all the common configuration shared by all host types.
|
||||||
|
# It is automatically imported by the fleet generator for every host.
|
||||||
|
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
inputs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
# Import all hardware modules so they're available for enabling
|
||||||
|
hwTypes = import ../hw { inherit inputs; };
|
||||||
|
hwModules = lib.attrValues hwTypes;
|
||||||
|
|
||||||
|
# User account submodule definition
|
||||||
|
userSubmodule = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
enable = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Whether this user account is enabled on this system.";
|
||||||
|
};
|
||||||
|
isNormalUser = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Whether this is a normal user account (vs system user).";
|
||||||
|
};
|
||||||
|
description = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Full name or description of the user (GECOS field).";
|
||||||
|
example = "John Doe";
|
||||||
|
};
|
||||||
|
extraGroups = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "Additional groups for the user (wheel, docker, etc.).";
|
||||||
|
};
|
||||||
|
hashedPassword = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "!";
|
||||||
|
description = "Hashed password for the user account. Default '!' means locked.";
|
||||||
|
};
|
||||||
|
extraPackages = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.package;
|
||||||
|
default = [ ];
|
||||||
|
description = "Additional system packages available to this user.";
|
||||||
|
};
|
||||||
|
excludePackages = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.package;
|
||||||
|
default = [ ];
|
||||||
|
description = "System packages to exclude for this user.";
|
||||||
|
};
|
||||||
|
homePackages = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.package;
|
||||||
|
default = [ ];
|
||||||
|
description = "Packages to install in the user's home-manager profile.";
|
||||||
|
};
|
||||||
|
extraImports = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.path;
|
||||||
|
default = [ ];
|
||||||
|
description = "Additional home-manager modules to import for this user.";
|
||||||
|
};
|
||||||
|
external = lib.mkOption {
|
||||||
|
type = lib.types.nullOr (
|
||||||
|
lib.types.oneOf [
|
||||||
|
lib.types.path
|
||||||
|
(lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
url = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "Git repository URL to fetch user configuration from.";
|
||||||
|
};
|
||||||
|
rev = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "Git commit hash, tag, or branch to fetch.";
|
||||||
|
};
|
||||||
|
submodules = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Whether to fetch Git submodules.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
]
|
||||||
|
);
|
||||||
|
default = null;
|
||||||
|
description = "External dotfiles repository (user.nix + optional nixos.nix).";
|
||||||
|
};
|
||||||
|
opensshKeys = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "SSH public keys for the user (authorized_keys).";
|
||||||
|
};
|
||||||
|
shell = lib.mkOption {
|
||||||
|
type = lib.types.nullOr (
|
||||||
|
lib.types.enum [
|
||||||
|
"bash"
|
||||||
|
"zsh"
|
||||||
|
"fish"
|
||||||
|
"tcsh"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
default = "bash";
|
||||||
|
description = "Default shell for the user.";
|
||||||
|
};
|
||||||
|
editor = lib.mkOption {
|
||||||
|
type = lib.types.nullOr (
|
||||||
|
lib.types.enum [
|
||||||
|
"vim"
|
||||||
|
"neovim"
|
||||||
|
"emacs"
|
||||||
|
"nano"
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
default = "neovim";
|
||||||
|
description = "Default text editor for the user (sets EDITOR).";
|
||||||
|
};
|
||||||
|
useZshTheme = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Whether to apply the system Zsh theme (Oh My Posh).";
|
||||||
|
};
|
||||||
|
useNvimPlugins = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Whether to apply the system Neovim configuration.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./fs.nix
|
||||||
|
./boot.nix
|
||||||
|
./user-config.nix
|
||||||
|
../sw
|
||||||
|
inputs.vscode-server.nixosModules.default
|
||||||
|
inputs.nixos-wsl.nixosModules.default
|
||||||
|
]
|
||||||
|
++ hwModules;
|
||||||
|
|
||||||
|
options.athenix.users = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf userSubmodule;
|
||||||
|
default = { };
|
||||||
|
description = "User accounts configuration. Set enable=true for users that should exist on this system.";
|
||||||
|
};
|
||||||
|
|
||||||
|
options.athenix = {
|
||||||
|
forUser = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Convenience option to configure a host for a specific user.
|
||||||
|
|
||||||
|
When set, automatically:
|
||||||
|
- Enables the user account (athenix.users.<username>.enable = true)
|
||||||
|
- Sets as default WSL user (on WSL systems)
|
||||||
|
|
||||||
|
The username must exist in athenix.users (defined in users.nix).
|
||||||
|
'';
|
||||||
|
example = "engr-ugaif";
|
||||||
|
};
|
||||||
|
host.useHostPrefix = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Whether to prepend the hardware type prefix to the hostname.
|
||||||
|
|
||||||
|
When true:
|
||||||
|
- "nix-laptop" with device "1" → hostname "nix-laptop1"
|
||||||
|
- "nix-wsl" with device "alice" → hostname "nix-wsl-alice"
|
||||||
|
|
||||||
|
When false:
|
||||||
|
- Device name becomes the full hostname (useful for custom names)
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkMerge [
|
||||||
|
(lib.mkIf (config.athenix.forUser != null) {
|
||||||
|
athenix.users.${config.athenix.forUser}.enable = true;
|
||||||
|
})
|
||||||
|
{
|
||||||
|
system.stateVersion = "25.11";
|
||||||
|
nix.settings.experimental-features = [
|
||||||
|
"nix-command"
|
||||||
|
"flakes"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -1,36 +1,25 @@
|
|||||||
{
|
{
|
||||||
inputs,
|
inputs,
|
||||||
hosts ? import ../inventory.nix,
|
lib,
|
||||||
|
config,
|
||||||
|
self ? null,
|
||||||
|
users ? { },
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Host Generator
|
# Fleet Generator
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# This file contains the logic to generate NixOS configurations for all hosts
|
# This file contains the logic to generate NixOS configurations for all hosts
|
||||||
# defined in inventory.nix. It supports both hostname-based and count-based
|
# defined in inventory.nix. It supports both hostname-based and count-based
|
||||||
# configurations with flexible type associations.
|
# configurations with flexible type associations.
|
||||||
#
|
|
||||||
# Inventory format:
|
|
||||||
# {
|
|
||||||
# "my-hostname" = {
|
|
||||||
# type = "nix-desktop"; # Host type module to use
|
|
||||||
# system = "x86_64-linux"; # Optional
|
|
||||||
# # ... any athenix.* options or device-specific config
|
|
||||||
# };
|
|
||||||
#
|
|
||||||
# "lab-prefix" = {
|
|
||||||
# type = "nix-laptop";
|
|
||||||
# count = 5; # Generates lab-prefix1, lab-prefix2, ... lab-prefix5
|
|
||||||
# devices = {
|
|
||||||
# "machine-1" = { ... }; # Override for lab-prefix1
|
|
||||||
# };
|
|
||||||
# };
|
|
||||||
# }
|
|
||||||
|
|
||||||
let
|
let
|
||||||
nixpkgs = inputs.nixpkgs;
|
|
||||||
lib = nixpkgs.lib;
|
# Evaluate inventory to get fleet data
|
||||||
|
# Import fleet-option.nix (defines athenix.fleet) and inventory.nix (sets values)
|
||||||
|
# We use a minimal module here to avoid circular dependencies from common.nix's imports
|
||||||
|
|
||||||
# Helper to create a single NixOS system configuration
|
# Helper to create a single NixOS system configuration
|
||||||
mkHost =
|
mkHost =
|
||||||
{
|
{
|
||||||
@@ -38,13 +27,42 @@ let
|
|||||||
system ? "x86_64-linux",
|
system ? "x86_64-linux",
|
||||||
hostType,
|
hostType,
|
||||||
configOverrides ? { },
|
configOverrides ? { },
|
||||||
externalModulePath ? null,
|
externalModuleThunk ? null,
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
|
# Lazy evaluation: only fetch external module when building this host
|
||||||
|
externalModulePath =
|
||||||
|
if externalModuleThunk != null then
|
||||||
|
let
|
||||||
|
# Force evaluation of the thunk
|
||||||
|
fetchedPath =
|
||||||
|
if
|
||||||
|
builtins.isAttrs externalModuleThunk
|
||||||
|
&& externalModuleThunk ? _type
|
||||||
|
&& externalModuleThunk._type == "lazy-fetchGit"
|
||||||
|
then
|
||||||
|
# New format: lazy fetchGit - only execute when needed
|
||||||
|
(builtins.fetchGit {
|
||||||
|
inherit (externalModuleThunk) url rev submodules;
|
||||||
|
}).outPath
|
||||||
|
else
|
||||||
|
# Legacy: pre-fetched derivation or path
|
||||||
|
externalModuleThunk;
|
||||||
|
# Extract outPath from fetchGit/fetchTarball results
|
||||||
|
extractedPath =
|
||||||
|
if builtins.isAttrs fetchedPath && fetchedPath ? outPath then fetchedPath.outPath else fetchedPath;
|
||||||
|
in
|
||||||
|
if builtins.isPath extractedPath then
|
||||||
|
extractedPath + "/default.nix"
|
||||||
|
else if lib.isDerivation extractedPath then
|
||||||
|
extractedPath + "/default.nix"
|
||||||
|
else
|
||||||
|
extractedPath + "/default.nix"
|
||||||
|
else
|
||||||
|
null;
|
||||||
|
|
||||||
# Load users.nix to find external user modules
|
# Load users.nix to find external user modules
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
accounts = config.athenix.users or { };
|
||||||
usersData = import ../users.nix { inherit pkgs; };
|
|
||||||
accounts = usersData.athenix.users or { };
|
|
||||||
|
|
||||||
# Build a map of user names to their nixos module paths (if they exist)
|
# Build a map of user names to their nixos module paths (if they exist)
|
||||||
# We'll use this to conditionally import modules based on user.enable
|
# We'll use this to conditionally import modules based on user.enable
|
||||||
@@ -53,10 +71,19 @@ let
|
|||||||
name: user:
|
name: user:
|
||||||
if (user ? external && user.external != null) then
|
if (user ? external && user.external != null) then
|
||||||
let
|
let
|
||||||
|
# Resolve external path (lazy fetchGit if needed)
|
||||||
externalPath =
|
externalPath =
|
||||||
if builtins.isAttrs user.external && user.external ? outPath then
|
if builtins.isAttrs user.external && user.external ? url && user.external ? rev then
|
||||||
|
# New format: lazy fetchGit
|
||||||
|
(builtins.fetchGit {
|
||||||
|
inherit (user.external) url rev;
|
||||||
|
submodules = user.external.submodules or false;
|
||||||
|
}).outPath
|
||||||
|
else if builtins.isAttrs user.external && user.external ? outPath then
|
||||||
|
# Legacy: pre-fetched
|
||||||
user.external.outPath
|
user.external.outPath
|
||||||
else
|
else
|
||||||
|
# Direct path
|
||||||
user.external;
|
user.external;
|
||||||
nixosModulePath = externalPath + "/nixos.nix";
|
nixosModulePath = externalPath + "/nixos.nix";
|
||||||
in
|
in
|
||||||
@@ -94,14 +121,6 @@ let
|
|||||||
}
|
}
|
||||||
) userNixosModulePaths;
|
) userNixosModulePaths;
|
||||||
|
|
||||||
# Load the host type module
|
|
||||||
typeFile = ./types + "/${hostType}.nix";
|
|
||||||
typeModule =
|
|
||||||
if builtins.pathExists typeFile then
|
|
||||||
import typeFile { inherit inputs; }
|
|
||||||
else
|
|
||||||
throw "Host type '${hostType}' not found in hosts/types/";
|
|
||||||
|
|
||||||
# External module from fetchGit/fetchurl
|
# External module from fetchGit/fetchurl
|
||||||
externalPathModule =
|
externalPathModule =
|
||||||
if externalModulePath != null then import externalModulePath { inherit inputs; } else { };
|
if externalModulePath != null then import externalModulePath { inherit inputs; } else { };
|
||||||
@@ -129,19 +148,35 @@ let
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Hardware-specific external modules
|
||||||
|
hwSpecificModules =
|
||||||
|
lib.optional (hostType == "nix-lxc")
|
||||||
|
"${inputs.nixpkgs.legacyPackages.${system}.path}/nixos/modules/virtualisation/proxmox-lxc.nix";
|
||||||
|
|
||||||
allModules =
|
allModules =
|
||||||
userNixosModules
|
userNixosModules
|
||||||
++ [
|
++ [
|
||||||
typeModule
|
./common.nix
|
||||||
overrideModule
|
overrideModule
|
||||||
{ networking.hostName = hostName; }
|
{ networking.hostName = hostName; }
|
||||||
|
# Set athenix.host.name for secrets and other modules to use
|
||||||
|
{ athenix.host.name = hostName; }
|
||||||
|
{
|
||||||
|
# Inject user definitions from flake-parts level
|
||||||
|
config.athenix.users = lib.mapAttrs (_: user: lib.mapAttrs (_: lib.mkDefault) user) users;
|
||||||
|
}
|
||||||
|
# Enable the appropriate hardware module based on hostType
|
||||||
|
{ config.athenix.hw.${hostType}.enable = lib.mkDefault true; }
|
||||||
]
|
]
|
||||||
|
++ hwSpecificModules
|
||||||
++ lib.optional (externalModulePath != null) externalPathModule;
|
++ lib.optional (externalModulePath != null) externalPathModule;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
system = lib.nixosSystem {
|
system = lib.nixosSystem {
|
||||||
inherit system;
|
inherit system;
|
||||||
specialArgs = { inherit inputs; };
|
specialArgs = {
|
||||||
|
inputs = if self != null then inputs // { inherit self; } else inputs;
|
||||||
|
};
|
||||||
modules = allModules;
|
modules = allModules;
|
||||||
};
|
};
|
||||||
modules = allModules;
|
modules = allModules;
|
||||||
@@ -153,8 +188,6 @@ let
|
|||||||
let
|
let
|
||||||
hostType = config.type or prefix;
|
hostType = config.type or prefix;
|
||||||
system = config.system or "x86_64-linux";
|
system = config.system or "x86_64-linux";
|
||||||
devices = config.devices or { };
|
|
||||||
hasCount = config ? count;
|
|
||||||
|
|
||||||
# Helper to generate hostname from prefix and suffix
|
# Helper to generate hostname from prefix and suffix
|
||||||
# Numbers get no dash: "nix-surface1", "nix-surface2"
|
# Numbers get no dash: "nix-surface1", "nix-surface2"
|
||||||
@@ -192,48 +225,38 @@ let
|
|||||||
lib.mapAttrsToList (
|
lib.mapAttrsToList (
|
||||||
deviceKey: deviceConfig:
|
deviceKey: deviceConfig:
|
||||||
let
|
let
|
||||||
# Check if deviceConfig is a path/derivation (from fetchGit, fetchurl, etc.)
|
# Check if deviceConfig has an 'external' field for lazy evaluation
|
||||||
# fetchGit/fetchTarball return an attrset with outPath attribute
|
hasExternalField = builtins.isAttrs deviceConfig && deviceConfig ? external;
|
||||||
isExternalModule =
|
|
||||||
(builtins.isPath deviceConfig)
|
|
||||||
|| (builtins.isString deviceConfig && lib.hasPrefix "/" deviceConfig)
|
|
||||||
|| (lib.isDerivation deviceConfig)
|
|
||||||
|| (builtins.isAttrs deviceConfig && deviceConfig ? outPath);
|
|
||||||
|
|
||||||
# Extract the actual path from fetchGit/fetchTarball results
|
# Extract external module spec (don't evaluate fetchGit yet!)
|
||||||
extractedPath =
|
externalModuleThunk =
|
||||||
if builtins.isAttrs deviceConfig && deviceConfig ? outPath then
|
if hasExternalField then
|
||||||
deviceConfig.outPath
|
let
|
||||||
|
ext = deviceConfig.external;
|
||||||
|
in
|
||||||
|
# New format: { url, rev, submodules? } - create lazy fetchGit thunk
|
||||||
|
if builtins.isAttrs ext && ext ? url && ext ? rev then
|
||||||
|
{
|
||||||
|
_type = "lazy-fetchGit";
|
||||||
|
inherit (ext) url rev;
|
||||||
|
submodules = ext.submodules or false;
|
||||||
|
}
|
||||||
|
# Legacy: pre-fetched or path
|
||||||
|
else
|
||||||
|
ext
|
||||||
else
|
else
|
||||||
deviceConfig;
|
null;
|
||||||
|
|
||||||
# If external module, we use base config + overrides as the config
|
# Remove 'external' from config to avoid conflicts
|
||||||
# and pass the module path separately
|
cleanDeviceConfig =
|
||||||
actualConfig =
|
if hasExternalField then lib.removeAttrs deviceConfig [ "external" ] else deviceConfig;
|
||||||
if isExternalModule then (lib.recursiveUpdate baseConfig overrides) else deviceConfig;
|
|
||||||
|
|
||||||
# Merge: base config -> overrides -> device-specific config (only if not external module)
|
# Merge: base config -> overrides -> device-specific config
|
||||||
mergedConfig =
|
mergedConfig = lib.recursiveUpdate (lib.recursiveUpdate baseConfig overrides) cleanDeviceConfig;
|
||||||
if isExternalModule then
|
|
||||||
actualConfig
|
|
||||||
else
|
|
||||||
lib.recursiveUpdate (lib.recursiveUpdate baseConfig overrides) deviceConfig;
|
|
||||||
|
|
||||||
# Check useHostPrefix from the merged config
|
# Check useHostPrefix from the merged config
|
||||||
usePrefix = mergedConfig.athenix.host.useHostPrefix or true;
|
usePrefix = mergedConfig.athenix.host.useHostPrefix or true;
|
||||||
hostName = mkHostName prefix deviceKey usePrefix;
|
hostName = mkHostName prefix deviceKey usePrefix;
|
||||||
|
|
||||||
# If external module, also add a default.nix path for import
|
|
||||||
externalModulePath =
|
|
||||||
if isExternalModule then
|
|
||||||
if builtins.isPath extractedPath then
|
|
||||||
extractedPath + "/default.nix"
|
|
||||||
else if lib.isDerivation extractedPath then
|
|
||||||
extractedPath + "/default.nix"
|
|
||||||
else
|
|
||||||
extractedPath + "/default.nix"
|
|
||||||
else
|
|
||||||
null;
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
name = hostName;
|
name = hostName;
|
||||||
@@ -242,7 +265,7 @@ let
|
|||||||
hostName
|
hostName
|
||||||
system
|
system
|
||||||
hostType
|
hostType
|
||||||
externalModulePath
|
externalModuleThunk
|
||||||
;
|
;
|
||||||
configOverrides = mergedConfig;
|
configOverrides = mergedConfig;
|
||||||
};
|
};
|
||||||
@@ -289,10 +312,12 @@ let
|
|||||||
{ };
|
{ };
|
||||||
in
|
in
|
||||||
lib.recursiveUpdate deviceHosts countHosts
|
lib.recursiveUpdate deviceHosts countHosts
|
||||||
) hosts;
|
);
|
||||||
|
|
||||||
|
fleetData = config.athenix.fleet;
|
||||||
|
|
||||||
# Flatten the nested structure
|
# Flatten the nested structure
|
||||||
allHosts = lib.foldl' lib.recursiveUpdate { } (lib.attrValues processInventory);
|
allHosts = lib.foldl' lib.recursiveUpdate { } (lib.attrValues (processInventory fleetData));
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
nixosConfigurations = lib.mapAttrs (n: v: v.system) allHosts;
|
nixosConfigurations = lib.mapAttrs (n: v: v.system) allHosts;
|
||||||
227
fleet/fleet-option.nix
Normal file
227
fleet/fleet-option.nix
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# Fleet Option Definition
|
||||||
|
# ============================================================================
|
||||||
|
# This module defines the athenix.fleet and athenix.hwTypes options.
|
||||||
|
# Self-contained fleet management without dependencies on user configuration.
|
||||||
|
{ inputs, lib, ... }:
|
||||||
|
let
|
||||||
|
fleetDefinition = lib.mkOption {
|
||||||
|
description = "Hardware types definitions for the fleet.";
|
||||||
|
type = lib.types.attrsOf (
|
||||||
|
lib.types.submodule (
|
||||||
|
{ name, ... }:
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
type = lib.mkOption {
|
||||||
|
type = lib.types.oneOf [
|
||||||
|
lib.types.str
|
||||||
|
lib.types.listOf
|
||||||
|
lib.types.str
|
||||||
|
];
|
||||||
|
default = name;
|
||||||
|
description = "Type(s) of system configuration for this device.";
|
||||||
|
};
|
||||||
|
system = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "x86_64-linux";
|
||||||
|
description = "NixOS system architecture for this hardware type.";
|
||||||
|
};
|
||||||
|
devices = lib.mkOption {
|
||||||
|
type = lib.types.oneOf [
|
||||||
|
lib.types.int
|
||||||
|
(lib.types.attrsOf (
|
||||||
|
lib.types.submodule (
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
freeformType = lib.types.attrs;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
))
|
||||||
|
];
|
||||||
|
};
|
||||||
|
count = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 0;
|
||||||
|
description = "Number of devices of this type to create.";
|
||||||
|
};
|
||||||
|
defaultCount = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 0;
|
||||||
|
description = "Default number of devices to create with default configurations and numbered hostnames.";
|
||||||
|
};
|
||||||
|
overrides = lib.mkOption {
|
||||||
|
type = lib.types.attrs;
|
||||||
|
default = { };
|
||||||
|
description = "Overrides to apply to all devices of this type.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
# Forward declaration for user options (full definition in user-config.nix)
|
||||||
|
# This allows users.nix to be evaluated at flake level
|
||||||
|
userSubmodule = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
enable = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Whether this user account is enabled on this system.";
|
||||||
|
};
|
||||||
|
isNormalUser = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Whether this is a normal user account (vs system user).";
|
||||||
|
};
|
||||||
|
description = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Full name or description of the user (GECOS field).";
|
||||||
|
example = "John Doe";
|
||||||
|
};
|
||||||
|
extraGroups = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "Additional groups for the user (wheel, docker, etc.).";
|
||||||
|
example = [
|
||||||
|
"wheel"
|
||||||
|
"networkmanager"
|
||||||
|
"docker"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
hashedPassword = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "!";
|
||||||
|
description = ''
|
||||||
|
Hashed password for the user account.
|
||||||
|
Generate with: mkpasswd -m sha-512
|
||||||
|
Default "!" means account is locked (SSH key only).
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
extraPackages = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.package;
|
||||||
|
default = [ ];
|
||||||
|
description = "Additional system packages available to this user.";
|
||||||
|
example = lib.literalExpression "[ pkgs.vim pkgs.git ]";
|
||||||
|
};
|
||||||
|
excludePackages = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.package;
|
||||||
|
default = [ ];
|
||||||
|
description = "System packages to exclude for this user.";
|
||||||
|
};
|
||||||
|
homePackages = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.package;
|
||||||
|
default = [ ];
|
||||||
|
description = "Packages to install in the user's home-manager profile.";
|
||||||
|
example = lib.literalExpression "[ pkgs.firefox pkgs.vscode ]";
|
||||||
|
};
|
||||||
|
extraImports = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.path;
|
||||||
|
default = [ ];
|
||||||
|
description = "Additional home-manager modules to import for this user.";
|
||||||
|
};
|
||||||
|
external = lib.mkOption {
|
||||||
|
type = lib.types.nullOr (
|
||||||
|
lib.types.oneOf [
|
||||||
|
lib.types.path
|
||||||
|
(lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
url = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "Git repository URL to fetch user configuration from.";
|
||||||
|
example = "https://github.com/username/dotfiles";
|
||||||
|
};
|
||||||
|
rev = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = "Git commit hash, tag, or branch to fetch.";
|
||||||
|
example = "abc123def456...";
|
||||||
|
};
|
||||||
|
submodules = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Whether to fetch Git submodules.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
]
|
||||||
|
);
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
External user configuration module from Git or local path.
|
||||||
|
|
||||||
|
Can be either:
|
||||||
|
- A local path: /path/to/config
|
||||||
|
- A Git repository: { url = "..."; rev = "..."; submodules? = false; }
|
||||||
|
|
||||||
|
The Git repository is only fetched when the user is actually enabled.
|
||||||
|
|
||||||
|
Should contain user.nix (user options + home-manager config)
|
||||||
|
and optionally nixos.nix (system-level config).
|
||||||
|
'';
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
{
|
||||||
|
url = "https://github.com/username/dotfiles";
|
||||||
|
rev = "abc123def456789abcdef0123456789abcdef012";
|
||||||
|
submodules = false;
|
||||||
|
}'';
|
||||||
|
};
|
||||||
|
opensshKeys = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = "SSH public keys for the user (authorized_keys).";
|
||||||
|
example = [ "ssh-ed25519 AAAAC3Nza... user@host" ];
|
||||||
|
};
|
||||||
|
shell = lib.mkOption {
|
||||||
|
type = lib.types.nullOr (
|
||||||
|
lib.types.enum [
|
||||||
|
"bash"
|
||||||
|
"zsh"
|
||||||
|
"fish"
|
||||||
|
"tcsh"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
default = "bash";
|
||||||
|
description = "Default shell for the user.";
|
||||||
|
};
|
||||||
|
editor = lib.mkOption {
|
||||||
|
type = lib.types.nullOr (
|
||||||
|
lib.types.enum [
|
||||||
|
"vim"
|
||||||
|
"neovim"
|
||||||
|
"emacs"
|
||||||
|
"nano"
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
default = "neovim";
|
||||||
|
description = "Default text editor for the user (sets EDITOR).";
|
||||||
|
};
|
||||||
|
useZshTheme = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Whether to apply the system Zsh theme (Oh My Posh).";
|
||||||
|
};
|
||||||
|
useNvimPlugins = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Whether to apply the system Neovim configuration.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.athenix = {
|
||||||
|
fleet = fleetDefinition;
|
||||||
|
hwTypes = lib.mkOption {
|
||||||
|
description = "Hardware types definitions for the fleet.";
|
||||||
|
type = lib.types.attrs;
|
||||||
|
};
|
||||||
|
users = lib.mkOption {
|
||||||
|
type = lib.types.attrsOf userSubmodule;
|
||||||
|
description = "User accounts configuration. Set enable=true for users that should exist on this system.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config.athenix.hwTypes = lib.mkDefault (import ../hw { inherit inputs; });
|
||||||
|
}
|
||||||
132
fleet/fs.nix
Normal file
132
fleet/fs.nix
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# FS & Storage Configuration
|
||||||
|
# ============================================================================
|
||||||
|
# This module defines:
|
||||||
|
# - Disko partition layout (EFI, swap, root)
|
||||||
|
# - Filesystem options (device, swap size)
|
||||||
|
#
|
||||||
|
# Only applies to systems with physical disk management needs
|
||||||
|
# (not containers, not WSL, not systems without a configured device)
|
||||||
|
|
||||||
|
{ config, lib, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.athenix.host.filesystem;
|
||||||
|
# Only enable disk config if device is set and disko is enabled
|
||||||
|
hasDiskConfig = cfg.device != null && config.disko.enableConfig;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.athenix = {
|
||||||
|
host = {
|
||||||
|
name = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = ''
|
||||||
|
Fleet-assigned hostname for this system.
|
||||||
|
Used for secrets discovery and other host-specific configurations.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
filesystem = {
|
||||||
|
device = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
The main disk device to use for automated partitioning and installation.
|
||||||
|
|
||||||
|
When set, enables disko for declarative disk management with:
|
||||||
|
- 1GB EFI boot partition
|
||||||
|
- Optional swap partition (see swapSize)
|
||||||
|
- Root partition using remaining space
|
||||||
|
|
||||||
|
Leave null for systems that don't need disk partitioning (containers, WSL).
|
||||||
|
'';
|
||||||
|
example = "/dev/nvme0n1";
|
||||||
|
};
|
||||||
|
useSwap = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Whether to create and use a swap partition.
|
||||||
|
Disable for systems with ample RAM or SSDs where swap is undesirable.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
swapSize = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.str;
|
||||||
|
default = null;
|
||||||
|
description = ''
|
||||||
|
Size of the swap partition (e.g., "16G", "32G").
|
||||||
|
|
||||||
|
Recommended sizes:
|
||||||
|
- 8-16GB for desktops with 16GB+ RAM
|
||||||
|
- 32GB for laptops (enables hibernation)
|
||||||
|
- Match RAM size for systems <8GB RAM
|
||||||
|
'';
|
||||||
|
example = "32G";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = lib.mkMerge [
|
||||||
|
{
|
||||||
|
# ========== Disk Partitioning (Disko) ==========
|
||||||
|
disko.enableConfig = lib.mkDefault (cfg.device != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
(lib.mkIf hasDiskConfig {
|
||||||
|
disko.devices = {
|
||||||
|
disk.main = {
|
||||||
|
type = "disk";
|
||||||
|
device = cfg.device;
|
||||||
|
content = {
|
||||||
|
type = "gpt";
|
||||||
|
partitions = {
|
||||||
|
# EFI System Partition
|
||||||
|
ESP = {
|
||||||
|
name = "ESP";
|
||||||
|
label = "BOOT";
|
||||||
|
size = "1G";
|
||||||
|
type = "EF00";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "vfat";
|
||||||
|
mountpoint = "/boot";
|
||||||
|
mountOptions = [ "umask=0077" ];
|
||||||
|
extraArgs = [
|
||||||
|
"-n"
|
||||||
|
"BOOT"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Swap Partition (size configurable per host)
|
||||||
|
swap = lib.mkIf cfg.useSwap {
|
||||||
|
name = "swap";
|
||||||
|
label = "swap";
|
||||||
|
size = cfg.swapSize;
|
||||||
|
content = {
|
||||||
|
type = "swap";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Root Partition (takes remaining space)
|
||||||
|
root = {
|
||||||
|
name = "root";
|
||||||
|
label = "root";
|
||||||
|
size = "100%";
|
||||||
|
content = {
|
||||||
|
type = "filesystem";
|
||||||
|
format = "ext4";
|
||||||
|
mountpoint = "/";
|
||||||
|
extraArgs = [
|
||||||
|
"-L"
|
||||||
|
"ROOT"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
];
|
||||||
|
}
|
||||||
151
fleet/user-config.nix
Normal file
151
fleet/user-config.nix
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
inputs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# User Configuration Module
|
||||||
|
# ============================================================================
|
||||||
|
# This module implements user account creation and home-manager setup.
|
||||||
|
# Options are defined in fleet-option.nix for early availability.
|
||||||
|
|
||||||
|
let
|
||||||
|
# Helper: Resolve external module path (with lazy Git fetching)
|
||||||
|
resolveExternalPath =
|
||||||
|
external:
|
||||||
|
if external == null then
|
||||||
|
null
|
||||||
|
# New format: { url, rev, submodules? } - only fetch when needed
|
||||||
|
else if builtins.isAttrs external && external ? url && external ? rev then
|
||||||
|
(builtins.fetchGit {
|
||||||
|
inherit (external) url rev;
|
||||||
|
submodules = external.submodules or false;
|
||||||
|
}).outPath
|
||||||
|
# Legacy: pre-fetched derivation/package
|
||||||
|
else if builtins.isAttrs external && external ? outPath then
|
||||||
|
external.outPath
|
||||||
|
# Direct path
|
||||||
|
else
|
||||||
|
external;
|
||||||
|
|
||||||
|
# Helper: Check if path exists and is valid
|
||||||
|
isValidPath =
|
||||||
|
path:
|
||||||
|
path != null
|
||||||
|
&& (builtins.isPath path || (builtins.isString path && lib.hasPrefix "/" path))
|
||||||
|
&& builtins.pathExists path;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
config = {
|
||||||
|
|
||||||
|
# Generate NixOS users
|
||||||
|
users.users =
|
||||||
|
let
|
||||||
|
enabledAccounts = lib.filterAttrs (_: user: user.enable) config.athenix.users;
|
||||||
|
in
|
||||||
|
lib.mapAttrs (
|
||||||
|
name: user:
|
||||||
|
let
|
||||||
|
isPlasma6 = config.services.desktopManager.plasma6.enable;
|
||||||
|
defaultPackages = lib.optionals (isPlasma6 && name != "root") [ pkgs.kdePackages.kate ];
|
||||||
|
finalPackages = lib.subtractLists user.excludePackages (defaultPackages ++ user.extraPackages);
|
||||||
|
shells = {
|
||||||
|
bash = pkgs.bash;
|
||||||
|
zsh = pkgs.zsh;
|
||||||
|
fish = pkgs.fish;
|
||||||
|
tcsh = pkgs.tcsh;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
rec {
|
||||||
|
isNormalUser = user.isNormalUser;
|
||||||
|
inherit (user) extraGroups hashedPassword;
|
||||||
|
description = if user.description != null then user.description else lib.mkDefault "";
|
||||||
|
openssh.authorizedKeys.keys = user.opensshKeys;
|
||||||
|
shell = if user.shell != null then shells.${user.shell} else pkgs.bash;
|
||||||
|
packages = finalPackages ++ [ shell ];
|
||||||
|
}
|
||||||
|
) enabledAccounts;
|
||||||
|
|
||||||
|
# Home Manager configs per user
|
||||||
|
home-manager = {
|
||||||
|
useGlobalPkgs = true;
|
||||||
|
useUserPackages = true;
|
||||||
|
extraSpecialArgs = {
|
||||||
|
osConfig = config;
|
||||||
|
inherit inputs;
|
||||||
|
};
|
||||||
|
|
||||||
|
users =
|
||||||
|
let
|
||||||
|
enabledAccounts = lib.filterAttrs (_: user: user.enable) config.athenix.users;
|
||||||
|
in
|
||||||
|
lib.mapAttrs (
|
||||||
|
name: user:
|
||||||
|
let
|
||||||
|
# Resolve external module paths
|
||||||
|
hasExternal = user.external != null;
|
||||||
|
externalPath = resolveExternalPath user.external;
|
||||||
|
userNixPath = if externalPath != null then externalPath + "/user.nix" else null;
|
||||||
|
hasExternalUser = isValidPath userNixPath;
|
||||||
|
|
||||||
|
# Import external user.nix for home-manager (filter out athenix.* options)
|
||||||
|
externalUserModule =
|
||||||
|
if hasExternalUser then
|
||||||
|
let
|
||||||
|
fullModule = import userNixPath { inherit inputs; };
|
||||||
|
in
|
||||||
|
# Only pass through non-athenix options to home-manager
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
osConfig,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
evaluated = fullModule {
|
||||||
|
inherit
|
||||||
|
config
|
||||||
|
lib
|
||||||
|
pkgs
|
||||||
|
osConfig
|
||||||
|
;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
lib.filterAttrs (attrName: _: attrName != "athenix") evaluated
|
||||||
|
else
|
||||||
|
{ };
|
||||||
|
|
||||||
|
# Common imports based on user flags
|
||||||
|
commonImports = lib.optional user.useZshTheme ../sw/theme.nix ++ [
|
||||||
|
(import ../sw/nvim.nix { inherit user; })
|
||||||
|
];
|
||||||
|
|
||||||
|
# Build imports list
|
||||||
|
allImports = user.extraImports ++ commonImports ++ lib.optional hasExternalUser externalUserModule;
|
||||||
|
in
|
||||||
|
lib.mkMerge [
|
||||||
|
{
|
||||||
|
imports = allImports;
|
||||||
|
|
||||||
|
# Always set these required options
|
||||||
|
home.username = name;
|
||||||
|
home.homeDirectory = lib.mkOverride 999 (if name == "root" then "/root" else "/home/${name}");
|
||||||
|
home.stateVersion = "25.11";
|
||||||
|
programs.${user.editor} = {
|
||||||
|
enable = true;
|
||||||
|
defaultEditor = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
(lib.mkIf (!hasExternal) {
|
||||||
|
# For local users only, add their packages
|
||||||
|
home.packages = user.homePackages;
|
||||||
|
})
|
||||||
|
]
|
||||||
|
) enabledAccounts;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
189
hosts/boot.nix
189
hosts/boot.nix
@@ -1,189 +0,0 @@
|
|||||||
# ============================================================================
|
|
||||||
# Boot & Storage Configuration
|
|
||||||
# ============================================================================
|
|
||||||
# This module defines:
|
|
||||||
# - Disko partition layout (EFI, swap, root)
|
|
||||||
# - Bootloader configuration (systemd-boot with Plymouth)
|
|
||||||
# - Filesystem options (device, swap size)
|
|
||||||
# - Build method options (ISO, iPXE, LXC, Proxmox)
|
|
||||||
# - Garbage collection settings
|
|
||||||
# - Convenience options (forUser, useHostPrefix)
|
|
||||||
|
|
||||||
{ config, lib, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
options.athenix = {
|
|
||||||
forUser = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.str;
|
|
||||||
default = null;
|
|
||||||
description = ''
|
|
||||||
Convenience option to configure a host for a specific user.
|
|
||||||
Automatically enables the user (sets athenix.users.username.enable = true).
|
|
||||||
Value should be a username from athenix.users.accounts.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
host = {
|
|
||||||
useHostPrefix = lib.mkOption {
|
|
||||||
type = lib.types.bool;
|
|
||||||
default = true;
|
|
||||||
description = "Whether to prepend the host prefix to the hostname (used in inventory).";
|
|
||||||
};
|
|
||||||
filesystem = {
|
|
||||||
device = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
description = "The main disk device to use for installation.";
|
|
||||||
};
|
|
||||||
swapSize = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
description = "The size of the swap partition.";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
buildMethods = lib.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.str;
|
|
||||||
default = [ "installer-iso" ];
|
|
||||||
description = ''
|
|
||||||
List of allowed build methods for this host.
|
|
||||||
Supported methods:
|
|
||||||
- "installer-iso": Generates an auto-install ISO that installs this configuration to disk.
|
|
||||||
- "iso": Generates a live ISO (using nixos-generators).
|
|
||||||
- "ipxe": Generates iPXE netboot artifacts (kernel, initrd, script).
|
|
||||||
- "lxc": Generates an LXC container tarball.
|
|
||||||
- "proxmox": Generates a Proxmox VMA archive.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
system.gc = {
|
|
||||||
enable = lib.mkOption {
|
|
||||||
type = lib.types.bool;
|
|
||||||
default = true;
|
|
||||||
description = "Whether to enable automatic garbage collection.";
|
|
||||||
};
|
|
||||||
frequency = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "weekly";
|
|
||||||
description = "How often to run garbage collection (systemd timer format).";
|
|
||||||
};
|
|
||||||
retentionDays = lib.mkOption {
|
|
||||||
type = lib.types.int;
|
|
||||||
default = 30;
|
|
||||||
description = "Number of days to keep old generations before deletion.";
|
|
||||||
};
|
|
||||||
optimise = lib.mkOption {
|
|
||||||
type = lib.types.bool;
|
|
||||||
default = true;
|
|
||||||
description = "Whether to automatically optimize the Nix store.";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = lib.mkMerge [
|
|
||||||
# Enable forUser if specified
|
|
||||||
(lib.mkIf (config.athenix.forUser != null) {
|
|
||||||
athenix.users.${config.athenix.forUser}.enable = true;
|
|
||||||
})
|
|
||||||
|
|
||||||
# Main configuration
|
|
||||||
{
|
|
||||||
# ========== Disk Partitioning (Disko) ==========
|
|
||||||
disko.enableConfig = lib.mkDefault true;
|
|
||||||
|
|
||||||
disko.devices = {
|
|
||||||
disk.main = {
|
|
||||||
type = "disk";
|
|
||||||
device = config.athenix.host.filesystem.device;
|
|
||||||
content = {
|
|
||||||
type = "gpt";
|
|
||||||
partitions = {
|
|
||||||
# EFI System Partition
|
|
||||||
ESP = {
|
|
||||||
name = "ESP";
|
|
||||||
label = "BOOT";
|
|
||||||
size = "1G";
|
|
||||||
type = "EF00";
|
|
||||||
content = {
|
|
||||||
type = "filesystem";
|
|
||||||
format = "vfat";
|
|
||||||
mountpoint = "/boot";
|
|
||||||
mountOptions = [ "umask=0077" ];
|
|
||||||
extraArgs = [
|
|
||||||
"-n"
|
|
||||||
"BOOT"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Swap Partition (size configurable per host)
|
|
||||||
swap = {
|
|
||||||
name = "swap";
|
|
||||||
label = "swap";
|
|
||||||
size = config.athenix.host.filesystem.swapSize;
|
|
||||||
content = {
|
|
||||||
type = "swap";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Root Partition (takes remaining space)
|
|
||||||
root = {
|
|
||||||
name = "root";
|
|
||||||
label = "root";
|
|
||||||
size = "100%";
|
|
||||||
content = {
|
|
||||||
type = "filesystem";
|
|
||||||
format = "ext4";
|
|
||||||
mountpoint = "/";
|
|
||||||
extraArgs = [
|
|
||||||
"-L"
|
|
||||||
"ROOT"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# Bootloader Configuration
|
|
||||||
boot = {
|
|
||||||
loader.systemd-boot.enable = true;
|
|
||||||
loader.efi.canTouchEfiVariables = true;
|
|
||||||
plymouth.enable = true;
|
|
||||||
|
|
||||||
# Enable "Silent boot"
|
|
||||||
consoleLogLevel = 3;
|
|
||||||
initrd.verbose = false;
|
|
||||||
|
|
||||||
# Hide the OS choice for bootloaders.
|
|
||||||
# It's still possible to open the bootloader list by pressing any key
|
|
||||||
# It will just not appear on screen unless a key is pressed
|
|
||||||
loader.timeout = lib.mkDefault 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
# Set your time zone.
|
|
||||||
time.timeZone = "America/New_York";
|
|
||||||
|
|
||||||
# Select internationalisation properties.
|
|
||||||
i18n.defaultLocale = "en_US.UTF-8";
|
|
||||||
|
|
||||||
i18n.extraLocaleSettings = {
|
|
||||||
LC_ADDRESS = "en_US.UTF-8";
|
|
||||||
LC_IDENTIFICATION = "en_US.UTF-8";
|
|
||||||
LC_MEASUREMENT = "en_US.UTF-8";
|
|
||||||
LC_MONETARY = "en_US.UTF-8";
|
|
||||||
LC_NAME = "en_US.UTF-8";
|
|
||||||
LC_NUMERIC = "en_US.UTF-8";
|
|
||||||
LC_PAPER = "en_US.UTF-8";
|
|
||||||
LC_TELEPHONE = "en_US.UTF-8";
|
|
||||||
LC_TIME = "en_US.UTF-8";
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.sleep.extraConfig = ''
|
|
||||||
SuspendState=freeze
|
|
||||||
HibernateDelaySec=2h
|
|
||||||
'';
|
|
||||||
|
|
||||||
system.stateVersion = "25.11"; # Did you read the comment?
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
# ============================================================================
|
|
||||||
# Common Modules
|
|
||||||
# ============================================================================
|
|
||||||
# This module contains all the common configuration shared by all host types.
|
|
||||||
# It includes:
|
|
||||||
# - Boot and user configuration
|
|
||||||
# - Software configurations
|
|
||||||
# - User management (users.nix)
|
|
||||||
# - Home Manager integration
|
|
||||||
# - Secret management (agenix)
|
|
||||||
# - Disk partitioning (disko)
|
|
||||||
# - System-wide Nix settings (experimental features, garbage collection)
|
|
||||||
|
|
||||||
{ inputs }:
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
./boot.nix
|
|
||||||
./user-config.nix
|
|
||||||
../sw
|
|
||||||
../users.nix
|
|
||||||
inputs.home-manager.nixosModules.home-manager
|
|
||||||
inputs.agenix.nixosModules.default
|
|
||||||
inputs.disko.nixosModules.disko
|
|
||||||
];
|
|
||||||
|
|
||||||
system.stateVersion = "25.11";
|
|
||||||
|
|
||||||
nix.settings.experimental-features = [
|
|
||||||
"nix-command"
|
|
||||||
"flakes"
|
|
||||||
];
|
|
||||||
|
|
||||||
# Automatic Garbage Collection
|
|
||||||
nix.gc = lib.mkIf config.athenix.system.gc.enable {
|
|
||||||
automatic = true;
|
|
||||||
dates = config.athenix.system.gc.frequency;
|
|
||||||
options = "--delete-older-than ${toString config.athenix.system.gc.retentionDays}d";
|
|
||||||
};
|
|
||||||
|
|
||||||
# Optimize storage
|
|
||||||
nix.optimise.automatic = config.athenix.system.gc.optimise;
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
# ============================================================================
|
|
||||||
# Desktop Configuration
|
|
||||||
# ============================================================================
|
|
||||||
# Hardware and boot configuration for standard desktop workstations.
|
|
||||||
# Includes Intel CPU support and NVMe storage.
|
|
||||||
|
|
||||||
{ inputs, ... }:
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
modulesPath,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
(import ../common.nix { inherit inputs; })
|
|
||||||
(modulesPath + "/installer/scan/not-detected.nix")
|
|
||||||
];
|
|
||||||
|
|
||||||
# ========== Boot Configuration ==========
|
|
||||||
|
|
||||||
boot.initrd.availableKernelModules = [
|
|
||||||
"xhci_pci" # USB 3.0 support
|
|
||||||
"nvme" # NVMe SSD support
|
|
||||||
"usb_storage" # USB storage devices
|
|
||||||
"sd_mod" # SD card support
|
|
||||||
"sdhci_pci" # SD card host controller
|
|
||||||
];
|
|
||||||
boot.initrd.kernelModules = [ ];
|
|
||||||
boot.kernelModules = [ "kvm-intel" ]; # Intel virtualization support
|
|
||||||
boot.extraModulePackages = [ ];
|
|
||||||
boot.kernelParams = [
|
|
||||||
"quiet" # Minimal boot messages
|
|
||||||
"splash" # Show Plymouth boot splash
|
|
||||||
"boot.shell_on_fail" # Emergency shell on boot failure
|
|
||||||
"udev.log_priority=3" # Reduce udev logging
|
|
||||||
"rd.systemd.show_status=auto" # Show systemd status during boot
|
|
||||||
];
|
|
||||||
|
|
||||||
# ========== Filesystem Configuration ==========
|
|
||||||
athenix.host.filesystem.swapSize = lib.mkDefault "16G";
|
|
||||||
athenix.host.filesystem.device = lib.mkDefault "/dev/nvme0n1";
|
|
||||||
athenix.host.buildMethods = lib.mkDefault [ "installer-iso" ];
|
|
||||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
|
||||||
|
|
||||||
# ========== Hardware Configuration ==========
|
|
||||||
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
|
||||||
|
|
||||||
# ========== Software Profile ==========
|
|
||||||
athenix.sw.enable = lib.mkDefault true;
|
|
||||||
athenix.sw.type = lib.mkDefault "desktop";
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
# ============================================================================
|
|
||||||
# Ephemeral/Diskless System Configuration
|
|
||||||
# ============================================================================
|
|
||||||
# Configuration for systems that run entirely from RAM without persistent storage.
|
|
||||||
# Suitable for kiosks, netboot clients, and stateless workstations.
|
|
||||||
# All data is lost on reboot.
|
|
||||||
|
|
||||||
{ inputs, ... }:
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
modulesPath,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
(import ../common.nix { inherit inputs; })
|
|
||||||
(modulesPath + "/installer/scan/not-detected.nix")
|
|
||||||
];
|
|
||||||
|
|
||||||
# ========== Boot Configuration ==========
|
|
||||||
boot.initrd.availableKernelModules = [
|
|
||||||
"xhci_pci" # USB 3.0 support
|
|
||||||
"nvme" # NVMe support
|
|
||||||
"usb_storage" # USB storage devices
|
|
||||||
"sd_mod" # SD card support
|
|
||||||
"sdhci_pci" # SD card host controller
|
|
||||||
];
|
|
||||||
boot.initrd.kernelModules = [ ];
|
|
||||||
boot.kernelModules = [ "kvm-intel" ]; # Intel virtualization support
|
|
||||||
boot.extraModulePackages = [ ];
|
|
||||||
boot.kernelParams = [
|
|
||||||
"quiet" # Minimal boot messages
|
|
||||||
"splash" # Show Plymouth boot splash
|
|
||||||
"boot.shell_on_fail" # Emergency shell on boot failure
|
|
||||||
"udev.log_priority=3" # Reduce udev logging
|
|
||||||
"rd.systemd.show_status=auto" # Show systemd status during boot
|
|
||||||
];
|
|
||||||
|
|
||||||
# ========== Ephemeral Configuration ==========
|
|
||||||
# No persistent storage - everything runs from RAM
|
|
||||||
athenix.host.filesystem.swapSize = lib.mkForce "0G";
|
|
||||||
athenix.host.filesystem.device = lib.mkForce "/dev/null"; # Dummy device
|
|
||||||
athenix.host.buildMethods = lib.mkDefault [
|
|
||||||
"iso" # Live ISO image
|
|
||||||
"ipxe" # Network boot
|
|
||||||
];
|
|
||||||
|
|
||||||
# Disable disk management for RAM-only systems
|
|
||||||
disko.enableConfig = lib.mkForce false;
|
|
||||||
|
|
||||||
# Define tmpfs root filesystem
|
|
||||||
fileSystems."/" = {
|
|
||||||
device = "none";
|
|
||||||
fsType = "tmpfs";
|
|
||||||
options = [
|
|
||||||
"defaults"
|
|
||||||
"size=50%"
|
|
||||||
"mode=755"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
|
||||||
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
|
||||||
|
|
||||||
athenix.sw.enable = lib.mkDefault true;
|
|
||||||
athenix.sw.type = lib.mkDefault "stateless-kiosk";
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
# ============================================================================
|
|
||||||
# Laptop Configuration
|
|
||||||
# ============================================================================
|
|
||||||
# Hardware and boot configuration for laptop systems with mobile features.
|
|
||||||
# Includes power management, lid switch handling, and Intel graphics fixes.
|
|
||||||
|
|
||||||
{ inputs, ... }:
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
modulesPath,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
(import ../common.nix { inherit inputs; })
|
|
||||||
(modulesPath + "/installer/scan/not-detected.nix")
|
|
||||||
];
|
|
||||||
|
|
||||||
# ========== Boot Configuration ==========
|
|
||||||
|
|
||||||
boot.initrd.availableKernelModules = [
|
|
||||||
"xhci_pci" # USB 3.0 support
|
|
||||||
"thunderbolt" # Thunderbolt support
|
|
||||||
"nvme" # NVMe SSD support
|
|
||||||
"usb_storage" # USB storage devices
|
|
||||||
"sd_mod" # SD card support
|
|
||||||
"sdhci_pci" # SD card host controller
|
|
||||||
];
|
|
||||||
boot.initrd.kernelModules = [ ];
|
|
||||||
boot.kernelModules = [ "kvm-intel" ]; # Intel virtualization support
|
|
||||||
boot.extraModulePackages = [ ];
|
|
||||||
boot.kernelParams = [
|
|
||||||
"quiet" # Minimal boot messages
|
|
||||||
"splash" # Show Plymouth boot splash
|
|
||||||
"boot.shell_on_fail" # Emergency shell on boot failure
|
|
||||||
"udev.log_priority=3" # Reduce udev logging
|
|
||||||
"rd.systemd.show_status=auto" # Show systemd status during boot
|
|
||||||
"i915.enable_psr=0" # Disable Panel Self Refresh (stability)
|
|
||||||
"i915.enable_dc=0" # Disable display power saving
|
|
||||||
"i915.enable_fbc=0" # Disable framebuffer compression
|
|
||||||
];
|
|
||||||
|
|
||||||
# ========== Hardware Configuration ==========
|
|
||||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
|
||||||
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
|
||||||
|
|
||||||
# ========== Filesystem Configuration ==========
|
|
||||||
athenix.host.filesystem.device = lib.mkDefault "/dev/nvme0n1";
|
|
||||||
athenix.host.filesystem.swapSize = lib.mkDefault "34G"; # Larger swap for hibernation
|
|
||||||
athenix.host.buildMethods = lib.mkDefault [ "installer-iso" ];
|
|
||||||
|
|
||||||
# ========== Power Management ==========
|
|
||||||
services.upower.enable = lib.mkDefault true;
|
|
||||||
services.logind.settings = {
|
|
||||||
Login = {
|
|
||||||
HandleLidSwitch = "suspend";
|
|
||||||
HandleLidSwitchExternalPower = "suspend";
|
|
||||||
HandleLidSwitchDocked = "ignore";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
athenix.sw.enable = lib.mkDefault true;
|
|
||||||
athenix.sw.type = lib.mkDefault "desktop";
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
# ============================================================================
|
|
||||||
# Proxmox LXC Container Configuration
|
|
||||||
# ============================================================================
|
|
||||||
# Configuration for lightweight Linux containers running in Proxmox.
|
|
||||||
# Disables boot/disk management and enables remote development support.
|
|
||||||
|
|
||||||
{ inputs, ... }:
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
modulesPath,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
(import ../common.nix { inherit inputs; })
|
|
||||||
inputs.vscode-server.nixosModules.default
|
|
||||||
"${modulesPath}/virtualisation/proxmox-lxc.nix"
|
|
||||||
];
|
|
||||||
|
|
||||||
# ========== Nix Configuration ==========
|
|
||||||
nix.settings.trusted-users = [
|
|
||||||
"root"
|
|
||||||
"engr-ugaif"
|
|
||||||
];
|
|
||||||
nix.settings.experimental-features = [
|
|
||||||
"nix-command"
|
|
||||||
"flakes"
|
|
||||||
];
|
|
||||||
|
|
||||||
# ========== Container-Specific Configuration ==========
|
|
||||||
boot.isContainer = true;
|
|
||||||
boot.loader.systemd-boot.enable = lib.mkForce false; # No bootloader in container
|
|
||||||
disko.enableConfig = lib.mkForce false; # No disk management in container
|
|
||||||
console.enable = true;
|
|
||||||
|
|
||||||
# Allow getty to work in containers
|
|
||||||
systemd.services."getty@".unitConfig.ConditionPathExists = [
|
|
||||||
""
|
|
||||||
"/dev/%I"
|
|
||||||
];
|
|
||||||
|
|
||||||
# Suppress unnecessary systemd units for containers
|
|
||||||
systemd.suppressedSystemUnits = [
|
|
||||||
"dev-mqueue.mount"
|
|
||||||
"sys-kernel-debug.mount"
|
|
||||||
"sys-fs-fuse-connections.mount"
|
|
||||||
];
|
|
||||||
|
|
||||||
# ========== Remote Development ==========
|
|
||||||
services.vscode-server.enable = true;
|
|
||||||
|
|
||||||
# ========== System Configuration ==========
|
|
||||||
system.stateVersion = "25.11";
|
|
||||||
athenix.host.buildMethods = lib.mkDefault [
|
|
||||||
"lxc" # LXC container tarball
|
|
||||||
"proxmox" # Proxmox VMA archive
|
|
||||||
];
|
|
||||||
|
|
||||||
athenix.sw.enable = lib.mkDefault true;
|
|
||||||
athenix.sw.type = lib.mkDefault "headless";
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
# ============================================================================
|
|
||||||
# Microsoft Surface Tablet Configuration
|
|
||||||
# ============================================================================
|
|
||||||
# Hardware configuration for Surface Go tablets in kiosk mode.
|
|
||||||
# Uses nixos-hardware module and older kernel for Surface-specific drivers.
|
|
||||||
|
|
||||||
{ inputs, ... }:
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
modulesPath,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
# Use older kernel version for better Surface Go compatibility
|
|
||||||
refSystem = inputs.nixpkgs-old-kernel.lib.nixosSystem {
|
|
||||||
system = pkgs.stdenv.hostPlatform.system;
|
|
||||||
modules = [ inputs.nixos-hardware.nixosModules.microsoft-surface-go ];
|
|
||||||
};
|
|
||||||
refKernelPackages = refSystem.config.boot.kernelPackages;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
(import ../common.nix { inherit inputs; })
|
|
||||||
(modulesPath + "/installer/scan/not-detected.nix")
|
|
||||||
inputs.nixos-hardware.nixosModules.microsoft-surface-go
|
|
||||||
];
|
|
||||||
|
|
||||||
# ========== Boot Configuration ==========
|
|
||||||
|
|
||||||
boot.initrd.availableKernelModules = [
|
|
||||||
"xhci_pci" # USB 3.0 support
|
|
||||||
"nvme" # NVMe support (though Surface uses eMMC)
|
|
||||||
"usb_storage" # USB storage devices
|
|
||||||
"sd_mod" # SD card support
|
|
||||||
"sdhci_pci" # SD card host controller
|
|
||||||
];
|
|
||||||
boot.initrd.kernelModules = [ ];
|
|
||||||
boot.kernelModules = [ "kvm-intel" ]; # Intel virtualization support
|
|
||||||
boot.extraModulePackages = [ ];
|
|
||||||
boot.kernelParams = [
|
|
||||||
"quiet" # Minimal boot messages
|
|
||||||
"splash" # Show Plymouth boot splash
|
|
||||||
"boot.shell_on_fail" # Emergency shell on boot failure
|
|
||||||
"udev.log_priority=3" # Reduce udev logging
|
|
||||||
"rd.systemd.show_status=auto" # Show systemd status during boot
|
|
||||||
"intel_ipu3_imgu" # Intel camera image processing
|
|
||||||
"intel_ipu3_isys" # Intel camera sensor interface
|
|
||||||
"fbcon=map:1" # Framebuffer console mapping
|
|
||||||
"i915.enable_psr=0" # Disable Panel Self Refresh (breaks resume)
|
|
||||||
"i915.enable_dc=0" # Disable display power saving
|
|
||||||
];
|
|
||||||
|
|
||||||
# Use older kernel for better Surface hardware support
|
|
||||||
boot.kernelPackages = lib.mkForce refKernelPackages;
|
|
||||||
|
|
||||||
# ========== Filesystem Configuration ==========
|
|
||||||
athenix.host.filesystem.swapSize = lib.mkDefault "8G";
|
|
||||||
athenix.host.filesystem.device = lib.mkDefault "/dev/mmcblk0"; # eMMC storage # eMMC storage
|
|
||||||
athenix.host.buildMethods = lib.mkDefault [ "installer-iso" ];
|
|
||||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
|
||||||
|
|
||||||
# ========== Hardware Configuration ==========
|
|
||||||
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
|
||||||
|
|
||||||
# ========== Software Profile ==========
|
|
||||||
athenix.sw.enable = lib.mkDefault true;
|
|
||||||
athenix.sw.type = lib.mkDefault "tablet-kiosk"; # Touch-optimized kiosk mode
|
|
||||||
}
|
|
||||||
@@ -1,276 +0,0 @@
|
|||||||
{
|
|
||||||
pkgs,
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
inputs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# User Configuration Module
|
|
||||||
# ============================================================================
|
|
||||||
# This module defines the schema for user accounts and handles their creation.
|
|
||||||
# It bridges the gap between the data in 'users.nix' and the actual NixOS
|
|
||||||
# and Home Manager configuration.
|
|
||||||
|
|
||||||
let
|
|
||||||
# Load users.nix to get account definitions
|
|
||||||
usersData = import ../users.nix { inherit pkgs; };
|
|
||||||
accounts = usersData.athenix.users or { };
|
|
||||||
|
|
||||||
# Helper: Resolve external module path from fetchGit/fetchTarball/path
|
|
||||||
resolveExternalPath =
|
|
||||||
external:
|
|
||||||
if external == null then
|
|
||||||
null
|
|
||||||
else if builtins.isAttrs external && external ? outPath then
|
|
||||||
external.outPath
|
|
||||||
else
|
|
||||||
external;
|
|
||||||
|
|
||||||
# Helper: Check if path exists and is valid
|
|
||||||
isValidPath =
|
|
||||||
path:
|
|
||||||
path != null
|
|
||||||
&& (builtins.isPath path || (builtins.isString path && lib.hasPrefix "/" path))
|
|
||||||
&& builtins.pathExists path;
|
|
||||||
|
|
||||||
# Extract athenix.users options from external user.nix modules
|
|
||||||
# First, build a cache of options per user from their external user.nix (if any).
|
|
||||||
externalUserModuleOptions = lib.genAttrs (lib.attrNames accounts) (
|
|
||||||
name:
|
|
||||||
let
|
|
||||||
user = accounts.${name};
|
|
||||||
externalPath = resolveExternalPath (user.external or null);
|
|
||||||
userNixPath = if externalPath != null then externalPath + "/user.nix" else null;
|
|
||||||
in
|
|
||||||
if isValidPath userNixPath then
|
|
||||||
let
|
|
||||||
# Import and evaluate the module with minimal args
|
|
||||||
outerModule = import userNixPath { inherit inputs; };
|
|
||||||
evaluatedModule = outerModule {
|
|
||||||
config = { };
|
|
||||||
inherit lib pkgs;
|
|
||||||
osConfig = null;
|
|
||||||
};
|
|
||||||
# Extract just the athenix.users.<name> options
|
|
||||||
athenixUsers = evaluatedModule.athenix.users or { };
|
|
||||||
in
|
|
||||||
athenixUsers.${name} or { }
|
|
||||||
else
|
|
||||||
{ }
|
|
||||||
);
|
|
||||||
|
|
||||||
# externalUserOptions only contains users that actually have options defined
|
|
||||||
externalUserOptions = lib.filterAttrs (
|
|
||||||
_: moduleOptions: moduleOptions != { }
|
|
||||||
) externalUserModuleOptions;
|
|
||||||
|
|
||||||
# Submodule defining the structure of a user account
|
|
||||||
userSubmodule = lib.types.submodule {
|
|
||||||
options = {
|
|
||||||
isNormalUser = lib.mkOption {
|
|
||||||
type = lib.types.bool;
|
|
||||||
default = true;
|
|
||||||
};
|
|
||||||
description = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.str;
|
|
||||||
default = null;
|
|
||||||
};
|
|
||||||
extraGroups = lib.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.str;
|
|
||||||
default = [ ];
|
|
||||||
};
|
|
||||||
hashedPassword = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "!";
|
|
||||||
};
|
|
||||||
extraPackages = lib.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.package;
|
|
||||||
default = [ ];
|
|
||||||
};
|
|
||||||
excludePackages = lib.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.package;
|
|
||||||
default = [ ];
|
|
||||||
};
|
|
||||||
homePackages = lib.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.package;
|
|
||||||
default = [ ];
|
|
||||||
};
|
|
||||||
extraImports = lib.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.path;
|
|
||||||
default = [ ];
|
|
||||||
};
|
|
||||||
external = lib.mkOption {
|
|
||||||
type = lib.types.nullOr (
|
|
||||||
lib.types.oneOf [
|
|
||||||
lib.types.path
|
|
||||||
lib.types.package
|
|
||||||
lib.types.attrs
|
|
||||||
]
|
|
||||||
);
|
|
||||||
default = null;
|
|
||||||
description = ''
|
|
||||||
External user configuration module. Can be:
|
|
||||||
- A path to a local module directory
|
|
||||||
- A fetchGit/fetchTarball result pointing to a repository
|
|
||||||
|
|
||||||
The external module can contain:
|
|
||||||
- user.nix (optional): Sets athenix.users.<name> options AND home-manager config
|
|
||||||
- nixos.nix (optional): System-level NixOS configuration
|
|
||||||
|
|
||||||
Example: builtins.fetchGit { url = "https://github.com/user/dotfiles"; rev = "..."; }
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
opensshKeys = lib.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.str;
|
|
||||||
default = [ ];
|
|
||||||
description = "List of SSH public keys for the user.";
|
|
||||||
};
|
|
||||||
shell = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.package;
|
|
||||||
default = null;
|
|
||||||
description = "The shell for this user.";
|
|
||||||
};
|
|
||||||
editor = lib.mkOption {
|
|
||||||
type = lib.types.nullOr lib.types.package;
|
|
||||||
default = null;
|
|
||||||
description = "The default editor for this user.";
|
|
||||||
};
|
|
||||||
useZshTheme = lib.mkOption {
|
|
||||||
type = lib.types.bool;
|
|
||||||
default = true;
|
|
||||||
description = "Whether to apply the system Zsh theme.";
|
|
||||||
};
|
|
||||||
useNvimPlugins = lib.mkOption {
|
|
||||||
type = lib.types.bool;
|
|
||||||
default = true;
|
|
||||||
description = "Whether to apply the system Neovim configuration.";
|
|
||||||
};
|
|
||||||
enable = lib.mkOption {
|
|
||||||
type = lib.types.bool;
|
|
||||||
default = false;
|
|
||||||
description = "Whether this user account is enabled on this system.";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
in
|
|
||||||
{
|
|
||||||
|
|
||||||
options.athenix.users = lib.mkOption {
|
|
||||||
type = lib.types.attrsOf userSubmodule;
|
|
||||||
default = { };
|
|
||||||
description = "User accounts configuration. Set enable=true for users that should exist on this system.";
|
|
||||||
};
|
|
||||||
|
|
||||||
config = {
|
|
||||||
# Merge user definitions from users.nix with options from external user.nix modules
|
|
||||||
# External options take precedence over users.nix (which uses lib.mkDefault)
|
|
||||||
athenix.users = lib.mapAttrs (
|
|
||||||
name: user:
|
|
||||||
user
|
|
||||||
// {
|
|
||||||
description = lib.mkDefault (user.description or null);
|
|
||||||
shell = lib.mkDefault (user.shell or null);
|
|
||||||
extraGroups = lib.mkDefault (user.extraGroups or [ ]);
|
|
||||||
}
|
|
||||||
// (externalUserOptions.${name} or { })
|
|
||||||
) accounts;
|
|
||||||
|
|
||||||
# Generate NixOS users
|
|
||||||
users.users =
|
|
||||||
let
|
|
||||||
enabledAccounts = lib.filterAttrs (_: user: user.enable) config.athenix.users;
|
|
||||||
in
|
|
||||||
lib.mapAttrs (
|
|
||||||
name: user:
|
|
||||||
let
|
|
||||||
isPlasma6 = config.services.desktopManager.plasma6.enable;
|
|
||||||
defaultPackages = lib.optionals (isPlasma6 && name != "root") [ pkgs.kdePackages.kate ];
|
|
||||||
finalPackages = lib.subtractLists user.excludePackages (defaultPackages ++ user.extraPackages);
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit (user) isNormalUser extraGroups hashedPassword;
|
|
||||||
description = if user.description != null then user.description else lib.mkDefault "";
|
|
||||||
openssh.authorizedKeys.keys = user.opensshKeys;
|
|
||||||
packages = finalPackages;
|
|
||||||
shell = if user.shell != null then user.shell else pkgs.bash;
|
|
||||||
}
|
|
||||||
) enabledAccounts;
|
|
||||||
|
|
||||||
# Home Manager configs per user
|
|
||||||
home-manager = {
|
|
||||||
useGlobalPkgs = true;
|
|
||||||
useUserPackages = true;
|
|
||||||
extraSpecialArgs = {
|
|
||||||
osConfig = config;
|
|
||||||
inherit inputs;
|
|
||||||
};
|
|
||||||
|
|
||||||
users =
|
|
||||||
let
|
|
||||||
enabledAccounts = lib.filterAttrs (_: user: user.enable) config.athenix.users;
|
|
||||||
in
|
|
||||||
lib.mapAttrs (
|
|
||||||
name: user:
|
|
||||||
let
|
|
||||||
# Resolve external module paths
|
|
||||||
hasExternal = user.external != null;
|
|
||||||
externalPath = resolveExternalPath user.external;
|
|
||||||
userNixPath = if externalPath != null then externalPath + "/user.nix" else null;
|
|
||||||
hasExternalUser = isValidPath userNixPath;
|
|
||||||
|
|
||||||
# Import external user.nix for home-manager (filter out athenix.* options)
|
|
||||||
externalUserModule =
|
|
||||||
if hasExternalUser then
|
|
||||||
let
|
|
||||||
fullModule = import userNixPath { inherit inputs; };
|
|
||||||
in
|
|
||||||
# Only pass through non-athenix options to home-manager
|
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
osConfig,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
evaluated = fullModule {
|
|
||||||
inherit
|
|
||||||
config
|
|
||||||
lib
|
|
||||||
pkgs
|
|
||||||
osConfig
|
|
||||||
;
|
|
||||||
};
|
|
||||||
in
|
|
||||||
lib.filterAttrs (attrName: _: attrName != "athenix") evaluated
|
|
||||||
else
|
|
||||||
{ };
|
|
||||||
|
|
||||||
# Common imports based on user flags
|
|
||||||
commonImports = lib.optional user.useZshTheme ../sw/theme.nix ++ [
|
|
||||||
(import ../sw/nvim.nix { inherit user; })
|
|
||||||
];
|
|
||||||
|
|
||||||
# Build imports list
|
|
||||||
allImports = user.extraImports ++ commonImports ++ lib.optional hasExternalUser externalUserModule;
|
|
||||||
in
|
|
||||||
lib.mkMerge [
|
|
||||||
{
|
|
||||||
imports = allImports;
|
|
||||||
|
|
||||||
# Always set these required options
|
|
||||||
home.username = name;
|
|
||||||
home.homeDirectory = if name == "root" then "/root" else "/home/${name}";
|
|
||||||
home.stateVersion = "25.11";
|
|
||||||
}
|
|
||||||
(lib.mkIf (!hasExternal) {
|
|
||||||
# For local users only, add their packages
|
|
||||||
home.packages = user.homePackages;
|
|
||||||
})
|
|
||||||
]
|
|
||||||
) enabledAccounts;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
23
hw/default.nix
Normal file
23
hw/default.nix
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# Host Types Module
|
||||||
|
# ============================================================================
|
||||||
|
# This module exports all available host types as an attribute set.
|
||||||
|
# Each type is a NixOS module (a function suitable for lib.types.submodule).
|
||||||
|
|
||||||
|
{ inputs }:
|
||||||
|
let
|
||||||
|
lib = inputs.nixpkgs.lib;
|
||||||
|
inherit (builtins) readDir attrNames;
|
||||||
|
inherit (lib) filterAttrs removeSuffix genAttrs;
|
||||||
|
|
||||||
|
files = readDir ./.;
|
||||||
|
|
||||||
|
# Keep only regular *.nix files except default.nix
|
||||||
|
nixFiles = filterAttrs (
|
||||||
|
name: type: type == "regular" && lib.hasSuffix ".nix" name && name != "default.nix"
|
||||||
|
) files;
|
||||||
|
|
||||||
|
moduleNames = map (name: removeSuffix ".nix" name) (attrNames nixFiles);
|
||||||
|
in
|
||||||
|
# Export: { name = <module function from ./name.nix>; }
|
||||||
|
genAttrs moduleNames (name: import (./. + ("/" + name + ".nix")))
|
||||||
73
hw/nix-desktop.nix
Normal file
73
hw/nix-desktop.nix
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# Desktop Configuration
|
||||||
|
# ============================================================================
|
||||||
|
# Hardware and boot configuration for standard desktop workstations.
|
||||||
|
# Includes Intel CPU support and NVMe storage.
|
||||||
|
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
modulesPath,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.athenix.hw.nix-desktop;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
(modulesPath + "/installer/scan/not-detected.nix")
|
||||||
|
];
|
||||||
|
|
||||||
|
options.athenix.hw.nix-desktop = mkOption {
|
||||||
|
type = types.submodule {
|
||||||
|
options = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Enable desktop workstation hardware configuration.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
description = "Desktop workstation hardware type configuration.";
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
|
||||||
|
# ========== Boot Configuration ==========
|
||||||
|
|
||||||
|
boot.initrd.availableKernelModules = [
|
||||||
|
"xhci_pci" # USB 3.0 support
|
||||||
|
"nvme" # NVMe SSD support
|
||||||
|
"usb_storage" # USB storage devices
|
||||||
|
"sd_mod" # SD card support
|
||||||
|
"sdhci_pci" # SD card host controller
|
||||||
|
];
|
||||||
|
boot.initrd.kernelModules = [ ];
|
||||||
|
boot.kernelModules = [ "kvm-intel" ]; # Intel virtualization support
|
||||||
|
boot.extraModulePackages = [ ];
|
||||||
|
boot.kernelParams = [
|
||||||
|
"quiet" # Minimal boot messages
|
||||||
|
"splash" # Show Plymouth boot splash
|
||||||
|
"boot.shell_on_fail" # Emergency shell on boot failure
|
||||||
|
"udev.log_priority=3" # Reduce udev logging
|
||||||
|
"rd.systemd.show_status=auto" # Show systemd status during boot
|
||||||
|
];
|
||||||
|
|
||||||
|
# ========== Filesystem Configuration ==========
|
||||||
|
athenix.host.filesystem.swapSize = lib.mkDefault "16G";
|
||||||
|
athenix.host.filesystem.device = lib.mkDefault "/dev/nvme0n1";
|
||||||
|
athenix.host.buildMethods = lib.mkDefault [ "installer-iso" ];
|
||||||
|
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||||
|
|
||||||
|
# ========== Hardware Configuration ==========
|
||||||
|
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||||
|
|
||||||
|
# ========== Software Profile ==========
|
||||||
|
athenix.sw.enable = lib.mkDefault true;
|
||||||
|
athenix.sw.desktop.enable = lib.mkDefault true;
|
||||||
|
};
|
||||||
|
}
|
||||||
88
hw/nix-ephemeral.nix
Normal file
88
hw/nix-ephemeral.nix
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# Ephemeral/Diskless System Configuration
|
||||||
|
# ============================================================================
|
||||||
|
# Configuration for systems that run entirely from RAM without persistent storage.
|
||||||
|
# Suitable for kiosks, netboot clients, and stateless workstations.
|
||||||
|
# All data is lost on reboot.
|
||||||
|
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
modulesPath,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.athenix.hw.nix-ephemeral;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
(modulesPath + "/installer/scan/not-detected.nix")
|
||||||
|
];
|
||||||
|
|
||||||
|
options.athenix.hw.nix-ephemeral = mkOption {
|
||||||
|
type = types.submodule {
|
||||||
|
options = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Enable ephemeral/diskless system hardware configuration.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
description = "Ephemeral hardware type configuration.";
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
# ========== Boot Configuration ==========
|
||||||
|
boot.initrd.availableKernelModules = [
|
||||||
|
"xhci_pci" # USB 3.0 support
|
||||||
|
"nvme" # NVMe support
|
||||||
|
"usb_storage" # USB storage devices
|
||||||
|
"sd_mod" # SD card support
|
||||||
|
"sdhci_pci" # SD card host controller
|
||||||
|
];
|
||||||
|
boot.initrd.kernelModules = [ ];
|
||||||
|
boot.kernelModules = [ "kvm-intel" ]; # Intel virtualization support
|
||||||
|
boot.extraModulePackages = [ ];
|
||||||
|
boot.kernelParams = [
|
||||||
|
"quiet" # Minimal boot messages
|
||||||
|
"splash" # Show Plymouth boot splash
|
||||||
|
"boot.shell_on_fail" # Emergency shell on boot failure
|
||||||
|
"udev.log_priority=3" # Reduce udev logging
|
||||||
|
"rd.systemd.show_status=auto" # Show systemd status during boot
|
||||||
|
];
|
||||||
|
|
||||||
|
# ========== Ephemeral Configuration ==========
|
||||||
|
# No persistent storage - everything runs from RAM
|
||||||
|
athenix.host.filesystem.swapSize = lib.mkForce "0G";
|
||||||
|
athenix.host.filesystem.device = lib.mkForce "/dev/null"; # Dummy device
|
||||||
|
athenix.host.buildMethods = lib.mkDefault [
|
||||||
|
"iso" # Live ISO image
|
||||||
|
"ipxe" # Network boot
|
||||||
|
];
|
||||||
|
|
||||||
|
# Disable disk management for RAM-only systems
|
||||||
|
disko.enableConfig = lib.mkForce false;
|
||||||
|
|
||||||
|
# Define tmpfs root filesystem
|
||||||
|
fileSystems."/" = {
|
||||||
|
device = "none";
|
||||||
|
fsType = "tmpfs";
|
||||||
|
options = [
|
||||||
|
"defaults"
|
||||||
|
"size=50%"
|
||||||
|
"mode=755"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||||
|
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||||
|
|
||||||
|
athenix.sw.enable = lib.mkDefault true;
|
||||||
|
athenix.sw.stateless-kiosk.enable = lib.mkDefault true;
|
||||||
|
};
|
||||||
|
}
|
||||||
85
hw/nix-laptop.nix
Normal file
85
hw/nix-laptop.nix
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# Laptop Configuration
|
||||||
|
# ============================================================================
|
||||||
|
# Hardware and boot configuration for laptop systems with mobile features.
|
||||||
|
# Includes power management, lid switch handling, and Intel graphics fixes.
|
||||||
|
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
modulesPath,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.athenix.hw.nix-laptop;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
(modulesPath + "/installer/scan/not-detected.nix")
|
||||||
|
];
|
||||||
|
|
||||||
|
options.athenix.hw.nix-laptop = mkOption {
|
||||||
|
type = types.submodule {
|
||||||
|
options = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Enable laptop hardware configuration with power management.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
description = "Laptop hardware type configuration.";
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
# ========== Boot Configuration ==========
|
||||||
|
|
||||||
|
boot.initrd.availableKernelModules = [
|
||||||
|
"xhci_pci" # USB 3.0 support
|
||||||
|
"thunderbolt" # Thunderbolt support
|
||||||
|
"nvme" # NVMe SSD support
|
||||||
|
"usb_storage" # USB storage devices
|
||||||
|
"sd_mod" # SD card support
|
||||||
|
"sdhci_pci" # SD card host controller
|
||||||
|
];
|
||||||
|
boot.initrd.kernelModules = [ ];
|
||||||
|
boot.kernelModules = [ "kvm-intel" ]; # Intel virtualization support
|
||||||
|
boot.extraModulePackages = [ ];
|
||||||
|
boot.kernelParams = [
|
||||||
|
"quiet" # Minimal boot messages
|
||||||
|
"splash" # Show Plymouth boot splash
|
||||||
|
"boot.shell_on_fail" # Emergency shell on boot failure
|
||||||
|
"udev.log_priority=3" # Reduce udev logging
|
||||||
|
"rd.systemd.show_status=auto" # Show systemd status during boot
|
||||||
|
"i915.enable_psr=0" # Disable Panel Self Refresh (stability)
|
||||||
|
"i915.enable_dc=0" # Disable display power saving
|
||||||
|
"i915.enable_fbc=0" # Disable framebuffer compression
|
||||||
|
];
|
||||||
|
|
||||||
|
# ========== Hardware Configuration ==========
|
||||||
|
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||||
|
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||||
|
|
||||||
|
# ========== Filesystem Configuration ==========
|
||||||
|
athenix.host.filesystem.device = lib.mkDefault "/dev/nvme0n1";
|
||||||
|
athenix.host.filesystem.swapSize = lib.mkDefault "34G"; # Larger swap for hibernation
|
||||||
|
athenix.host.buildMethods = lib.mkDefault [ "installer-iso" ];
|
||||||
|
|
||||||
|
# ========== Power Management ==========
|
||||||
|
services.upower.enable = lib.mkDefault true;
|
||||||
|
services.logind.settings = {
|
||||||
|
Login = {
|
||||||
|
HandleLidSwitch = "suspend";
|
||||||
|
HandleLidSwitchExternalPower = "suspend";
|
||||||
|
HandleLidSwitchDocked = "ignore";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
athenix.sw.enable = lib.mkDefault true;
|
||||||
|
athenix.sw.desktop.enable = lib.mkDefault true;
|
||||||
|
};
|
||||||
|
}
|
||||||
79
hw/nix-lxc.nix
Normal file
79
hw/nix-lxc.nix
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# Proxmox LXC Container Configuration
|
||||||
|
# ============================================================================
|
||||||
|
# Configuration for lightweight Linux containers running in Proxmox.
|
||||||
|
# Disables boot/disk management and enables remote development support.
|
||||||
|
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.athenix.hw.nix-lxc;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.athenix.hw.nix-lxc = mkOption {
|
||||||
|
type = types.submodule {
|
||||||
|
options = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Enable Proxmox LXC container hardware configuration.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
description = "Proxmox LXC hardware type configuration.";
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
# ========== Nix Configuration ==========
|
||||||
|
nix.settings.trusted-users = [
|
||||||
|
"root"
|
||||||
|
"engr-ugaif"
|
||||||
|
];
|
||||||
|
nix.settings.experimental-features = [
|
||||||
|
"nix-command"
|
||||||
|
"flakes"
|
||||||
|
];
|
||||||
|
|
||||||
|
# ========== Container-Specific Configuration ==========
|
||||||
|
boot.isContainer = true;
|
||||||
|
boot.loader.systemd-boot.enable = lib.mkForce false; # No bootloader in container
|
||||||
|
disko.enableConfig = lib.mkForce false; # No disk management in container
|
||||||
|
console.enable = true;
|
||||||
|
|
||||||
|
# Set timezone to fix /etc/localtime for Docker containers
|
||||||
|
time.timeZone = lib.mkDefault "America/New_York";
|
||||||
|
|
||||||
|
# Allow getty to work in containers
|
||||||
|
systemd.services."getty@".unitConfig.ConditionPathExists = [
|
||||||
|
""
|
||||||
|
"/dev/%I"
|
||||||
|
];
|
||||||
|
|
||||||
|
# Suppress unnecessary systemd units for containers
|
||||||
|
systemd.suppressedSystemUnits = [
|
||||||
|
"dev-mqueue.mount"
|
||||||
|
"sys-kernel-debug.mount"
|
||||||
|
"sys-fs-fuse-connections.mount"
|
||||||
|
];
|
||||||
|
|
||||||
|
# ========== Remote Development ==========
|
||||||
|
services.vscode-server.enable = true;
|
||||||
|
|
||||||
|
# ========== System Configuration ==========
|
||||||
|
system.stateVersion = "25.11";
|
||||||
|
athenix.host.buildMethods = lib.mkDefault [
|
||||||
|
"lxc" # LXC container tarball
|
||||||
|
"proxmox" # Proxmox VMA archive
|
||||||
|
];
|
||||||
|
|
||||||
|
athenix.sw.enable = lib.mkDefault true;
|
||||||
|
athenix.sw.headless.enable = lib.mkDefault true;
|
||||||
|
};
|
||||||
|
}
|
||||||
89
hw/nix-surface.nix
Normal file
89
hw/nix-surface.nix
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# Microsoft Surface Tablet Configuration
|
||||||
|
# ============================================================================
|
||||||
|
# Hardware configuration for Surface Go tablets in kiosk mode.
|
||||||
|
# Uses nixos-hardware module and older kernel for Surface-specific drivers.
|
||||||
|
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
modulesPath,
|
||||||
|
inputs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.athenix.hw.nix-surface;
|
||||||
|
# Use older kernel version for better Surface Go compatibility
|
||||||
|
refSystem = inputs.nixpkgs-old-kernel.lib.nixosSystem {
|
||||||
|
system = pkgs.stdenv.hostPlatform.system;
|
||||||
|
modules = [ inputs.nixos-hardware.nixosModules.microsoft-surface-go ];
|
||||||
|
};
|
||||||
|
refKernelPackages = refSystem.config.boot.kernelPackages;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
(modulesPath + "/installer/scan/not-detected.nix")
|
||||||
|
inputs.nixos-hardware.nixosModules.microsoft-surface-go
|
||||||
|
];
|
||||||
|
|
||||||
|
options.athenix.hw.nix-surface = mkOption {
|
||||||
|
type = types.submodule {
|
||||||
|
options = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Enable Microsoft Surface tablet hardware configuration.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
description = "Microsoft Surface hardware type configuration.";
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
# ========== Boot Configuration ==========
|
||||||
|
|
||||||
|
boot.initrd.availableKernelModules = [
|
||||||
|
"xhci_pci" # USB 3.0 support
|
||||||
|
"nvme" # NVMe support (though Surface uses eMMC)
|
||||||
|
"usb_storage" # USB storage devices
|
||||||
|
"sd_mod" # SD card support
|
||||||
|
"sdhci_pci" # SD card host controller
|
||||||
|
];
|
||||||
|
boot.initrd.kernelModules = [ ];
|
||||||
|
boot.kernelModules = [ "kvm-intel" ]; # Intel virtualization support
|
||||||
|
boot.extraModulePackages = [ ];
|
||||||
|
boot.kernelParams = [
|
||||||
|
"quiet" # Minimal boot messages
|
||||||
|
"splash" # Show Plymouth boot splash
|
||||||
|
"boot.shell_on_fail" # Emergency shell on boot failure
|
||||||
|
"udev.log_priority=3" # Reduce udev logging
|
||||||
|
"rd.systemd.show_status=auto" # Show systemd status during boot
|
||||||
|
"intel_ipu3_imgu" # Intel camera image processing
|
||||||
|
"intel_ipu3_isys" # Intel camera sensor interface
|
||||||
|
"fbcon=map:1" # Framebuffer console mapping
|
||||||
|
"i915.enable_psr=0" # Disable Panel Self Refresh (breaks resume)
|
||||||
|
"i915.enable_dc=0" # Disable display power saving
|
||||||
|
];
|
||||||
|
|
||||||
|
# Use older kernel for better Surface hardware support
|
||||||
|
boot.kernelPackages = lib.mkForce refKernelPackages;
|
||||||
|
|
||||||
|
# ========== Filesystem Configuration ==========
|
||||||
|
athenix.host.filesystem.swapSize = lib.mkDefault "8G";
|
||||||
|
athenix.host.filesystem.device = lib.mkDefault "/dev/mmcblk0"; # eMMC storage # eMMC storage
|
||||||
|
athenix.host.buildMethods = lib.mkDefault [ "installer-iso" ];
|
||||||
|
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||||
|
|
||||||
|
# ========== Hardware Configuration ==========
|
||||||
|
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||||
|
|
||||||
|
# ========== Software Profile ==========
|
||||||
|
athenix.sw.enable = lib.mkDefault true;
|
||||||
|
athenix.sw.tablet-kiosk.enable = lib.mkDefault true; # Touch-optimized kiosk mode
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -4,27 +4,46 @@
|
|||||||
# Configuration for NixOS running in WSL2 on Windows.
|
# Configuration for NixOS running in WSL2 on Windows.
|
||||||
# Integrates with nixos-wsl for WSL-specific functionality.
|
# Integrates with nixos-wsl for WSL-specific functionality.
|
||||||
|
|
||||||
{ inputs, ... }:
|
|
||||||
{
|
{
|
||||||
lib,
|
lib,
|
||||||
config,
|
config,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
(import ../common.nix { inherit inputs; })
|
|
||||||
inputs.nixos-wsl.nixosModules.default
|
|
||||||
inputs.vscode-server.nixosModules.default
|
|
||||||
];
|
|
||||||
|
|
||||||
# ========== Options ==========
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.athenix.hw.nix-wsl;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.athenix.hw.nix-wsl = mkOption {
|
||||||
|
type = types.submodule {
|
||||||
|
options = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Enable Windows Subsystem for Linux hardware configuration.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
description = "WSL hardware type configuration.";
|
||||||
|
};
|
||||||
|
|
||||||
|
# WSL user option (at module level, not inside config)
|
||||||
options.athenix.host.wsl.user = lib.mkOption {
|
options.athenix.host.wsl.user = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
default = "engr-ugaif";
|
default = "engr-ugaif";
|
||||||
description = "The default user to log in as in WSL.";
|
description = ''
|
||||||
|
The default user to automatically log in as when starting WSL.
|
||||||
|
|
||||||
|
This user must be enabled via athenix.users.<username>.enable = true.
|
||||||
|
Tip: Use athenix.forUser = "username" as a shortcut to set both.
|
||||||
|
'';
|
||||||
|
example = "alice";
|
||||||
};
|
};
|
||||||
|
|
||||||
config = {
|
config = mkIf cfg.enable {
|
||||||
# ========== WSL Configuration ==========
|
# ========== WSL Configuration ==========
|
||||||
wsl.enable = true;
|
wsl.enable = true;
|
||||||
# Use forUser if set, otherwise fall back to wsl.user option
|
# Use forUser if set, otherwise fall back to wsl.user option
|
||||||
@@ -33,7 +52,7 @@
|
|||||||
|
|
||||||
# ========== Software Profile ==========
|
# ========== Software Profile ==========
|
||||||
athenix.sw.enable = lib.mkDefault true;
|
athenix.sw.enable = lib.mkDefault true;
|
||||||
athenix.sw.type = lib.mkDefault "headless";
|
athenix.sw.headless.enable = lib.mkDefault true;
|
||||||
|
|
||||||
# ========== Remote Development ==========
|
# ========== Remote Development ==========
|
||||||
services.vscode-server.enable = true;
|
services.vscode-server.enable = true;
|
||||||
@@ -50,5 +69,8 @@
|
|||||||
# Provide dummy values for required options from boot.nix
|
# Provide dummy values for required options from boot.nix
|
||||||
athenix.host.filesystem.device = "/dev/null";
|
athenix.host.filesystem.device = "/dev/null";
|
||||||
athenix.host.filesystem.swapSize = "0G";
|
athenix.host.filesystem.swapSize = "0G";
|
||||||
|
|
||||||
|
# WSL doesn't use installer ISOs
|
||||||
|
athenix.host.buildMethods = lib.mkDefault [ ];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
71
hw/nix-zima.nix
Normal file
71
hw/nix-zima.nix
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# Desktop Configuration
|
||||||
|
# ============================================================================
|
||||||
|
# Hardware and boot configuration for standard desktop workstations.
|
||||||
|
# Includes Intel CPU support and NVMe storage.
|
||||||
|
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
modulesPath,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.athenix.hw.nix-zima;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
(modulesPath + "/installer/scan/not-detected.nix")
|
||||||
|
];
|
||||||
|
|
||||||
|
options.athenix.hw.nix-zima = mkOption {
|
||||||
|
type = types.submodule {
|
||||||
|
options = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Enable Zima-specific hardware configuration.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
description = "Zima hardware type configuration.";
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
# ========== Boot Configuration ==========
|
||||||
|
|
||||||
|
boot.initrd.availableKernelModules = [
|
||||||
|
"xhci_pci" # USB 3.0 support
|
||||||
|
"usb_storage" # USB storage devices
|
||||||
|
"sd_mod" # SD card support
|
||||||
|
"sdhci_pci" # SD card host controller
|
||||||
|
];
|
||||||
|
boot.initrd.kernelModules = [ ];
|
||||||
|
boot.kernelModules = [ "kvm-intel" ]; # Intel virtualization support
|
||||||
|
boot.extraModulePackages = [ ];
|
||||||
|
boot.kernelParams = [
|
||||||
|
"quiet" # Minimal boot messages
|
||||||
|
"splash" # Show Plymouth boot splash
|
||||||
|
"boot.shell_on_fail" # Emergency shell on boot failure
|
||||||
|
"udev.log_priority=3" # Reduce udev logging
|
||||||
|
"rd.systemd.show_status=auto" # Show systemd status during boot
|
||||||
|
];
|
||||||
|
|
||||||
|
# ========== Filesystem Configuration ==========
|
||||||
|
athenix.host.filesystem.useSwap = lib.mkDefault false;
|
||||||
|
athenix.host.filesystem.device = lib.mkDefault "/dev/mmcblk0";
|
||||||
|
athenix.host.buildMethods = lib.mkDefault [ "installer-iso" ];
|
||||||
|
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||||
|
|
||||||
|
# ========== Hardware Configuration ==========
|
||||||
|
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
|
||||||
|
|
||||||
|
# ========== Software Profile ==========
|
||||||
|
athenix.sw.enable = lib.mkDefault true;
|
||||||
|
athenix.sw.desktop.enable = lib.mkDefault true;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -46,7 +46,7 @@ Add the host to `inventory.nix` with the `nix-lxc` type or ensure it has the app
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Your host type configuration (`hosts/types/nix-lxc.nix`) should include:
|
Your host type configuration (`hw/nix-lxc.nix`) should include:
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
inputs,
|
inputs,
|
||||||
hosts,
|
fleet,
|
||||||
self,
|
self,
|
||||||
system,
|
system,
|
||||||
|
users ? { },
|
||||||
}:
|
}:
|
||||||
# This file defines the logic for generating various build artifacts (ISOs, Netboot, LXC, etc.)
|
# This file defines the logic for generating various build artifacts (ISOs, Netboot, LXC, etc.)
|
||||||
# It exports a set of packages that can be built using `nix build .#<artifact-name>`
|
# It exports a set of packages that can be built using `nix build .#<artifact-name>`
|
||||||
@@ -18,7 +19,7 @@ let
|
|||||||
hostName:
|
hostName:
|
||||||
let
|
let
|
||||||
targetConfig = self.nixosConfigurations.${hostName}.config;
|
targetConfig = self.nixosConfigurations.${hostName}.config;
|
||||||
targetSystem = targetConfig.system.build.toplevel;
|
targetSystemBuild = targetConfig.system.build;
|
||||||
diskoScript = targetConfig.system.build.diskoScript;
|
diskoScript = targetConfig.system.build.diskoScript;
|
||||||
in
|
in
|
||||||
nixpkgs.lib.nixosSystem {
|
nixpkgs.lib.nixosSystem {
|
||||||
@@ -27,8 +28,9 @@ let
|
|||||||
inherit
|
inherit
|
||||||
inputs
|
inputs
|
||||||
hostName
|
hostName
|
||||||
targetSystem
|
targetSystemBuild
|
||||||
diskoScript
|
diskoScript
|
||||||
|
users
|
||||||
;
|
;
|
||||||
hostPlatform = system;
|
hostPlatform = system;
|
||||||
};
|
};
|
||||||
@@ -45,7 +47,10 @@ let
|
|||||||
nixos-generators.nixosGenerate {
|
nixos-generators.nixosGenerate {
|
||||||
inherit system;
|
inherit system;
|
||||||
specialArgs = { inherit inputs; };
|
specialArgs = { inherit inputs; };
|
||||||
modules = hosts.modules.${hostName} ++ [
|
modules = fleet.modules.${hostName} ++ [
|
||||||
|
{
|
||||||
|
config.athenix.users = lib.mapAttrs (_: user: lib.mapAttrs (_: lib.mkDefault) user) users;
|
||||||
|
}
|
||||||
{
|
{
|
||||||
disko.enableConfig = lib.mkForce false;
|
disko.enableConfig = lib.mkForce false;
|
||||||
services.upower.enable = lib.mkForce false;
|
services.upower.enable = lib.mkForce false;
|
||||||
@@ -61,8 +66,11 @@ let
|
|||||||
nixpkgs.lib.nixosSystem {
|
nixpkgs.lib.nixosSystem {
|
||||||
inherit system;
|
inherit system;
|
||||||
specialArgs = { inherit inputs; };
|
specialArgs = { inherit inputs; };
|
||||||
modules = hosts.modules.${hostName} ++ [
|
modules = fleet.modules.${hostName} ++ [
|
||||||
"${nixpkgs}/nixos/modules/installer/netboot/netboot.nix"
|
"${nixpkgs}/nixos/modules/installer/netboot/netboot.nix"
|
||||||
|
{
|
||||||
|
config.athenix.users = lib.mapAttrs (_: user: lib.mapAttrs (_: lib.mkDefault) user) users;
|
||||||
|
}
|
||||||
{
|
{
|
||||||
disko.enableConfig = lib.mkForce false;
|
disko.enableConfig = lib.mkForce false;
|
||||||
services.upower.enable = lib.mkForce false;
|
services.upower.enable = lib.mkForce false;
|
||||||
@@ -70,14 +78,14 @@ let
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
hostNames = builtins.attrNames hosts.nixosConfigurations;
|
hostNames = builtins.attrNames fleet.nixosConfigurations;
|
||||||
|
|
||||||
# Generate installer ISOs for hosts that have "installer-iso" in their buildMethods
|
# Generate installer ISOs for hosts that have "installer-iso" in their buildMethods
|
||||||
installerPackages = lib.listToAttrs (
|
installerPackages = lib.listToAttrs (
|
||||||
lib.concatMap (
|
lib.concatMap (
|
||||||
name:
|
name:
|
||||||
let
|
let
|
||||||
cfg = hosts.nixosConfigurations.${name};
|
cfg = fleet.nixosConfigurations.${name};
|
||||||
in
|
in
|
||||||
if lib.elem "installer-iso" cfg.config.athenix.host.buildMethods then
|
if lib.elem "installer-iso" cfg.config.athenix.host.buildMethods then
|
||||||
[
|
[
|
||||||
@@ -96,7 +104,7 @@ let
|
|||||||
lib.concatMap (
|
lib.concatMap (
|
||||||
name:
|
name:
|
||||||
let
|
let
|
||||||
cfg = hosts.nixosConfigurations.${name};
|
cfg = fleet.nixosConfigurations.${name};
|
||||||
in
|
in
|
||||||
if lib.elem "iso" cfg.config.athenix.host.buildMethods then
|
if lib.elem "iso" cfg.config.athenix.host.buildMethods then
|
||||||
[
|
[
|
||||||
@@ -115,7 +123,7 @@ let
|
|||||||
lib.concatMap (
|
lib.concatMap (
|
||||||
name:
|
name:
|
||||||
let
|
let
|
||||||
cfg = hosts.nixosConfigurations.${name};
|
cfg = fleet.nixosConfigurations.${name};
|
||||||
in
|
in
|
||||||
if lib.elem "ipxe" cfg.config.athenix.host.buildMethods then
|
if lib.elem "ipxe" cfg.config.athenix.host.buildMethods then
|
||||||
[
|
[
|
||||||
@@ -145,7 +153,7 @@ let
|
|||||||
lib.concatMap (
|
lib.concatMap (
|
||||||
name:
|
name:
|
||||||
let
|
let
|
||||||
cfg = hosts.nixosConfigurations.${name};
|
cfg = fleet.nixosConfigurations.${name};
|
||||||
in
|
in
|
||||||
if lib.elem "lxc" cfg.config.athenix.host.buildMethods then
|
if lib.elem "lxc" cfg.config.athenix.host.buildMethods then
|
||||||
[
|
[
|
||||||
@@ -164,7 +172,7 @@ let
|
|||||||
lib.concatMap (
|
lib.concatMap (
|
||||||
name:
|
name:
|
||||||
let
|
let
|
||||||
cfg = hosts.nixosConfigurations.${name};
|
cfg = fleet.nixosConfigurations.${name};
|
||||||
in
|
in
|
||||||
if lib.elem "proxmox" cfg.config.athenix.host.buildMethods then
|
if lib.elem "proxmox" cfg.config.athenix.host.buildMethods then
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -2,13 +2,10 @@
|
|||||||
# It is intended to be used in an installation ISO.
|
# It is intended to be used in an installation ISO.
|
||||||
# It expects `targetSystem` (the closure to install) and `diskoScript` (the partitioning script) to be passed as arguments.
|
# It expects `targetSystem` (the closure to install) and `diskoScript` (the partitioning script) to be passed as arguments.
|
||||||
{
|
{
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
pkgs,
|
||||||
inputs,
|
|
||||||
hostName,
|
hostName,
|
||||||
hostPlatform,
|
hostPlatform,
|
||||||
targetSystem,
|
targetSystemBuild,
|
||||||
diskoScript,
|
diskoScript,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
@@ -17,11 +14,21 @@
|
|||||||
pkgs.git
|
pkgs.git
|
||||||
pkgs.bashInteractive
|
pkgs.bashInteractive
|
||||||
pkgs.curl
|
pkgs.curl
|
||||||
targetSystem
|
targetSystemBuild.toplevel
|
||||||
];
|
];
|
||||||
|
|
||||||
nixpkgs.hostPlatform = hostPlatform;
|
nixpkgs.hostPlatform = hostPlatform;
|
||||||
|
|
||||||
|
nix.settings.experimental-features = "nix-command flakes";
|
||||||
|
|
||||||
|
system.extraDependencies = with targetSystemBuild; [
|
||||||
|
toplevel
|
||||||
|
etc
|
||||||
|
bootStage2
|
||||||
|
];
|
||||||
|
|
||||||
|
isoImage.storeContents = [ targetSystemBuild.toplevel ];
|
||||||
|
|
||||||
systemd.services.auto-install = {
|
systemd.services.auto-install = {
|
||||||
description = "Automatic NixOS install for ${hostName}";
|
description = "Automatic NixOS install for ${hostName}";
|
||||||
after = [
|
after = [
|
||||||
@@ -44,8 +51,12 @@
|
|||||||
echo ">>> Running disko script..."
|
echo ">>> Running disko script..."
|
||||||
${diskoScript}
|
${diskoScript}
|
||||||
|
|
||||||
echo ">>> Running nixos-install..."
|
echo ">>> Setting up NixOS..."
|
||||||
nixos-install --no-root-passwd --system ${targetSystem}
|
nixos-install \
|
||||||
|
--system ${targetSystemBuild.toplevel} \
|
||||||
|
--no-root-passwd \
|
||||||
|
--no-channel-copy \
|
||||||
|
--substituters ""
|
||||||
|
|
||||||
echo ">>> Done. Rebooting."
|
echo ">>> Done. Rebooting."
|
||||||
systemctl reboot
|
systemctl reboot
|
||||||
|
|||||||
@@ -6,40 +6,26 @@
|
|||||||
#
|
#
|
||||||
# Usage in another flake:
|
# Usage in another flake:
|
||||||
# # Full host type configurations (includes hardware + software + system config)
|
# # Full host type configurations (includes hardware + software + system config)
|
||||||
# inputs.nixos-systems.nixosModules.nix-desktop
|
# inputs.athenix.nixosModules.nix-desktop
|
||||||
# inputs.nixos-systems.nixosModules.nix-laptop
|
# inputs.athenix.nixosModules.nix-laptop
|
||||||
#
|
#
|
||||||
# # Software-only configurations (for custom hardware setups)
|
# # Software-only configuration (for custom hardware setups)
|
||||||
# # Note: These include theme.nix in home-manager.sharedModules automatically
|
# inputs.athenix.nixosModules.sw
|
||||||
# inputs.nixos-systems.nixosModules.sw-desktop
|
|
||||||
# inputs.nixos-systems.nixosModules.sw-headless
|
|
||||||
#
|
|
||||||
# # Home Manager modules (user-level configuration)
|
|
||||||
# # Theme module (no parameters):
|
|
||||||
# home-manager.users.myuser.imports = [ inputs.nixos-systems.homeManagerModules.theme ];
|
|
||||||
#
|
|
||||||
# # Neovim module (requires user parameter):
|
|
||||||
# home-manager.users.myuser.imports = [
|
|
||||||
# (inputs.nixos-systems.homeManagerModules.nvim {
|
|
||||||
# user = config.athenix.users.accounts.myuser;
|
|
||||||
# })
|
|
||||||
# ];
|
|
||||||
|
|
||||||
{ inputs }:
|
{ inputs }:
|
||||||
|
# Expose hardware type modules from hw/ directory
|
||||||
|
# This returns an attribute set like: { nix-desktop = ...; nix-laptop = ...; nix-lxc = ...; }
|
||||||
|
let
|
||||||
|
hostTypes = import ../hw { inherit inputs; };
|
||||||
|
in
|
||||||
{
|
{
|
||||||
# ========== Full Host Type Modules ==========
|
# Software configuration module - main module with all athenix.sw options
|
||||||
# Complete system configurations including hardware, boot, and software
|
# Use athenix.sw.<type>.enable to enable software profiles: desktop, tablet-kiosk, headless, stateless-kiosk, builders
|
||||||
nix-desktop = import ../hosts/types/nix-desktop.nix { inherit inputs; }; # Desktop workstations
|
hw = hostTypes;
|
||||||
nix-laptop = import ../hosts/types/nix-laptop.nix { inherit inputs; }; # Laptop systems
|
sw =
|
||||||
nix-surface = import ../hosts/types/nix-surface.nix { inherit inputs; }; # Surface tablets
|
{
|
||||||
nix-lxc = import ../hosts/types/nix-lxc.nix { inherit inputs; }; # Proxmox containers
|
inputs,
|
||||||
nix-wsl = import ../hosts/types/nix-wsl.nix { inherit inputs; }; # WSL2 systems
|
...
|
||||||
nix-ephemeral = import ../hosts/types/nix-ephemeral.nix { inherit inputs; }; # Diskless/RAM-only
|
}@args:
|
||||||
|
(import ../sw/default.nix (args // { inherit inputs; }));
|
||||||
# ========== Software Configuration Module ==========
|
|
||||||
# Main software module with all athenix.sw options
|
|
||||||
# Use athenix.sw.type to select profile: "desktop", "tablet-kiosk", "headless", "stateless-kiosk"
|
|
||||||
# Use athenix.sw.extraPackages to add additional packages
|
|
||||||
# Use athenix.sw.kioskUrl to set kiosk mode URL
|
|
||||||
sw = { inputs, ... }@args: (import ../sw/default.nix (args // { inherit inputs; }));
|
|
||||||
}
|
}
|
||||||
|
|||||||
310
inventory.nix
310
inventory.nix
@@ -1,175 +1,157 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# Fleet Inventory
|
||||||
|
# ============================================================================
|
||||||
|
# Top-level keys are ALWAYS hostname prefixes. Actual hostnames are generated
|
||||||
|
# from the devices map or count.
|
||||||
|
#
|
||||||
|
# Hostname generation rules:
|
||||||
|
# - Numeric suffixes: no dash (e.g., "nix-surface1", "nix-surface2")
|
||||||
|
# - Non-numeric suffixes: add dash (e.g., "nix-surface-alpha", "nix-surface-beta")
|
||||||
|
# - Set athenix.host.useHostPrefix = false to use suffix as full hostname
|
||||||
|
#
|
||||||
|
# Format:
|
||||||
|
# "prefix" = {
|
||||||
|
# type = "nix-desktop"; # Optional: defaults to prefix name
|
||||||
|
# system = "x86_64-linux"; # Optional: default is x86_64-linux
|
||||||
|
#
|
||||||
|
# # Option 1: Simple count (quick syntax)
|
||||||
|
# devices = 5; # Creates: prefix1, prefix2, ..., prefix5
|
||||||
|
#
|
||||||
|
# # Option 2: Explicit count
|
||||||
|
# count = 5; # Creates: prefix1, prefix2, ..., prefix5
|
||||||
|
#
|
||||||
|
# # Option 3: Default count (for groups with mixed devices)
|
||||||
|
# defaultCount = 3; # Creates default numbered hosts
|
||||||
|
#
|
||||||
|
# # Option 4: Named device configurations
|
||||||
|
# devices = {
|
||||||
|
# "1" = { ... }; # Creates: prefix1
|
||||||
|
# "alpha" = { ... }; # Creates: prefix-alpha
|
||||||
|
# "custom" = { # Creates: custom (no prefix)
|
||||||
|
# athenix.host.useHostPrefix = false;
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
#
|
||||||
|
# # Common config for all devices in this group
|
||||||
|
# overrides = {
|
||||||
|
# athenix.users.user1.enable = true; # Applied to all devices in this group
|
||||||
|
# # ... any other config
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
#
|
||||||
|
# Convenience options:
|
||||||
|
# athenix.forUser = "username"; # Automatically enables user (sets athenix.users.username.enable = true)
|
||||||
|
#
|
||||||
|
# External modules (instead of config):
|
||||||
|
# Device values can be a config attrset with an optional 'external' field:
|
||||||
|
# devices."hostname" = {
|
||||||
|
# external = { url = "..."; rev = "..."; submodules? = false; }; # Lazy: only fetched when building this host
|
||||||
|
# # ... additional config options
|
||||||
|
# };
|
||||||
|
# The external module will be imported and evaluated only when this specific host is built.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# "lab" = { devices = 3; }; # Quick: lab1, lab2, lab3
|
||||||
|
# "lab" = { count = 3; }; # Same as above
|
||||||
|
# "kiosk" = {
|
||||||
|
# defaultCount = 2; # kiosk1, kiosk2 (default)
|
||||||
|
# devices."special" = {}; # kiosk-special (custom)
|
||||||
|
# };
|
||||||
|
# "laptop" = {
|
||||||
|
# devices = 5;
|
||||||
|
# overrides.athenix.users.student.enable = true; # All 5 laptops get this user
|
||||||
|
# };
|
||||||
|
# "wsl" = {
|
||||||
|
# devices."alice".athenix.forUser = "alice123"; # Sets up for user alice123
|
||||||
|
# };
|
||||||
|
# "external" = {
|
||||||
|
# devices."remote".external = { url = "..."; rev = "..."; }; # External module via Git (lazy)
|
||||||
|
# url = "https://github.com/example/config";
|
||||||
|
# rev = "e1ccd7cc3e709afe4f50b0627e1c4bde49165014";
|
||||||
|
# };
|
||||||
|
# };
|
||||||
|
{ ... }:
|
||||||
{
|
{
|
||||||
# ============================================================================
|
athenix.fleet = {
|
||||||
# Fleet Inventory
|
# ========== Lab Laptops ==========
|
||||||
# ============================================================================
|
# Creates: nix-laptop1, nix-laptop2
|
||||||
# Top-level keys are ALWAYS hostname prefixes. Actual hostnames are generated
|
# Both get hdh20267 user via overrides
|
||||||
# from the devices map or count.
|
nix-laptop = {
|
||||||
#
|
devices = 2;
|
||||||
# Hostname generation rules:
|
overrides.athenix.users.hdh20267.enable = true;
|
||||||
# - Numeric suffixes: no dash (e.g., "nix-surface1", "nix-surface2")
|
|
||||||
# - Non-numeric suffixes: add dash (e.g., "nix-surface-alpha", "nix-surface-beta")
|
|
||||||
# - Set athenix.host.useHostPrefix = false to use suffix as full hostname
|
|
||||||
#
|
|
||||||
# Format:
|
|
||||||
# "prefix" = {
|
|
||||||
# type = "nix-desktop"; # Optional: defaults to prefix name
|
|
||||||
# system = "x86_64-linux"; # Optional: default is x86_64-linux
|
|
||||||
#
|
|
||||||
# # Option 1: Simple count (quick syntax)
|
|
||||||
# devices = 5; # Creates: prefix1, prefix2, ..., prefix5
|
|
||||||
#
|
|
||||||
# # Option 2: Explicit count
|
|
||||||
# count = 5; # Creates: prefix1, prefix2, ..., prefix5
|
|
||||||
#
|
|
||||||
# # Option 3: Default count (for groups with mixed devices)
|
|
||||||
# defaultCount = 3; # Creates default numbered hosts
|
|
||||||
#
|
|
||||||
# # Option 4: Named device configurations
|
|
||||||
# devices = {
|
|
||||||
# "1" = { ... }; # Creates: prefix1
|
|
||||||
# "alpha" = { ... }; # Creates: prefix-alpha
|
|
||||||
# "custom" = { # Creates: custom (no prefix)
|
|
||||||
# athenix.host.useHostPrefix = false;
|
|
||||||
# };
|
|
||||||
# };
|
|
||||||
#
|
|
||||||
# # Common config for all devices in this group
|
|
||||||
# overrides = {
|
|
||||||
# athenix.users.user1.enable = true; # Applied to all devices in this group
|
|
||||||
# # ... any other config
|
|
||||||
# };
|
|
||||||
# };
|
|
||||||
#
|
|
||||||
# Convenience options:
|
|
||||||
# athenix.forUser = "username"; # Automatically enables user (sets athenix.users.username.enable = true)
|
|
||||||
#
|
|
||||||
# External modules (instead of config):
|
|
||||||
# Device values can be either a config attrset OR a fetchGit/fetchurl call
|
|
||||||
# that points to an external Nix module. The module will be imported and evaluated.
|
|
||||||
#
|
|
||||||
# Examples:
|
|
||||||
# "lab" = { devices = 3; }; # Quick: lab1, lab2, lab3
|
|
||||||
# "lab" = { count = 3; }; # Same as above
|
|
||||||
# "kiosk" = {
|
|
||||||
# defaultCount = 2; # kiosk1, kiosk2 (default)
|
|
||||||
# devices."special" = {}; # kiosk-special (custom)
|
|
||||||
# };
|
|
||||||
# "laptop" = {
|
|
||||||
# devices = 5;
|
|
||||||
# overrides.athenix.users.student.enable = true; # All 5 laptops get this user
|
|
||||||
# };
|
|
||||||
# "wsl" = {
|
|
||||||
# devices."alice".athenix.forUser = "alice123"; # Sets up for user alice123
|
|
||||||
# };
|
|
||||||
# "external" = {
|
|
||||||
# devices."remote" = builtins.fetchGit { # External module via Git
|
|
||||||
# url = "https://github.com/example/config";
|
|
||||||
# rev = "e1ccd7cc3e709afe4f50b0627e1c4bde49165014";
|
|
||||||
# };
|
|
||||||
# }; # ========== Lab Laptops ==========
|
|
||||||
# Creates: nix-laptop1, nix-laptop2
|
|
||||||
# Both get hdh20267 user via overrides
|
|
||||||
nix-laptop = {
|
|
||||||
devices = 2;
|
|
||||||
overrides.athenix.users.hdh20267.enable = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
# ========== Desktop ==========
|
|
||||||
# Creates: nix-desktop1
|
|
||||||
nix-desktop = {
|
|
||||||
devices = 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
# ========== Surface Tablets (Kiosk Mode) ==========
|
|
||||||
# Creates: nix-surface1 (custom), nix-surface2, nix-surface3 (via defaultCount)
|
|
||||||
nix-surface = {
|
|
||||||
defaultCount = 3;
|
|
||||||
devices = {
|
|
||||||
"1".athenix.sw.kioskUrl = "https://google.com";
|
|
||||||
};
|
};
|
||||||
overrides = {
|
|
||||||
athenix.sw.kioskUrl = "https://yahoo.com";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# ========== LXC Containers ==========
|
# ========== Desktop ==========
|
||||||
# Creates: nix-builder (without lxc prefix)
|
# Creates: nix-desktop1
|
||||||
nix-lxc = {
|
nix-desktop = {
|
||||||
devices = {
|
devices = 1;
|
||||||
"nix-builder" = {
|
};
|
||||||
# Gitea Actions self-hosted runner configuration
|
|
||||||
athenix.sw = {
|
# ========== Surface Tablets (Kiosk Mode) ==========
|
||||||
type = [
|
# Creates: nix-surface1 (custom), nix-surface2, nix-surface3 (via defaultCount)
|
||||||
"headless"
|
nix-surface = {
|
||||||
"builders"
|
defaultCount = 3;
|
||||||
];
|
devices = {
|
||||||
builders.giteaRunner = {
|
"1".athenix.sw.tablet-kiosk.kioskUrl = "https://google.com";
|
||||||
enable = true;
|
};
|
||||||
url = "https://git.factory.uga.edu";
|
overrides = {
|
||||||
# Token file must be created manually at this path with a Gitea runner token
|
athenix.sw.tablet-kiosk.kioskUrl = "https://yahoo.com";
|
||||||
# Generate in repository settings: Settings > Actions > Runners > Create new Runner
|
};
|
||||||
# echo "TOKEN=YOUR_TOKEN_HERE" | sudo tee /var/lib/gitea-runner-token > /dev/null
|
};
|
||||||
tokenFile = "/var/lib/gitea-runner-token";
|
|
||||||
# Labels to identify this runner in workflows
|
# ========== LXC Containers ==========
|
||||||
extraLabels = [
|
# Creates: nix-builder (without lxc prefix)
|
||||||
"self-hosted"
|
nix-lxc = {
|
||||||
"nix-builder"
|
devices = {
|
||||||
];
|
"nix-builder" = {
|
||||||
# Runner service name
|
# Gitea Actions self-hosted runner configuration
|
||||||
name = "athenix";
|
athenix.sw = {
|
||||||
|
headless.enable = true;
|
||||||
|
builders = {
|
||||||
|
enable = true;
|
||||||
|
giteaRunner = {
|
||||||
|
enable = true;
|
||||||
|
url = "https://git.factory.uga.edu";
|
||||||
|
# Token file must be created manually at this path with a Gitea runner token
|
||||||
|
# Generate in repository settings: Settings > Actions > Runners > Create new Runner
|
||||||
|
# echo "TOKEN=YOUR_TOKEN_HERE" | sudo tee /var/lib/gitea-runner-token > /dev/null
|
||||||
|
tokenFile = "/var/lib/gitea-runner-token";
|
||||||
|
# Labels to identify this runner in workflows
|
||||||
|
extraLabels = [
|
||||||
|
"self-hosted"
|
||||||
|
"nix-builder"
|
||||||
|
];
|
||||||
|
# Runner service name
|
||||||
|
name = "athenix";
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
"usda-dash".external = {
|
||||||
|
url = "https://git.factory.uga.edu/MODEL/usda-dash-config.git";
|
||||||
|
rev = "ce2700b0196e106f7c013bbcee851a5f96b146a3";
|
||||||
|
submodules = false;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
"usda-dash" = builtins.fetchGit {
|
overrides = {
|
||||||
url = "git@factory.uga.edu:MODEL/usda-dash-config.git";
|
athenix.host.useHostPrefix = false;
|
||||||
rev = "49cded91cff4a956d4e01ac6b8fe4efa86f82182";
|
|
||||||
submodules = true;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
overrides = {
|
|
||||||
athenix.host.useHostPrefix = false;
|
# ========== WSL Instances ==========
|
||||||
|
# Creates: nix-wsl-alireza
|
||||||
|
nix-wsl = {
|
||||||
|
devices = {
|
||||||
|
"alireza".athenix.forUser = "sv22900";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# ========== ZimaBoard Desktops ==========
|
||||||
|
# Creates: nix-zima1, nix-zima2, nix-zima3
|
||||||
|
nix-zima.devices = 3;
|
||||||
|
|
||||||
|
# ========== Ephemeral/Netboot System ==========
|
||||||
|
# Creates: nix-ephemeral1
|
||||||
|
nix-ephemeral.devices = 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
# ========== WSL Instances ==========
|
|
||||||
# Creates: nix-wsl-alireza
|
|
||||||
nix-wsl = {
|
|
||||||
devices = {
|
|
||||||
"alireza".athenix.forUser = "sv22900";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
# ========== Ephemeral/Netboot System ==========
|
|
||||||
# Creates: nix-ephemeral1
|
|
||||||
nix-ephemeral.devices = 1;
|
|
||||||
|
|
||||||
# ========== Example: External Module Configurations ==========
|
|
||||||
# Uncomment to use external modules from Git repositories:
|
|
||||||
#
|
|
||||||
# external-systems = {
|
|
||||||
# devices = {
|
|
||||||
# # Option 1: fetchGit with specific revision (recommended for reproducibility)
|
|
||||||
# "prod-server" = builtins.fetchGit {
|
|
||||||
# url = "https://github.com/example/server-config";
|
|
||||||
# rev = "e1ccd7cc3e709afe4f50b0627e1c4bde49165014"; # Full commit hash
|
|
||||||
# ref = "main"; # Optional: branch/tag name
|
|
||||||
# };
|
|
||||||
#
|
|
||||||
# # Option 2: fetchGit with latest from branch (less reproducible)
|
|
||||||
# "dev-server" = builtins.fetchGit {
|
|
||||||
# url = "https://github.com/example/server-config";
|
|
||||||
# ref = "develop";
|
|
||||||
# };
|
|
||||||
#
|
|
||||||
# # Option 3: fetchTarball for specific release
|
|
||||||
# "test-server" = builtins.fetchTarball {
|
|
||||||
# url = "https://github.com/example/server-config/archive/v1.0.0.tar.gz";
|
|
||||||
# sha256 = "sha256:0000000000000000000000000000000000000000000000000000";
|
|
||||||
# };
|
|
||||||
#
|
|
||||||
# # Option 4: Mix external module with local overrides
|
|
||||||
# # Note: The external module's default.nix should export a NixOS module
|
|
||||||
# # that accepts { inputs, ... } as parameters
|
|
||||||
# };
|
|
||||||
# };
|
|
||||||
}
|
}
|
||||||
|
|||||||
8
lib/default.nix
Normal file
8
lib/default.nix
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
mkFleet = import ./mkFleet.nix;
|
||||||
|
macCaseBuilder = import ./macCaseBuilder.nix { inherit lib; };
|
||||||
|
}
|
||||||
33
lib/macCaseBuilder.nix
Normal file
33
lib/macCaseBuilder.nix
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
{ lib }:
|
||||||
|
let
|
||||||
|
# Default MAC address to station number mapping
|
||||||
|
defaultHostmap = {
|
||||||
|
"00:e0:4c:46:0b:32" = "1";
|
||||||
|
"00:e0:4c:46:07:26" = "2";
|
||||||
|
"00:e0:4c:46:05:94" = "3";
|
||||||
|
"00:e0:4c:46:07:11" = "4";
|
||||||
|
"00:e0:4c:46:08:02" = "5";
|
||||||
|
"00:e0:4c:46:08:5c" = "6";
|
||||||
|
};
|
||||||
|
|
||||||
|
# macCaseBuilder: builds a shell case statement from a hostmap
|
||||||
|
# Parameters:
|
||||||
|
# varName: the shell variable to assign
|
||||||
|
# prefix: optional string to prepend to the value (default: "")
|
||||||
|
# hostmap: optional attribute set to use (default: built-in hostmap)
|
||||||
|
#
|
||||||
|
# Example:
|
||||||
|
# macCaseBuilder { varName = "STATION"; prefix = "nix-"; }
|
||||||
|
# # Generates case statements like: 00:e0:4c:46:0b:32) STATION=nix-1 ;;
|
||||||
|
builder =
|
||||||
|
{
|
||||||
|
varName,
|
||||||
|
prefix ? "",
|
||||||
|
hostmap ? defaultHostmap,
|
||||||
|
}:
|
||||||
|
lib.concatStringsSep "\n" (
|
||||||
|
lib.mapAttrsToList (mac: val: " ${mac}) ${varName}=${prefix}${val} ;;") hostmap
|
||||||
|
);
|
||||||
|
in
|
||||||
|
# Export the builder function with hostmap as an accessible attribute
|
||||||
|
lib.setFunctionArgs builder { } // { hostmap = defaultHostmap; }
|
||||||
18
lib/mkFleet.nix
Normal file
18
lib/mkFleet.nix
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generate fleet configurations with custom fleet and hardware types
|
||||||
|
# Usage: nixosConfigurations = athenix.lib.mkFleet { fleet = { ... }; hwTypes = { ... }; }
|
||||||
|
{
|
||||||
|
inputs,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
self ? null,
|
||||||
|
users ? { },
|
||||||
|
}:
|
||||||
|
import ../fleet/default.nix {
|
||||||
|
inherit
|
||||||
|
inputs
|
||||||
|
lib
|
||||||
|
config
|
||||||
|
self
|
||||||
|
users
|
||||||
|
;
|
||||||
|
}
|
||||||
194
parts/docs.nix
Normal file
194
parts/docs.nix
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
# Documentation generation
|
||||||
|
{
|
||||||
|
inputs,
|
||||||
|
self,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
pkgs = inputs.nixpkgs.legacyPackages.x86_64-linux;
|
||||||
|
|
||||||
|
# Extract options from a sample configuration
|
||||||
|
getAthenixOptions =
|
||||||
|
configName:
|
||||||
|
let
|
||||||
|
nixosConfig = self.nixosConfigurations.${configName};
|
||||||
|
evaledOptions = nixosConfig.options;
|
||||||
|
|
||||||
|
# Filter to just athenix namespace
|
||||||
|
athenixOptions = evaledOptions.athenix or { };
|
||||||
|
in
|
||||||
|
athenixOptions;
|
||||||
|
|
||||||
|
# Generate wiki home page
|
||||||
|
wikiHome = pkgs.writeText "Home.md" ''
|
||||||
|
# Athenix - NixOS Fleet Management
|
||||||
|
|
||||||
|
Athenix is a NixOS configuration system for managing the UGA Innovation Factory's fleet of devices using Nix flakes and a custom configuration framework.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
- [Configuration Options](Configuration-Options) - All available `athenix.*` options
|
||||||
|
- [User Guide](User-Configuration) - Setting up user accounts and dotfiles
|
||||||
|
- [Building](Building) - Creating installers and system images
|
||||||
|
- [Development](Development) - Contributing to Athenix
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Inventory-based fleet management** - Define entire device fleets in a single file
|
||||||
|
- **Multiple hardware types** - Desktops, laptops, Surface tablets, LXC containers, WSL
|
||||||
|
- **Flexible software configurations** - Desktop, headless, kiosk, and builder modes
|
||||||
|
- **External module support** - Load user dotfiles and system configs from Git repos
|
||||||
|
- **Declarative everything** - Reproducible builds with pinned dependencies
|
||||||
|
|
||||||
|
## Software Types
|
||||||
|
|
||||||
|
Enable different system configurations:
|
||||||
|
|
||||||
|
- **desktop** - Full KDE Plasma 6 desktop environment
|
||||||
|
- **headless** - Minimal server/container configuration
|
||||||
|
- **tablet-kiosk** - Touch-optimized kiosk for Surface tablets
|
||||||
|
- **stateless-kiosk** - Diskless PXE boot kiosk
|
||||||
|
- **builders** - CI/CD build server with Gitea Actions runner
|
||||||
|
|
||||||
|
## Hardware Types
|
||||||
|
|
||||||
|
- **nix-desktop** - Desktop workstations
|
||||||
|
- **nix-laptop** - Laptop computers
|
||||||
|
- **nix-surface** - Microsoft Surface Pro tablets
|
||||||
|
- **nix-lxc** - LXC containers (Proxmox)
|
||||||
|
- **nix-wsl** - Windows Subsystem for Linux
|
||||||
|
- **nix-ephemeral** - Stateless systems (PXE boot)
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Browse the documentation using the sidebar or start with:
|
||||||
|
|
||||||
|
- [README](README) - Repository overview and getting started
|
||||||
|
- [Configuration Options](Configuration-Options) - Complete option reference
|
||||||
|
- [Inventory Guide](Inventory) - Managing the device fleet
|
||||||
|
- [External Modules](External-Modules) - Using external configurations
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Generate markdown documentation from options
|
||||||
|
optionsToMarkdown =
|
||||||
|
options:
|
||||||
|
pkgs.writeText "options.md" ''
|
||||||
|
# Configuration Options
|
||||||
|
|
||||||
|
This document describes all available configuration options in the Athenix namespace.
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
- **athenix.sw** - Software configuration (desktop, headless, kiosk modes)
|
||||||
|
- **athenix.users** - User account management
|
||||||
|
- **athenix.host** - Host-specific settings (filesystem, build methods)
|
||||||
|
- **athenix.fleet** - Fleet inventory definitions
|
||||||
|
- **athenix.forUser** - Convenience option to enable a user
|
||||||
|
- **athenix.system.gc** - Garbage collection settings
|
||||||
|
|
||||||
|
## Detailed Options
|
||||||
|
|
||||||
|
For detailed option information, use:
|
||||||
|
```bash
|
||||||
|
# View all athenix options
|
||||||
|
nix eval .#nixosConfigurations.nix-desktop1.options.athenix --apply builtins.attrNames
|
||||||
|
|
||||||
|
# View specific option description
|
||||||
|
nix eval .#nixosConfigurations.nix-desktop1.options.athenix.sw.desktop.enable.description
|
||||||
|
|
||||||
|
# Export all options to JSON
|
||||||
|
nix build .#athenix-options
|
||||||
|
cat result | jq
|
||||||
|
```
|
||||||
|
|
||||||
|
## Software Types
|
||||||
|
|
||||||
|
Enable different system configurations:
|
||||||
|
|
||||||
|
- **desktop** - Full KDE Plasma desktop environment
|
||||||
|
- **headless** - Server/container configuration
|
||||||
|
- **tablet-kiosk** - Touch-optimized kiosk for tablets
|
||||||
|
- **stateless-kiosk** - Diskless PXE boot kiosk
|
||||||
|
- **builders** - Build server with optional Gitea Actions runner
|
||||||
|
|
||||||
|
See the individual option descriptions for detailed information.
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
perSystem =
|
||||||
|
{ system, ... }:
|
||||||
|
lib.mkIf (system == "x86_64-linux") {
|
||||||
|
packages = {
|
||||||
|
# Generate option documentation in markdown
|
||||||
|
docs =
|
||||||
|
pkgs.runCommand "athenix-docs"
|
||||||
|
{
|
||||||
|
nativeBuildInputs = [ pkgs.jq ];
|
||||||
|
}
|
||||||
|
''
|
||||||
|
mkdir -p $out
|
||||||
|
|
||||||
|
# Generate wiki home page
|
||||||
|
cat > $out/Home.md << 'EOF'
|
||||||
|
${builtins.readFile wikiHome}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Copy main README
|
||||||
|
cp ${../README.md} $out/README.md
|
||||||
|
|
||||||
|
# Copy documentation with wiki-friendly names
|
||||||
|
cp ${../docs/BUILDING.md} $out/Building.md
|
||||||
|
cp ${../docs/DEVELOPMENT.md} $out/Development.md
|
||||||
|
cp ${../docs/EXTERNAL_MODULES.md} $out/External-Modules.md
|
||||||
|
cp ${../docs/INVENTORY.md} $out/Inventory.md
|
||||||
|
cp ${../docs/NAMESPACE.md} $out/Namespace.md
|
||||||
|
cp ${../docs/USER_CONFIGURATION.md} $out/User-Configuration.md
|
||||||
|
|
||||||
|
# Generate options reference
|
||||||
|
cat > $out/Configuration-Options.md << 'EOF'
|
||||||
|
${builtins.readFile (optionsToMarkdown (getAthenixOptions "nix-desktop1"))}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "Documentation generated in $out"
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Extract just the athenix namespace options as JSON
|
||||||
|
athenix-options =
|
||||||
|
let
|
||||||
|
nixosConfig =
|
||||||
|
self.nixosConfigurations.nix-desktop1
|
||||||
|
or (builtins.head (builtins.attrValues self.nixosConfigurations));
|
||||||
|
|
||||||
|
# Recursively extract option information
|
||||||
|
extractOption =
|
||||||
|
opt:
|
||||||
|
if opt ? _type && opt._type == "option" then
|
||||||
|
{
|
||||||
|
inherit (opt) description;
|
||||||
|
type = opt.type.description or (opt.type.name or "unknown");
|
||||||
|
default =
|
||||||
|
if opt ? default then
|
||||||
|
if builtins.isAttrs opt.default && opt.default ? _type then "<special>" else opt.default
|
||||||
|
else
|
||||||
|
null;
|
||||||
|
example =
|
||||||
|
if opt ? example then
|
||||||
|
if builtins.isAttrs opt.example && opt.example ? _type then "<special>" else opt.example
|
||||||
|
else
|
||||||
|
null;
|
||||||
|
}
|
||||||
|
else if builtins.isAttrs opt then
|
||||||
|
lib.mapAttrs (name: value: extractOption value) (
|
||||||
|
# Filter out internal attributes
|
||||||
|
lib.filterAttrs (n: _: !lib.hasPrefix "_" n) opt
|
||||||
|
)
|
||||||
|
else
|
||||||
|
null;
|
||||||
|
|
||||||
|
athenixOpts = nixosConfig.options.athenix or { };
|
||||||
|
in
|
||||||
|
pkgs.writeText "athenix-options.json" (builtins.toJSON (extractOption athenixOpts));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
9
parts/formatter.nix
Normal file
9
parts/formatter.nix
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Formatter configuration for flake-parts
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
perSystem =
|
||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
formatter = pkgs.nixfmt-rfc-style;
|
||||||
|
};
|
||||||
|
}
|
||||||
8
parts/lib.nix
Normal file
8
parts/lib.nix
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Library functions for flake-parts
|
||||||
|
{ inputs, ... }:
|
||||||
|
{
|
||||||
|
flake.lib = import ../lib {
|
||||||
|
inherit inputs;
|
||||||
|
lib = inputs.nixpkgs.lib;
|
||||||
|
};
|
||||||
|
}
|
||||||
28
parts/nixos-configurations.nix
Normal file
28
parts/nixos-configurations.nix
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# NixOS configurations generated from fleet
|
||||||
|
{
|
||||||
|
inputs,
|
||||||
|
self,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
../fleet/fleet-option.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
flake.nixosConfigurations =
|
||||||
|
let
|
||||||
|
users = config.athenix.users;
|
||||||
|
fleet = self.lib.mkFleet {
|
||||||
|
inherit
|
||||||
|
inputs
|
||||||
|
lib
|
||||||
|
config
|
||||||
|
self
|
||||||
|
users
|
||||||
|
;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
fleet.nixosConfigurations;
|
||||||
|
}
|
||||||
5
parts/nixos-modules.nix
Normal file
5
parts/nixos-modules.nix
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Expose host type modules and installer modules for external use
|
||||||
|
{ inputs, ... }:
|
||||||
|
{
|
||||||
|
flake.nixosModules = import ../installer/modules.nix { inherit inputs; };
|
||||||
|
}
|
||||||
37
parts/packages.nix
Normal file
37
parts/packages.nix
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Build artifacts (ISOs, LXC containers, etc.)
|
||||||
|
{
|
||||||
|
inputs,
|
||||||
|
self,
|
||||||
|
lib,
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
perSystem =
|
||||||
|
{ system, ... }:
|
||||||
|
lib.mkIf (system == "x86_64-linux") {
|
||||||
|
packages =
|
||||||
|
let
|
||||||
|
users = config.athenix.users;
|
||||||
|
fleet = self.lib.mkFleet {
|
||||||
|
inherit
|
||||||
|
inputs
|
||||||
|
lib
|
||||||
|
config
|
||||||
|
self
|
||||||
|
users
|
||||||
|
;
|
||||||
|
};
|
||||||
|
artifacts = import ../installer/artifacts.nix {
|
||||||
|
inherit
|
||||||
|
inputs
|
||||||
|
fleet
|
||||||
|
self
|
||||||
|
system
|
||||||
|
users
|
||||||
|
;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
artifacts;
|
||||||
|
};
|
||||||
|
}
|
||||||
5
parts/templates.nix
Normal file
5
parts/templates.nix
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Templates for external configurations
|
||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
flake.templates = import ../templates;
|
||||||
|
}
|
||||||
187
secrets.nix
Normal file
187
secrets.nix
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# Agenix Secret Recipients Configuration (Auto-Generated)
|
||||||
|
# ============================================================================
|
||||||
|
# This file automatically discovers hosts and their public keys from the
|
||||||
|
# secrets/ directory structure and generates recipient configurations.
|
||||||
|
#
|
||||||
|
# Directory structure:
|
||||||
|
# secrets/{hostname}/*.pub -> SSH/age public keys for that host
|
||||||
|
# secrets/global/*.pub -> Keys accessible to all hosts
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ragenix -e secrets/global/example.age # Edit/create secret
|
||||||
|
# ragenix -r # Re-key all secrets
|
||||||
|
#
|
||||||
|
# To add admin keys for editing secrets, create secrets/admins/*.pub files
|
||||||
|
# with your personal age public keys (generated with: age-keygen)
|
||||||
|
|
||||||
|
let
|
||||||
|
lib = builtins;
|
||||||
|
|
||||||
|
# Helper functions not in builtins
|
||||||
|
filterAttrs =
|
||||||
|
pred: set:
|
||||||
|
lib.listToAttrs (
|
||||||
|
lib.filter (item: pred item.name item.value) (
|
||||||
|
lib.map (name: {
|
||||||
|
inherit name;
|
||||||
|
value = set.${name};
|
||||||
|
}) (lib.attrNames set)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
concatLists = lists: lib.foldl' (acc: list: acc ++ list) [ ] lists;
|
||||||
|
|
||||||
|
unique =
|
||||||
|
list:
|
||||||
|
let
|
||||||
|
go =
|
||||||
|
acc: remaining:
|
||||||
|
if remaining == [ ] then
|
||||||
|
acc
|
||||||
|
else if lib.elem (lib.head remaining) acc then
|
||||||
|
go acc (lib.tail remaining)
|
||||||
|
else
|
||||||
|
go (acc ++ [ (lib.head remaining) ]) (lib.tail remaining);
|
||||||
|
in
|
||||||
|
go [ ] list;
|
||||||
|
|
||||||
|
hasSuffix =
|
||||||
|
suffix: str:
|
||||||
|
let
|
||||||
|
lenStr = lib.stringLength str;
|
||||||
|
lenSuffix = lib.stringLength suffix;
|
||||||
|
in
|
||||||
|
lenStr >= lenSuffix && lib.substring (lenStr - lenSuffix) lenSuffix str == suffix;
|
||||||
|
|
||||||
|
nameValuePair = name: value: { inherit name value; };
|
||||||
|
|
||||||
|
secretsPath = ./secrets;
|
||||||
|
|
||||||
|
# Read all directories in secrets/
|
||||||
|
secretDirs = if lib.pathExists secretsPath then lib.readDir secretsPath else { };
|
||||||
|
|
||||||
|
# Filter to only directories (excludes files)
|
||||||
|
isDirectory = name: type: type == "directory";
|
||||||
|
directories = lib.filter (name: isDirectory name secretDirs.${name}) (lib.attrNames secretDirs);
|
||||||
|
|
||||||
|
# Read public keys from a directory and convert to age format
|
||||||
|
readHostKeys =
|
||||||
|
dirName:
|
||||||
|
let
|
||||||
|
dirPath = secretsPath + "/${dirName}";
|
||||||
|
files = if lib.pathExists dirPath then lib.readDir dirPath else { };
|
||||||
|
|
||||||
|
# Prefer .age.pub files (pre-converted), fall back to .pub files
|
||||||
|
agePubFiles = filterAttrs (name: type: type == "regular" && hasSuffix ".age.pub" name) files;
|
||||||
|
|
||||||
|
sshPubFiles = filterAttrs (
|
||||||
|
name: type: type == "regular" && hasSuffix ".pub" name && !(hasSuffix ".age.pub" name)
|
||||||
|
) files;
|
||||||
|
|
||||||
|
# Read age public keys (already in correct format)
|
||||||
|
ageKeys = lib.map (
|
||||||
|
name:
|
||||||
|
let
|
||||||
|
content = lib.readFile (dirPath + "/${name}");
|
||||||
|
# Trim whitespace/newlines
|
||||||
|
trimmed = lib.replaceStrings [ "\n" " " "\r" "\t" ] [ "" "" "" "" ] content;
|
||||||
|
in
|
||||||
|
trimmed
|
||||||
|
) (lib.attrNames agePubFiles);
|
||||||
|
|
||||||
|
# For SSH keys, just include them as-is (user needs to convert with ssh-to-age)
|
||||||
|
# Or they can run the update-age-keys.sh script
|
||||||
|
sshKeys =
|
||||||
|
if (lib.length (lib.attrNames sshPubFiles)) > 0 then
|
||||||
|
lib.trace "Warning: ${dirName} has unconverted SSH keys. Run secrets/update-age-keys.sh" [ ]
|
||||||
|
else
|
||||||
|
[ ];
|
||||||
|
in
|
||||||
|
lib.filter (k: k != null && k != "") (ageKeys ++ sshKeys);
|
||||||
|
|
||||||
|
# Build host key mappings: { hostname = [ "age1..." "age2..." ]; }
|
||||||
|
hostKeys = lib.listToAttrs (
|
||||||
|
lib.map (dir: nameValuePair dir (readHostKeys dir)) (
|
||||||
|
lib.filter (d: d != "global" && d != "admins") directories
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
# Global keys that all hosts can use
|
||||||
|
globalKeys = if lib.elem "global" directories then readHostKeys "global" else [ ];
|
||||||
|
|
||||||
|
# Admin keys for editing secrets
|
||||||
|
adminKeys = if lib.elem "admins" directories then readHostKeys "admins" else [ ];
|
||||||
|
|
||||||
|
# All host keys combined
|
||||||
|
allHostKeys = concatLists (lib.attrValues hostKeys);
|
||||||
|
|
||||||
|
# Find all .age files in the secrets directory
|
||||||
|
findSecrets =
|
||||||
|
dir:
|
||||||
|
let
|
||||||
|
dirPath = secretsPath + "/${dir}";
|
||||||
|
files = if lib.pathExists dirPath then lib.readDir dirPath else { };
|
||||||
|
ageFiles = filterAttrs (name: type: type == "regular" && hasSuffix ".age" name) files;
|
||||||
|
in
|
||||||
|
lib.map (name: "secrets/${dir}/${name}") (lib.attrNames ageFiles);
|
||||||
|
|
||||||
|
# Generate recipient list for a secret based on its location
|
||||||
|
getRecipients =
|
||||||
|
secretPath:
|
||||||
|
let
|
||||||
|
# Extract directory name from path: "secrets/nix-builder/foo.age" -> "nix-builder"
|
||||||
|
pathParts = lib.split "/" secretPath;
|
||||||
|
dirName = lib.elemAt pathParts 2;
|
||||||
|
in
|
||||||
|
if dirName == "global" then
|
||||||
|
# Global secrets: all hosts + admins
|
||||||
|
allHostKeys ++ globalKeys ++ adminKeys
|
||||||
|
else if hostKeys ? ${dirName} then
|
||||||
|
# Host-specific secrets: that host + global keys + admins
|
||||||
|
hostKeys.${dirName} ++ globalKeys ++ adminKeys
|
||||||
|
else
|
||||||
|
# Fallback: just admins
|
||||||
|
adminKeys;
|
||||||
|
|
||||||
|
# Find all secrets across all directories
|
||||||
|
allSecrets = concatLists (lib.map findSecrets directories);
|
||||||
|
|
||||||
|
# Generate the configuration
|
||||||
|
secretsConfig = lib.listToAttrs (
|
||||||
|
lib.map (
|
||||||
|
secretPath:
|
||||||
|
let
|
||||||
|
recipients = getRecipients secretPath;
|
||||||
|
# Remove duplicates and empty keys
|
||||||
|
uniqueRecipients = unique (lib.filter (k: k != null && k != "") recipients);
|
||||||
|
in
|
||||||
|
nameValuePair secretPath {
|
||||||
|
publicKeys = uniqueRecipients;
|
||||||
|
}
|
||||||
|
) allSecrets
|
||||||
|
);
|
||||||
|
|
||||||
|
# Generate wildcard rules for each directory to allow creating new secrets
|
||||||
|
wildcardRules = lib.listToAttrs (
|
||||||
|
lib.concatMap (dir: [
|
||||||
|
# Match with and without .age extension for ragenix compatibility
|
||||||
|
(nameValuePair "secrets/${dir}/*" {
|
||||||
|
publicKeys =
|
||||||
|
let
|
||||||
|
recipients = getRecipients "secrets/${dir}/dummy.age";
|
||||||
|
in
|
||||||
|
unique (lib.filter (k: k != null && k != "") recipients);
|
||||||
|
})
|
||||||
|
(nameValuePair "secrets/${dir}/*.age" {
|
||||||
|
publicKeys =
|
||||||
|
let
|
||||||
|
recipients = getRecipients "secrets/${dir}/dummy.age";
|
||||||
|
in
|
||||||
|
unique (lib.filter (k: k != null && k != "") recipients);
|
||||||
|
})
|
||||||
|
]) (lib.filter (d: d != "admins") directories)
|
||||||
|
);
|
||||||
|
|
||||||
|
in
|
||||||
|
secretsConfig // wildcardRules
|
||||||
174
secrets/DESIGN.md
Normal file
174
secrets/DESIGN.md
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
# Athenix Secrets System Design
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Athenix secrets management system integrates ragenix (agenix) with automatic host discovery based on the repository's fleet inventory structure. It provides a seamless workflow for managing encrypted secrets across all systems.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Auto-Discovery Module (`sw/secrets.nix`)
|
||||||
|
|
||||||
|
**Purpose**: Automatically load and configure secrets at system deployment time.
|
||||||
|
|
||||||
|
**Features**:
|
||||||
|
- Discovers `.age` encrypted files from `secrets/` directories
|
||||||
|
- Loads global secrets from `secrets/global/` on ALL systems
|
||||||
|
- Loads host-specific secrets from `secrets/{hostname}/` on matching hosts
|
||||||
|
- Auto-configures decryption keys based on `.pub` files in directories
|
||||||
|
- Supports custom secret configuration via `default.nix` in each directory
|
||||||
|
|
||||||
|
**Key Behaviors**:
|
||||||
|
- Secrets are decrypted to `/run/agenix/{name}` at boot
|
||||||
|
- Identity paths include: system SSH keys + global keys + host-specific keys
|
||||||
|
- Host-specific secrets override global secrets with the same name
|
||||||
|
|
||||||
|
### Dynamic Recipients Configuration (`secrets/secrets.nix`)
|
||||||
|
|
||||||
|
**Purpose**: Generate ragenix recipient configuration from directory structure.
|
||||||
|
|
||||||
|
**Features**:
|
||||||
|
- Automatically discovers hosts from `secrets/` subdirectories
|
||||||
|
- Reads age public keys from `.age.pub` files (converted from SSH keys)
|
||||||
|
- Generates recipient lists based on secret location:
|
||||||
|
- `secrets/global/*.age` → ALL hosts + admins
|
||||||
|
- `secrets/{hostname}/*.age` → that host + global keys + admins
|
||||||
|
- Supports admin keys in `secrets/admins/` for secret editing
|
||||||
|
|
||||||
|
**Key Behaviors**:
|
||||||
|
- No manual recipient list maintenance required
|
||||||
|
- Adding a new host = create directory + add .pub key + run `update-age-keys.sh`
|
||||||
|
- Works with ragenix CLI: `ragenix -e`, `ragenix -r`
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
### Adding a New Host
|
||||||
|
|
||||||
|
1. **Capture SSH host key**:
|
||||||
|
```bash
|
||||||
|
# From the running system
|
||||||
|
cat /etc/ssh/ssh_host_ed25519_key.pub > secrets/new-host/ssh_host_ed25519_key.pub
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Convert to age format**:
|
||||||
|
```bash
|
||||||
|
cd secrets/
|
||||||
|
./update-age-keys.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Re-key existing secrets** (if needed):
|
||||||
|
```bash
|
||||||
|
ragenix -r
|
||||||
|
```
|
||||||
|
|
||||||
|
### Creating a New Secret
|
||||||
|
|
||||||
|
1. **Choose location**:
|
||||||
|
- `secrets/global/` → all systems can decrypt
|
||||||
|
- `secrets/{hostname}/` → only that host can decrypt
|
||||||
|
|
||||||
|
2. **Create/edit secret**:
|
||||||
|
```bash
|
||||||
|
ragenix -e secrets/global/my-secret.age
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Recipients are auto-determined** from `secrets.nix`:
|
||||||
|
- Global secrets: all host keys + admin keys
|
||||||
|
- Host-specific: that host + global keys + admin keys
|
||||||
|
|
||||||
|
### Cross-Host Secret Management
|
||||||
|
|
||||||
|
Any Athenix host can manage secrets for other hosts because:
|
||||||
|
- All public keys are in the repository (`*.age.pub` files)
|
||||||
|
- `secrets/secrets.nix` auto-generates recipient lists
|
||||||
|
- Hosts decrypt using their own private keys (not shared)
|
||||||
|
|
||||||
|
Example: From `nix-builder`, create a secret for `usda-dash`:
|
||||||
|
```bash
|
||||||
|
ragenix -e secrets/usda-dash/database-password.age
|
||||||
|
# Encrypted for usda-dash's public key + admins
|
||||||
|
# usda-dash will decrypt using its private key at /etc/ssh/ssh_host_ed25519_key
|
||||||
|
```
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
secrets/
|
||||||
|
├── secrets.nix # Auto-generated recipient config
|
||||||
|
├── update-age-keys.sh # Helper to convert SSH → age keys
|
||||||
|
├── README.md # User documentation
|
||||||
|
├── DESIGN.md # This file
|
||||||
|
│
|
||||||
|
├── global/ # Secrets for ALL hosts
|
||||||
|
│ ├── *.pub # SSH public keys
|
||||||
|
│ ├── *.age.pub # Age public keys (generated)
|
||||||
|
│ ├── *.age # Encrypted secrets
|
||||||
|
│ └── default.nix # Optional: custom secret config
|
||||||
|
│
|
||||||
|
├── {hostname}/ # Host-specific secrets
|
||||||
|
│ ├── *.pub
|
||||||
|
│ ├── *.age.pub
|
||||||
|
│ ├── *.age
|
||||||
|
│ └── default.nix
|
||||||
|
│
|
||||||
|
└── admins/ # Admin keys for editing
|
||||||
|
└── *.age.pub
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Model
|
||||||
|
|
||||||
|
1. **Public keys in git**: Safe to commit (only public keys, `.age.pub` and `.pub`)
|
||||||
|
2. **Private keys on hosts**: Never leave the system (`/etc/ssh/ssh_host_*_key`, `/etc/age/identity.key`)
|
||||||
|
3. **Encrypted secrets in git**: Safe to commit (`.age` files)
|
||||||
|
4. **Decrypted secrets**: Only in memory/tmpfs (`/run/agenix/*`)
|
||||||
|
|
||||||
|
## Integration Points
|
||||||
|
|
||||||
|
### With NixOS Configuration
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# Access decrypted secrets in any NixOS module
|
||||||
|
config.age.secrets.my-secret.path # => /run/agenix/my-secret
|
||||||
|
|
||||||
|
# Example usage
|
||||||
|
services.myapp.passwordFile = config.age.secrets.database-password.path;
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Inventory System
|
||||||
|
|
||||||
|
The system automatically matches `secrets/{hostname}/` to hostnames from `inventory.nix`. No manual configuration needed.
|
||||||
|
|
||||||
|
### With External Modules
|
||||||
|
|
||||||
|
External user/system modules can reference secrets:
|
||||||
|
```nix
|
||||||
|
# In external module
|
||||||
|
{ config, ... }:
|
||||||
|
{
|
||||||
|
programs.git.extraConfig.credential.helper =
|
||||||
|
"store --file ${config.age.secrets.git-credentials.path}";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advantages
|
||||||
|
|
||||||
|
1. **Zero manual recipient management**: Just add directories and keys
|
||||||
|
2. **Cross-host secret creation**: Any host can manage secrets for others
|
||||||
|
3. **Automatic host discovery**: Syncs with inventory structure
|
||||||
|
4. **Flexible permission model**: Global vs host-specific + custom configs
|
||||||
|
5. **Version controlled**: All public data in git, auditable history
|
||||||
|
6. **Secure by default**: Private keys never shared, secrets encrypted at rest
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
1. **Requires age key conversion**: SSH keys must be converted to age format (automated by script)
|
||||||
|
2. **Bootstrap chicken-egg**: Need initial host key before encrypting secrets (capture from first boot or generate locally)
|
||||||
|
3. **No secret rotation automation**: Must manually re-key with `ragenix -r`
|
||||||
|
4. **Git history contains old encrypted versions**: Rotating keys doesn't remove old ciphertexts from history
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
- Auto-run `update-age-keys.sh` in pre-commit hook
|
||||||
|
- Integrate with inventory.nix to auto-generate host directories
|
||||||
|
- Support for multiple identity types per host
|
||||||
|
- Automated secret rotation scheduling
|
||||||
|
- Integration with hardware security modules (YubiKey, etc.)
|
||||||
250
secrets/README.md
Normal file
250
secrets/README.md
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
# Secrets Management with Agenix
|
||||||
|
|
||||||
|
This directory contains age-encrypted secrets for Athenix hosts. Secrets are automatically loaded based on directory structure.
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
secrets/
|
||||||
|
├── global/ # Secrets installed on ALL systems
|
||||||
|
│ ├── default.nix # Optional: Custom config for global secrets
|
||||||
|
│ └── example.age # Decrypted to /run/agenix/example on all hosts
|
||||||
|
├── nix-builder/ # Secrets only for nix-builder host
|
||||||
|
│ ├── default.nix # Optional: Custom config for nix-builder secrets
|
||||||
|
│ └── ssh_host_ed25519_key.age
|
||||||
|
└── usda-dash/ # Secrets only for usda-dash host
|
||||||
|
└── ssh_host_ed25519_key.age
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. **Global secrets** (`./secrets/global/*.age`) are installed on every system
|
||||||
|
2. **Host-specific secrets** (`./secrets/{hostname}/*.age`) are only installed on matching hosts
|
||||||
|
3. Only `.age` encrypted files are loaded; `.pub` public keys are ignored
|
||||||
|
4. Secrets are decrypted at boot to `/run/agenix/{secret-name}` with mode `0400` and owner `root:root`
|
||||||
|
5. **Custom configurations** can be defined in `default.nix` files within each directory
|
||||||
|
|
||||||
|
## Creating Secrets
|
||||||
|
|
||||||
|
### 1. Generate Age Keys
|
||||||
|
|
||||||
|
For a new host, generate an age identity:
|
||||||
|
```bash
|
||||||
|
# On the target system
|
||||||
|
mkdir -p /etc/age
|
||||||
|
age-keygen -o /etc/age/identity.key
|
||||||
|
chmod 600 /etc/age/identity.key
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use SSH host keys (automatically done by Athenix):
|
||||||
|
```bash
|
||||||
|
# Get the age public key from SSH host key
|
||||||
|
nix shell nixpkgs#ssh-to-age -c sh -c 'cat /etc/ssh/ssh_host_ed25519_key.pub | ssh-to-age'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Store Public Keys
|
||||||
|
|
||||||
|
Save the public key to `secrets/{hostname}/` for reference:
|
||||||
|
```bash
|
||||||
|
# Example for nix-builder
|
||||||
|
echo "age1..." > secrets/nix-builder/identity.pub
|
||||||
|
```
|
||||||
|
|
||||||
|
Or from SSH host key:
|
||||||
|
```bash
|
||||||
|
cat /etc/ssh/ssh_host_ed25519_key.pub > secrets/nix-builder/ssh_host_ed25519_key.pub
|
||||||
|
```
|
||||||
|
|
||||||
|
**Then convert SSH keys to age format:**
|
||||||
|
```bash
|
||||||
|
cd secrets/
|
||||||
|
./update-age-keys.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates `.age.pub` files that `secrets.nix` uses for ragenix recipient configuration.
|
||||||
|
|
||||||
|
### 3. Encrypt Secrets
|
||||||
|
|
||||||
|
Encrypt a secret for specific hosts:
|
||||||
|
```bash
|
||||||
|
# For a single host
|
||||||
|
age -r age1publickey... -o secrets/nix-builder/my-secret.age <<< "secret value"
|
||||||
|
|
||||||
|
# For multiple hosts (recipient list)
|
||||||
|
age -R recipients.txt -o secrets/global/shared-secret.age < plaintext-file
|
||||||
|
|
||||||
|
# Using SSH public keys
|
||||||
|
age -R secrets/nix-builder/ssh_host_ed25519_key.pub \
|
||||||
|
-o secrets/nix-builder/ssh_host_key.age < /etc/ssh/ssh_host_ed25519_key
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Creating and Editing Secrets
|
||||||
|
|
||||||
|
**For new secrets**, use the helper script (automatically determines recipients):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd secrets/
|
||||||
|
|
||||||
|
# Create a host-specific secret
|
||||||
|
./create-secret.sh usda-dash/database-url.age <<< "postgresql://..."
|
||||||
|
|
||||||
|
# Create a global secret
|
||||||
|
echo "shared-api-key" | ./create-secret.sh global/api-key.age
|
||||||
|
|
||||||
|
# From a file
|
||||||
|
./create-secret.sh nix-builder/ssh-key.age < ~/.ssh/id_ed25519
|
||||||
|
```
|
||||||
|
|
||||||
|
The script automatically includes the correct recipients:
|
||||||
|
- **Host-specific**: that host's keys + global keys + admin keys
|
||||||
|
- **Global**: all host keys + admin keys
|
||||||
|
|
||||||
|
**To edit existing secrets**, use `ragenix`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install ragenix
|
||||||
|
nix shell github:yaxitech/ragenix
|
||||||
|
|
||||||
|
# Edit an existing secret (you must have a decryption key)
|
||||||
|
ragenix -e secrets/global/existing-secret.age
|
||||||
|
|
||||||
|
# Re-key all secrets after adding new hosts
|
||||||
|
ragenix -r
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why create with `age` first?** Ragenix requires the `.age` file to exist before editing. The `secrets/secrets.nix` configuration auto-discovers recipients from the directory structure, but ragenix doesn't support wildcard patterns for creating new files.
|
||||||
|
|
||||||
|
**Recipient management** is automatic:
|
||||||
|
- **Global secrets** (`secrets/global/*.age`): encrypted for ALL hosts + admins
|
||||||
|
- **Host secrets** (`secrets/{hostname}/*.age`): encrypted for that host + global keys + admins
|
||||||
|
- **Admin keys** from `secrets/admins/*.age.pub` allow editing from your workstation
|
||||||
|
|
||||||
|
After creating new .age files with `age`, use `ragenix -r` to re-key all secrets with the updated recipient configuration.
|
||||||
|
|
||||||
|
To add admin keys for editing secrets:
|
||||||
|
```bash
|
||||||
|
# Generate personal age key
|
||||||
|
age-keygen -o ~/.config/age/personal.key
|
||||||
|
|
||||||
|
# Extract public key and add to secrets
|
||||||
|
grep "public key:" ~/.config/age/personal.key | cut -d: -f2 | tr -d ' ' > secrets/admins/your-name.age.pub
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using Secrets in Configuration
|
||||||
|
|
||||||
|
Secrets are automatically loaded. Reference them in your NixOS configuration:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# Example: Using a secret for a service
|
||||||
|
services.myservice = {
|
||||||
|
enable = true;
|
||||||
|
passwordFile = config.age.secrets.my-password.path; # /run/agenix/my-password
|
||||||
|
};
|
||||||
|
|
||||||
|
# Example: Setting up SSH host key from secret
|
||||||
|
services.openssh = {
|
||||||
|
hostKeys = [{
|
||||||
|
path = config.age.secrets.ssh_host_ed25519_key.path;
|
||||||
|
type = "ed25519";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom Secret Configuration
|
||||||
|
|
||||||
|
For secrets needing custom permissions, use `athenix.sw.secrets.extraSecrets`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# In inventory.nix or host config
|
||||||
|
athenix.sw.secrets.extraSecrets = {
|
||||||
|
"nginx-cert" = {
|
||||||
|
file = ./secrets/custom/cert.age;
|
||||||
|
mode = "0440";
|
||||||
|
owner = "nginx";
|
||||||
|
group = "nginx";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using default.nix in Secret Directories
|
||||||
|
|
||||||
|
Alternatively, create a `default.nix` file in the secret directory to configure all secrets in that directory:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# secrets/global/default.nix
|
||||||
|
{
|
||||||
|
"example" = {
|
||||||
|
mode = "0440"; # Custom file mode (default: "0400")
|
||||||
|
owner = "nginx"; # Custom owner (default: "root")
|
||||||
|
group = "nginx"; # Custom group (default: "root")
|
||||||
|
path = "/run/secrets/example"; # Custom path (default: /run/agenix/{name})
|
||||||
|
};
|
||||||
|
|
||||||
|
"api-key" = {
|
||||||
|
mode = "0400";
|
||||||
|
owner = "myservice";
|
||||||
|
group = "myservice";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `default.nix` file should return an attribute set where:
|
||||||
|
- **Keys** are secret names (without the `.age` extension)
|
||||||
|
- **Values** are configuration objects with optional fields:
|
||||||
|
- `mode` - File permissions (string, e.g., `"0440"`)
|
||||||
|
- `owner` - File owner (string, e.g., `"nginx"`)
|
||||||
|
- `group` - File group (string, e.g., `"nginx"`)
|
||||||
|
- `path` - Custom installation path (string, e.g., `"/custom/path"`)
|
||||||
|
|
||||||
|
Secrets not listed in `default.nix` will use default settings.
|
||||||
|
|
||||||
|
## Security Best Practices
|
||||||
|
|
||||||
|
1. **Never commit unencrypted secrets** - Only `.age` and `.pub` files belong in this directory
|
||||||
|
2. **Use host-specific secrets** when possible - Limit exposure by using hostname directories
|
||||||
|
3. **Rotate secrets regularly** - Re-encrypt with new keys periodically
|
||||||
|
4. **Backup age identity keys** - Store `/etc/age/identity.key` securely offline
|
||||||
|
5. **Use SSH keys** - Leverage existing SSH host keys for age encryption when possible
|
||||||
|
6. **Pin to commits** - When using external secrets modules, always use `rev = "commit-hash"`
|
||||||
|
|
||||||
|
## Converting SSH Keys to Age Format
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Convert SSH public key to age public key
|
||||||
|
nix shell nixpkgs#ssh-to-age -c ssh-to-age < secrets/nix-builder/ssh_host_ed25519_key.pub
|
||||||
|
|
||||||
|
# Convert SSH private key to age identity (for editing secrets)
|
||||||
|
nix shell nixpkgs#ssh-to-age -c ssh-to-age -private-key -i ~/.ssh/id_ed25519
|
||||||
|
```
|
||||||
|
|
||||||
|
## Disabling Automatic Secrets
|
||||||
|
|
||||||
|
To disable automatic secret loading:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
# In inventory.nix or host config
|
||||||
|
athenix.sw.secrets.enable = false;
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Secret not found
|
||||||
|
- Ensure the `.age` file exists in `secrets/global/` or `secrets/{hostname}/`
|
||||||
|
- Check `hostname` matches directory name: `echo $HOSTNAME` on the target system
|
||||||
|
- Run `nix flake check` to verify secrets are discovered
|
||||||
|
|
||||||
|
### Permission denied
|
||||||
|
- Verify secret permissions in `/run/agenix/`
|
||||||
|
- Check if custom permissions are needed (use `extraSecrets`)
|
||||||
|
- Ensure the service user/group has access to the secret file
|
||||||
|
|
||||||
|
### Age decrypt failed
|
||||||
|
- Verify the host's age identity exists: `ls -l /etc/age/identity.key`
|
||||||
|
- Check that the secret was encrypted with the host's public key
|
||||||
|
- Confirm SSH host key hasn't changed (would change derived age key)
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [ragenix GitHub](https://github.com/yaxitech/ragenix)
|
||||||
|
- [agenix upstream](https://github.com/ryantm/agenix)
|
||||||
|
- [age encryption tool](https://age-encryption.org/)
|
||||||
1
secrets/admins/temp-admin.age.pub
Normal file
1
secrets/admins/temp-admin.age.pub
Normal file
@@ -0,0 +1 @@
|
|||||||
|
age14emzyraytqzmre58c452t07rtcj87cwqwmd9z3gj7upugtxk8s3sda5tju
|
||||||
BIN
secrets/core
Normal file
BIN
secrets/core
Normal file
Binary file not shown.
121
secrets/create-secret.sh
Executable file
121
secrets/create-secret.sh
Executable file
@@ -0,0 +1,121 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Create a new age-encrypted secret with auto-determined recipients
|
||||||
|
# Usage: ./create-secret.sh <path> [content]
|
||||||
|
# path: relative to secrets/ (e.g., "usda-dash/my-secret.age" or "global/shared.age")
|
||||||
|
# content: stdin if not provided
|
||||||
|
|
||||||
|
SECRETS_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
||||||
|
if [ $# -lt 1 ]; then
|
||||||
|
echo "Usage: $0 <path> [content]" >&2
|
||||||
|
echo "Examples:" >&2
|
||||||
|
echo " $0 usda-dash/database-url.age <<< 'postgresql://...'" >&2
|
||||||
|
echo " $0 global/api-key.age < secret-file.txt" >&2
|
||||||
|
echo " echo 'secret' | $0 nix-builder/token.age" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SECRET_PATH="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
# Extract directory from path (e.g., "usda-dash/file.age" -> "usda-dash")
|
||||||
|
SECRET_DIR="$(dirname "$SECRET_PATH")"
|
||||||
|
SECRET_FILE="$(basename "$SECRET_PATH")"
|
||||||
|
|
||||||
|
# Ensure .age extension
|
||||||
|
if [[ ! "$SECRET_FILE" =~ \.age$ ]]; then
|
||||||
|
echo "Error: Secret file must have .age extension" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
TARGET_FILE="$SECRETS_DIR/$SECRET_PATH"
|
||||||
|
|
||||||
|
# Ensure target directory exists
|
||||||
|
mkdir -p "$(dirname "$TARGET_FILE")"
|
||||||
|
|
||||||
|
# Collect recipient keys
|
||||||
|
RECIPIENTS=()
|
||||||
|
|
||||||
|
if [ "$SECRET_DIR" = "global" ]; then
|
||||||
|
echo "Creating global secret (encrypted for all hosts + admins)..." >&2
|
||||||
|
|
||||||
|
# Add all host keys
|
||||||
|
for host_dir in "$SECRETS_DIR"/*/; do
|
||||||
|
host_name="$(basename "$host_dir")"
|
||||||
|
# Skip non-host directories
|
||||||
|
if [ "$host_name" = "admins" ] || [ "$host_name" = "global" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add all .age.pub files from this host
|
||||||
|
while IFS= read -r -d '' key_file; do
|
||||||
|
RECIPIENTS+=("$key_file")
|
||||||
|
done < <(find "$host_dir" -maxdepth 1 -name "*.age.pub" -print0)
|
||||||
|
done
|
||||||
|
|
||||||
|
# Add global keys
|
||||||
|
while IFS= read -r -d '' key_file; do
|
||||||
|
RECIPIENTS+=("$key_file")
|
||||||
|
done < <(find "$SECRETS_DIR/global" -maxdepth 1 -name "*.age.pub" -print0 2>/dev/null || true)
|
||||||
|
|
||||||
|
else
|
||||||
|
echo "Creating host-specific secret for $SECRET_DIR..." >&2
|
||||||
|
|
||||||
|
# Check if host directory exists
|
||||||
|
if [ ! -d "$SECRETS_DIR/$SECRET_DIR" ]; then
|
||||||
|
echo "Error: Host directory $SECRET_DIR does not exist" >&2
|
||||||
|
echo "Create it first: mkdir -p secrets/$SECRET_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add this host's keys
|
||||||
|
while IFS= read -r -d '' key_file; do
|
||||||
|
RECIPIENTS+=("$key_file")
|
||||||
|
done < <(find "$SECRETS_DIR/$SECRET_DIR" -maxdepth 1 -name "*.age.pub" -print0)
|
||||||
|
|
||||||
|
# Add global keys (so global hosts can also decrypt)
|
||||||
|
while IFS= read -r -d '' key_file; do
|
||||||
|
RECIPIENTS+=("$key_file")
|
||||||
|
done < <(find "$SECRETS_DIR/global" -maxdepth 1 -name "*.age.pub" -print0 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add admin keys (for editing from workstations)
|
||||||
|
if [ -d "$SECRETS_DIR/admins" ]; then
|
||||||
|
while IFS= read -r -d '' key_file; do
|
||||||
|
RECIPIENTS+=("$key_file")
|
||||||
|
done < <(find "$SECRETS_DIR/admins" -maxdepth 1 -name "*.age.pub" -print0 2>/dev/null || true)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if we have any recipients
|
||||||
|
if [ ${#RECIPIENTS[@]} -eq 0 ]; then
|
||||||
|
echo "Error: No recipient keys found!" >&2
|
||||||
|
echo "Run ./update-age-keys.sh first to generate .age.pub files" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Found ${#RECIPIENTS[@]} recipient key(s):" >&2
|
||||||
|
for key in "${RECIPIENTS[@]}"; do
|
||||||
|
echo " - $(basename "$key")" >&2
|
||||||
|
done
|
||||||
|
|
||||||
|
# Create recipient list file (temporary)
|
||||||
|
RECIPIENT_LIST=$(mktemp)
|
||||||
|
trap "rm -f $RECIPIENT_LIST" EXIT
|
||||||
|
|
||||||
|
for key in "${RECIPIENTS[@]}"; do
|
||||||
|
cat "$key" >> "$RECIPIENT_LIST"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Encrypt the secret
|
||||||
|
if [ $# -gt 0 ]; then
|
||||||
|
# Content provided as argument
|
||||||
|
echo "$@" | age -R "$RECIPIENT_LIST" -o "$TARGET_FILE"
|
||||||
|
else
|
||||||
|
# Content from stdin
|
||||||
|
age -R "$RECIPIENT_LIST" -o "$TARGET_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "✓ Created $TARGET_FILE" >&2
|
||||||
|
echo " Edit with: ragenix -e secrets/$SECRET_PATH" >&2
|
||||||
1
secrets/global/hunter_halloran_key.age.pub
Normal file
1
secrets/global/hunter_halloran_key.age.pub
Normal file
@@ -0,0 +1 @@
|
|||||||
|
age1udmpqkedupd33gyut85ud3nvppydzeg04kkuneymkvxcjjej244s4v8xjc
|
||||||
10
secrets/nix-builder/default.nix
Normal file
10
secrets/nix-builder/default.nix
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Host-specific secret configuration for nix-builder
|
||||||
|
{
|
||||||
|
# SSH host key should be readable by sshd
|
||||||
|
ssh_host_ed25519_key = {
|
||||||
|
mode = "0600";
|
||||||
|
owner = "root";
|
||||||
|
group = "root";
|
||||||
|
path = "/etc/ssh/ssh_host_ed25519_key";
|
||||||
|
};
|
||||||
|
}
|
||||||
1
secrets/nix-builder/ssh_host_ed25519_key.age.pub
Normal file
1
secrets/nix-builder/ssh_host_ed25519_key.age.pub
Normal file
@@ -0,0 +1 @@
|
|||||||
|
age1u5tczg2sx90n03uuz9h549f4h3h7sq5uehhqpampzs7vj8ew7y6s2mjwz0
|
||||||
176
secrets/secrets.nix
Normal file
176
secrets/secrets.nix
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# Agenix Secret Recipients Configuration (Auto-Generated)
|
||||||
|
# ============================================================================
|
||||||
|
# This file automatically discovers hosts and their public keys from the
|
||||||
|
# secrets/ directory structure and generates recipient configurations.
|
||||||
|
#
|
||||||
|
# Directory structure:
|
||||||
|
# secrets/{hostname}/*.pub -> SSH/age public keys for that host
|
||||||
|
# secrets/global/*.pub -> Keys accessible to all hosts
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ragenix -e secrets/global/example.age # Edit/create secret
|
||||||
|
# ragenix -r # Re-key all secrets
|
||||||
|
#
|
||||||
|
# To add admin keys for editing secrets, create secrets/admins/*.pub files
|
||||||
|
# with your personal age public keys (generated with: age-keygen)
|
||||||
|
|
||||||
|
let
|
||||||
|
lib = builtins;
|
||||||
|
|
||||||
|
# Helper functions not in builtins
|
||||||
|
filterAttrs =
|
||||||
|
pred: set:
|
||||||
|
lib.listToAttrs (
|
||||||
|
lib.filter (item: pred item.name item.value) (
|
||||||
|
lib.map (name: {
|
||||||
|
inherit name;
|
||||||
|
value = set.${name};
|
||||||
|
}) (lib.attrNames set)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
concatLists = lists: lib.foldl' (acc: list: acc ++ list) [ ] lists;
|
||||||
|
|
||||||
|
unique =
|
||||||
|
list:
|
||||||
|
let
|
||||||
|
go =
|
||||||
|
acc: remaining:
|
||||||
|
if remaining == [ ] then
|
||||||
|
acc
|
||||||
|
else if lib.elem (lib.head remaining) acc then
|
||||||
|
go acc (lib.tail remaining)
|
||||||
|
else
|
||||||
|
go (acc ++ [ (lib.head remaining) ]) (lib.tail remaining);
|
||||||
|
in
|
||||||
|
go [ ] list;
|
||||||
|
|
||||||
|
hasSuffix =
|
||||||
|
suffix: str:
|
||||||
|
let
|
||||||
|
lenStr = lib.stringLength str;
|
||||||
|
lenSuffix = lib.stringLength suffix;
|
||||||
|
in
|
||||||
|
lenStr >= lenSuffix && lib.substring (lenStr - lenSuffix) lenSuffix str == suffix;
|
||||||
|
|
||||||
|
nameValuePair = name: value: { inherit name value; };
|
||||||
|
|
||||||
|
secretsPath = ./secrets;
|
||||||
|
|
||||||
|
# Read all directories in secrets/
|
||||||
|
secretDirs = if lib.pathExists secretsPath then lib.readDir secretsPath else { };
|
||||||
|
|
||||||
|
# Filter to only directories (excludes files)
|
||||||
|
isDirectory = name: type: type == "directory";
|
||||||
|
directories = lib.filter (name: isDirectory name secretDirs.${name}) (lib.attrNames secretDirs);
|
||||||
|
|
||||||
|
# Read public keys from a directory and convert to age format
|
||||||
|
readHostKeys =
|
||||||
|
dirName:
|
||||||
|
let
|
||||||
|
dirPath = secretsPath + "/${dirName}";
|
||||||
|
files = if lib.pathExists dirPath then lib.readDir dirPath else { };
|
||||||
|
|
||||||
|
# Prefer .age.pub files (pre-converted), fall back to .pub files
|
||||||
|
agePubFiles = filterAttrs (name: type: type == "regular" && hasSuffix ".age.pub" name) files;
|
||||||
|
|
||||||
|
sshPubFiles = filterAttrs (
|
||||||
|
name: type: type == "regular" && hasSuffix ".pub" name && !(hasSuffix ".age.pub" name)
|
||||||
|
) files;
|
||||||
|
|
||||||
|
# Read age public keys (already in correct format)
|
||||||
|
ageKeys = lib.map (
|
||||||
|
name:
|
||||||
|
let
|
||||||
|
content = lib.readFile (dirPath + "/${name}");
|
||||||
|
# Trim whitespace/newlines
|
||||||
|
trimmed = lib.replaceStrings [ "\n" " " "\r" "\t" ] [ "" "" "" "" ] content;
|
||||||
|
in
|
||||||
|
trimmed
|
||||||
|
) (lib.attrNames agePubFiles);
|
||||||
|
|
||||||
|
# For SSH keys, just include them as-is (user needs to convert with ssh-to-age)
|
||||||
|
# Or they can run the update-age-keys.sh script
|
||||||
|
sshKeys =
|
||||||
|
if (lib.length (lib.attrNames sshPubFiles)) > 0 then
|
||||||
|
lib.trace "Warning: ${dirName} has unconverted SSH keys. Run secrets/update-age-keys.sh" [ ]
|
||||||
|
else
|
||||||
|
[ ];
|
||||||
|
in
|
||||||
|
lib.filter (k: k != null && k != "") (ageKeys ++ sshKeys);
|
||||||
|
|
||||||
|
# Build host key mappings: { hostname = [ "age1..." "age2..." ]; }
|
||||||
|
hostKeys = lib.listToAttrs (
|
||||||
|
lib.map (dir: nameValuePair dir (readHostKeys dir)) (
|
||||||
|
lib.filter (d: d != "global" && d != "admins") directories
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
# Global keys that all hosts can use
|
||||||
|
globalKeys = if lib.elem "global" directories then readHostKeys "global" else [ ];
|
||||||
|
|
||||||
|
# Admin keys for editing secrets
|
||||||
|
adminKeys = if lib.elem "admins" directories then readHostKeys "admins" else [ ];
|
||||||
|
|
||||||
|
# All host keys combined
|
||||||
|
allHostKeys = concatLists (lib.attrValues hostKeys);
|
||||||
|
|
||||||
|
# Find all .age files in the secrets directory
|
||||||
|
findSecrets =
|
||||||
|
dir:
|
||||||
|
let
|
||||||
|
dirPath = secretsPath + "/${dir}";
|
||||||
|
files = if lib.pathExists dirPath then lib.readDir dirPath else { };
|
||||||
|
ageFiles = filterAttrs (name: type: type == "regular" && hasSuffix ".age" name) files;
|
||||||
|
in
|
||||||
|
lib.map (name: "secrets/${dir}/${name}") (lib.attrNames ageFiles);
|
||||||
|
|
||||||
|
# Generate recipient list for a secret based on its location
|
||||||
|
getRecipients =
|
||||||
|
secretPath:
|
||||||
|
let
|
||||||
|
# Extract directory name from path: "secrets/nix-builder/foo.age" -> "nix-builder"
|
||||||
|
pathParts = lib.split "/" secretPath;
|
||||||
|
dirName = lib.elemAt pathParts 2;
|
||||||
|
in
|
||||||
|
if dirName == "global" then
|
||||||
|
# Global secrets: all hosts + admins
|
||||||
|
allHostKeys ++ globalKeys ++ adminKeys
|
||||||
|
else if hostKeys ? ${dirName} then
|
||||||
|
# Host-specific secrets: that host + global keys + admins
|
||||||
|
hostKeys.${dirName} ++ globalKeys ++ adminKeys
|
||||||
|
else
|
||||||
|
# Fallback: just admins
|
||||||
|
adminKeys;
|
||||||
|
|
||||||
|
# Find all secrets across all directories
|
||||||
|
allSecrets = concatLists (lib.map findSecrets directories);
|
||||||
|
|
||||||
|
# Generate the configuration
|
||||||
|
secretsConfig = lib.listToAttrs (
|
||||||
|
lib.map (
|
||||||
|
secretPath:
|
||||||
|
let
|
||||||
|
recipients = getRecipients secretPath;
|
||||||
|
# Remove duplicates and empty keys
|
||||||
|
uniqueRecipients = unique (lib.filter (k: k != null && k != "") recipients);
|
||||||
|
in
|
||||||
|
nameValuePair secretPath {
|
||||||
|
publicKeys = uniqueRecipients;
|
||||||
|
}
|
||||||
|
) allSecrets
|
||||||
|
);
|
||||||
|
|
||||||
|
in
|
||||||
|
secretsConfig
|
||||||
|
// {
|
||||||
|
# Export helper information for debugging
|
||||||
|
_meta = {
|
||||||
|
hostKeys = hostKeys;
|
||||||
|
globalKeys = globalKeys;
|
||||||
|
adminKeys = adminKeys;
|
||||||
|
allHostKeys = allHostKeys;
|
||||||
|
discoveredSecrets = allSecrets;
|
||||||
|
};
|
||||||
|
}
|
||||||
36
secrets/update-age-keys.sh
Executable file
36
secrets/update-age-keys.sh
Executable file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# ============================================================================
|
||||||
|
# Update Age Keys from SSH Public Keys
|
||||||
|
# ============================================================================
|
||||||
|
# This script converts SSH public keys to age format for use with ragenix.
|
||||||
|
# Run this after adding new SSH .pub files to create corresponding .age.pub files.
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
echo "Converting SSH public keys to age format..."
|
||||||
|
|
||||||
|
# Find all .pub files that are SSH keys (not already .age.pub)
|
||||||
|
find . -name "*.pub" -not -name "*.age.pub" -type f | while read -r pubkey; do
|
||||||
|
# Check if it's an SSH key
|
||||||
|
if grep -q "^ssh-" "$pubkey" 2>/dev/null || grep -q "^ecdsa-" "$pubkey" 2>/dev/null; then
|
||||||
|
age_key=$(nix shell nixpkgs#ssh-to-age -c ssh-to-age < "$pubkey" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ -n "$age_key" ]; then
|
||||||
|
# Create .age.pub file with the age key
|
||||||
|
age_file="${pubkey%.pub}.age.pub"
|
||||||
|
echo "$age_key" > "$age_file"
|
||||||
|
echo "✓ Converted: $pubkey -> $age_file"
|
||||||
|
else
|
||||||
|
echo "⚠ Skipped: $pubkey (conversion failed)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Done! Age public keys have been generated."
|
||||||
|
echo "You can now use ragenix to manage secrets:"
|
||||||
|
echo " ragenix -e secrets/global/my-secret.age"
|
||||||
|
echo " ragenix -r # Re-key all secrets with updated keys"
|
||||||
8
secrets/usda-dash/default.nix
Normal file
8
secrets/usda-dash/default.nix
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Host-specific secret configuration for usda-dash
|
||||||
|
{
|
||||||
|
usda-vision-azure-env = {
|
||||||
|
mode = "0600";
|
||||||
|
owner = "root";
|
||||||
|
group = "root";
|
||||||
|
};
|
||||||
|
}
|
||||||
1
secrets/usda-dash/ssh_host_ed25519_key.age.pub
Normal file
1
secrets/usda-dash/ssh_host_ed25519_key.age.pub
Normal file
@@ -0,0 +1 @@
|
|||||||
|
age1lr24yvk7rdfh5wkle7h32jpxqxm2e8vk85mc4plv370u2sh4yfmszaaejx
|
||||||
1
secrets/usda-dash/ssh_host_ed25519_key.pub
Normal file
1
secrets/usda-dash/ssh_host_ed25519_key.pub
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHI73LOEK2RgfjhZWpryntlLbx0LouHrhQ6v0vZu4Etr root@usda-dash
|
||||||
BIN
secrets/usda-dash/usda-vision-env.age
Normal file
BIN
secrets/usda-dash/usda-vision-env.age
Normal file
Binary file not shown.
@@ -11,21 +11,123 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
|
|
||||||
lib.mkMerge [
|
with lib;
|
||||||
(import ./programs.nix {
|
|
||||||
inherit
|
let
|
||||||
config
|
cfg = config.athenix.sw.builders;
|
||||||
lib
|
in
|
||||||
pkgs
|
{
|
||||||
inputs
|
options.athenix.sw.builders = mkOption {
|
||||||
;
|
type = lib.types.submodule {
|
||||||
})
|
options = {
|
||||||
(import ./services.nix {
|
enable = mkOption {
|
||||||
inherit
|
type = lib.types.bool;
|
||||||
config
|
default = false;
|
||||||
lib
|
description = ''
|
||||||
pkgs
|
Enable build server configuration.
|
||||||
inputs
|
|
||||||
;
|
Includes:
|
||||||
})
|
- SSH host keys for common Git servers (factory.uga.edu, github.com)
|
||||||
]
|
- Gitea Actions runner support (optional)
|
||||||
|
- Build tools and dependencies
|
||||||
|
|
||||||
|
Recommended for: CI/CD servers, build containers, development infrastructure
|
||||||
|
'';
|
||||||
|
example = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
giteaRunner = mkOption {
|
||||||
|
type = lib.types.submodule {
|
||||||
|
options = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Enable Gitea Actions self-hosted runner.
|
||||||
|
|
||||||
|
This runner will connect to a Gitea instance and execute CI/CD workflows.
|
||||||
|
Requires manual setup of the token file before the service will start.
|
||||||
|
'';
|
||||||
|
example = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
url = mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
description = ''
|
||||||
|
URL of the Gitea instance to connect to.
|
||||||
|
This should be the base URL without any path components.
|
||||||
|
'';
|
||||||
|
example = "https://git.factory.uga.edu";
|
||||||
|
};
|
||||||
|
|
||||||
|
tokenFile = mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
default = "/var/lib/gitea-runner-token";
|
||||||
|
description = ''
|
||||||
|
Path to file containing Gitea runner registration token.
|
||||||
|
|
||||||
|
To generate:
|
||||||
|
1. Go to your Gitea repository settings
|
||||||
|
2. Navigate to Actions > Runners
|
||||||
|
3. Click "Create new Runner"
|
||||||
|
4. Save the token to this file:
|
||||||
|
echo "TOKEN=your-token-here" | sudo tee /var/lib/gitea-runner-token > /dev/null
|
||||||
|
|
||||||
|
The service will not start until this file exists.
|
||||||
|
'';
|
||||||
|
example = "/var/secrets/gitea-runner-token";
|
||||||
|
};
|
||||||
|
|
||||||
|
extraLabels = mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ ];
|
||||||
|
description = ''
|
||||||
|
Additional labels to identify this runner in workflow files.
|
||||||
|
Use labels to target specific runners for different job types.
|
||||||
|
'';
|
||||||
|
example = [
|
||||||
|
"self-hosted"
|
||||||
|
"nix"
|
||||||
|
"x86_64-linux"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
name = mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "athenix";
|
||||||
|
description = ''
|
||||||
|
Unique name for this runner instance.
|
||||||
|
Shown in Gitea's runner list and logs.
|
||||||
|
'';
|
||||||
|
example = "nix-builder-1";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
description = "Gitea Actions runner configuration.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
description = "Build server configuration (CI/CD, Gitea Actions).";
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable (mkMerge [
|
||||||
|
(import ./programs.nix {
|
||||||
|
inherit
|
||||||
|
config
|
||||||
|
lib
|
||||||
|
pkgs
|
||||||
|
inputs
|
||||||
|
;
|
||||||
|
})
|
||||||
|
(import ./services.nix {
|
||||||
|
inherit
|
||||||
|
config
|
||||||
|
lib
|
||||||
|
pkgs
|
||||||
|
inputs
|
||||||
|
;
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
{
|
{
|
||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
|
||||||
inputs,
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
|
|
||||||
@@ -10,7 +8,7 @@ with lib;
|
|||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.athenix.sw;
|
cfg = config.athenix.sw;
|
||||||
basePackages = with pkgs; [
|
basePackages = [
|
||||||
# Build-related packages can be added here if needed
|
# Build-related packages can be added here if needed
|
||||||
];
|
];
|
||||||
in
|
in
|
||||||
|
|||||||
214
sw/default.nix
214
sw/default.nix
@@ -10,184 +10,98 @@
|
|||||||
# Software Module Entry Point
|
# Software Module Entry Point
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# This module manages the software configuration for the system. It provides
|
# This module manages the software configuration for the system. It provides
|
||||||
# options to select the system type ('desktop' or 'kiosk') and handles
|
# enable options for each system type (desktop, headless, builders, etc.)
|
||||||
# the conditional importation of the appropriate sub-modules.
|
# that can be enabled independently or in combination. Each type is a proper
|
||||||
|
# NixOS submodule with its own enable flag and type-specific options.
|
||||||
|
|
||||||
with lib;
|
with lib;
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.athenix.sw;
|
cfg = config.athenix.sw;
|
||||||
|
|
||||||
# Normalize type to always be a list
|
|
||||||
swTypes = if isList cfg.type then cfg.type else [ cfg.type ];
|
|
||||||
|
|
||||||
# Helper to check if a type is enabled
|
|
||||||
hasType = type: elem type swTypes;
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
./python.nix
|
./python.nix
|
||||||
./ghostty.nix
|
./ghostty.nix
|
||||||
|
./gc.nix
|
||||||
./updater.nix
|
./updater.nix
|
||||||
./update-ref.nix
|
./update-ref.nix
|
||||||
|
./secrets.nix
|
||||||
|
./desktop
|
||||||
|
./headless
|
||||||
|
./builders
|
||||||
|
./tablet-kiosk
|
||||||
|
./stateless-kiosk
|
||||||
|
inputs.home-manager.nixosModules.home-manager
|
||||||
|
inputs.agenix.nixosModules.default
|
||||||
|
inputs.disko.nixosModules.disko
|
||||||
];
|
];
|
||||||
|
|
||||||
options.athenix.sw = {
|
options.athenix.sw = {
|
||||||
enable = mkEnableOption "Standard Workstation Configuration";
|
enable = mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Enable standard workstation configuration with base packages.
|
||||||
|
|
||||||
|
Provides:
|
||||||
|
- Base CLI tools (htop, git, binutils)
|
||||||
|
- Shell configuration (Zsh)
|
||||||
|
- Secret management (agenix)
|
||||||
|
- Oh My Posh shell theme
|
||||||
|
|
||||||
|
This is typically enabled automatically when any sw type is enabled.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
type = mkOption {
|
type = mkOption {
|
||||||
type = types.oneOf [
|
type = lib.types.nullOr (lib.types.either lib.types.str (lib.types.listOf lib.types.str));
|
||||||
(types.enum [
|
default = null;
|
||||||
"desktop"
|
description = "DEPRECATED: Use athenix.sw.<type>.enable instead. Legacy type selection.";
|
||||||
"tablet-kiosk"
|
visible = false;
|
||||||
"headless"
|
|
||||||
"stateless-kiosk"
|
|
||||||
"builders"
|
|
||||||
])
|
|
||||||
(types.listOf (
|
|
||||||
types.enum [
|
|
||||||
"desktop"
|
|
||||||
"tablet-kiosk"
|
|
||||||
"headless"
|
|
||||||
"stateless-kiosk"
|
|
||||||
"builders"
|
|
||||||
]
|
|
||||||
))
|
|
||||||
];
|
|
||||||
default = "desktop";
|
|
||||||
description = "Type(s) of system configuration. Can be a single type or a list of types to combine multiple configurations.";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extraPackages = mkOption {
|
extraPackages = mkOption {
|
||||||
type = types.listOf types.package;
|
type = lib.types.listOf lib.types.package;
|
||||||
default = [ ];
|
default = [ ];
|
||||||
description = "Extra packages to install.";
|
description = ''
|
||||||
|
Additional system packages to install beyond the defaults.
|
||||||
|
These packages are added to environment.systemPackages.
|
||||||
|
'';
|
||||||
|
example = lib.literalExpression "[ pkgs.vim pkgs.wget pkgs.curl ]";
|
||||||
};
|
};
|
||||||
|
|
||||||
excludePackages = mkOption {
|
excludePackages = mkOption {
|
||||||
type = types.listOf types.package;
|
type = lib.types.listOf lib.types.package;
|
||||||
default = [ ];
|
default = [ ];
|
||||||
description = "Packages to exclude from the default list.";
|
description = ''
|
||||||
};
|
Packages to exclude from the default package list.
|
||||||
|
Useful for removing unwanted default packages.
|
||||||
kioskUrl = mkOption {
|
'';
|
||||||
type = types.str;
|
example = lib.literalExpression "[ pkgs.htop ]";
|
||||||
default = "https://ha.factory.uga.edu";
|
|
||||||
description = "URL to open in Chromium kiosk mode.";
|
|
||||||
};
|
|
||||||
|
|
||||||
# Builders-specific options
|
|
||||||
builders = mkOption {
|
|
||||||
type = types.submodule {
|
|
||||||
options = {
|
|
||||||
giteaRunner = {
|
|
||||||
enable = mkEnableOption "Gitea Actions self-hosted runner";
|
|
||||||
|
|
||||||
url = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
description = "Gitea instance URL for the runner";
|
|
||||||
};
|
|
||||||
|
|
||||||
tokenFile = mkOption {
|
|
||||||
type = types.path;
|
|
||||||
default = "/var/lib/gitea-runner-token";
|
|
||||||
description = ''
|
|
||||||
Path to file containing Gitea runner token.
|
|
||||||
Generate in Gitea repository settings under Actions > Runners.
|
|
||||||
The token must have runner registration access.
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
extraLabels = mkOption {
|
|
||||||
type = types.listOf types.str;
|
|
||||||
default = [ ];
|
|
||||||
description = "Extra labels to identify this runner in workflows";
|
|
||||||
};
|
|
||||||
|
|
||||||
name = mkOption {
|
|
||||||
type = types.str;
|
|
||||||
default = "athenix";
|
|
||||||
description = "Name of the Gitea runner service";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
default = { };
|
|
||||||
description = "Builder-specific configuration options";
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable (mkMerge [
|
config = mkIf cfg.enable {
|
||||||
{
|
# ========== System-Wide Configuration ==========
|
||||||
# ========== System-Wide Configuration ==========
|
nixpkgs.config.allowUnfree = true;
|
||||||
nixpkgs.config.allowUnfree = true;
|
|
||||||
|
|
||||||
# ========== Shell Configuration ==========
|
# ========== Shell Configuration ==========
|
||||||
programs.zsh.enable = true;
|
programs.zsh.enable = true;
|
||||||
programs.nix-ld.enable = true; # Allow running non-NixOS binaries
|
programs.nix-ld.enable = true; # Allow running non-NixOS binaries
|
||||||
|
|
||||||
# ========== Base Packages ==========
|
# ========== Base Packages ==========
|
||||||
environment.systemPackages =
|
environment.systemPackages =
|
||||||
with pkgs;
|
with pkgs;
|
||||||
subtractLists cfg.excludePackages [
|
subtractLists cfg.excludePackages [
|
||||||
htop # System monitor
|
htop # System monitor
|
||||||
binutils # Binary utilities
|
binutils # Binary utilities
|
||||||
zsh # Z shell
|
zsh # Z shell
|
||||||
git # Version control
|
git # Version control
|
||||||
oh-my-posh # Shell prompt theme
|
oh-my-posh # Shell prompt theme
|
||||||
inputs.agenix.packages.${stdenv.hostPlatform.system}.default # Secret management
|
age # Simple file encryption tool
|
||||||
];
|
age-plugin-fido2-hmac # age FIDO2 support
|
||||||
}
|
inputs.agenix.packages.${stdenv.hostPlatform.system}.default # Secret management
|
||||||
# ========== Software Profile Imports ==========
|
];
|
||||||
(mkIf (hasType "desktop") (
|
};
|
||||||
import ./desktop {
|
|
||||||
inherit
|
|
||||||
config
|
|
||||||
lib
|
|
||||||
pkgs
|
|
||||||
inputs
|
|
||||||
;
|
|
||||||
}
|
|
||||||
))
|
|
||||||
(mkIf (hasType "tablet-kiosk") (
|
|
||||||
import ./tablet-kiosk {
|
|
||||||
inherit
|
|
||||||
config
|
|
||||||
lib
|
|
||||||
pkgs
|
|
||||||
inputs
|
|
||||||
;
|
|
||||||
}
|
|
||||||
))
|
|
||||||
(mkIf (hasType "headless") (
|
|
||||||
import ./headless {
|
|
||||||
inherit
|
|
||||||
config
|
|
||||||
lib
|
|
||||||
pkgs
|
|
||||||
inputs
|
|
||||||
;
|
|
||||||
}
|
|
||||||
))
|
|
||||||
(mkIf (hasType "stateless-kiosk") (
|
|
||||||
import ./stateless-kiosk {
|
|
||||||
inherit
|
|
||||||
config
|
|
||||||
lib
|
|
||||||
pkgs
|
|
||||||
inputs
|
|
||||||
;
|
|
||||||
}
|
|
||||||
))
|
|
||||||
(mkIf (hasType "builders") (
|
|
||||||
import ./builders {
|
|
||||||
inherit
|
|
||||||
config
|
|
||||||
lib
|
|
||||||
pkgs
|
|
||||||
inputs
|
|
||||||
;
|
|
||||||
}
|
|
||||||
))
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,21 +10,56 @@
|
|||||||
inputs,
|
inputs,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
lib.mkMerge [
|
|
||||||
(import ./programs.nix {
|
with lib;
|
||||||
inherit
|
|
||||||
config
|
let
|
||||||
lib
|
cfg = config.athenix.sw.desktop;
|
||||||
pkgs
|
in
|
||||||
inputs
|
{
|
||||||
;
|
options.athenix.sw.desktop = mkOption {
|
||||||
})
|
type = lib.types.submodule {
|
||||||
(import ./services.nix {
|
options = {
|
||||||
inherit
|
enable = mkOption {
|
||||||
config
|
type = lib.types.bool;
|
||||||
lib
|
default = false;
|
||||||
pkgs
|
description = ''
|
||||||
inputs
|
Enable full desktop environment with KDE Plasma 6.
|
||||||
;
|
|
||||||
})
|
Includes:
|
||||||
]
|
- KDE Plasma 6 desktop with SDDM display manager
|
||||||
|
- Full graphical software suite (Firefox, Chromium, LibreOffice)
|
||||||
|
- Printing and scanning support (CUPS)
|
||||||
|
- Virtualization (libvirt, virt-manager)
|
||||||
|
- Bluetooth and audio (PipeWire)
|
||||||
|
- Video conferencing (Zoom, Teams)
|
||||||
|
|
||||||
|
Recommended for: Workstations, development machines, user desktops
|
||||||
|
'';
|
||||||
|
example = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
description = "Desktop environment configuration (KDE Plasma 6).";
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable (mkMerge [
|
||||||
|
(import ./programs.nix {
|
||||||
|
inherit
|
||||||
|
config
|
||||||
|
lib
|
||||||
|
pkgs
|
||||||
|
inputs
|
||||||
|
;
|
||||||
|
})
|
||||||
|
(import ./services.nix {
|
||||||
|
inherit
|
||||||
|
config
|
||||||
|
lib
|
||||||
|
pkgs
|
||||||
|
inputs
|
||||||
|
;
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
inputs,
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
config,
|
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
...
|
...
|
||||||
|
|||||||
75
sw/gc.nix
Normal file
75
sw/gc.nix
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
options.athenix = {
|
||||||
|
system.gc = {
|
||||||
|
enable = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Enable automatic garbage collection of old NixOS generations.
|
||||||
|
Helps keep disk usage under control on long-running systems.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
frequency = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "weekly";
|
||||||
|
description = ''
|
||||||
|
How often to run garbage collection (systemd timer format).
|
||||||
|
|
||||||
|
Common values: "daily", "weekly", "monthly"
|
||||||
|
Advanced: "*-*-* 03:00:00" (daily at 3 AM)
|
||||||
|
'';
|
||||||
|
example = "daily";
|
||||||
|
};
|
||||||
|
retentionDays = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 30;
|
||||||
|
description = ''
|
||||||
|
Number of days to keep old system generations before deletion.
|
||||||
|
|
||||||
|
Older generations allow rolling back system changes.
|
||||||
|
Recommended: 30-90 days for workstations, 7-14 for servers.
|
||||||
|
'';
|
||||||
|
example = 60;
|
||||||
|
};
|
||||||
|
optimise = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Whether to automatically hard-link identical files in the Nix store.
|
||||||
|
Can save significant disk space but uses CPU during optimization.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
host.buildMethods = lib.mkOption {
|
||||||
|
type = lib.types.listOf lib.types.str;
|
||||||
|
default = [ "installer-iso" ];
|
||||||
|
description = ''
|
||||||
|
List of allowed build methods for this host (used by installer/artifacts.nix).
|
||||||
|
Supported methods:
|
||||||
|
- "installer-iso": Generates an auto-install ISO that installs this configuration to disk.
|
||||||
|
- "iso": Generates a live ISO (using nixos-generators).
|
||||||
|
- "ipxe": Generates iPXE netboot artifacts (kernel, initrd, script).
|
||||||
|
- "lxc": Generates an LXC container tarball.
|
||||||
|
- "proxmox": Generates a Proxmox VMA archive.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
# Automatic Garbage Collection
|
||||||
|
nix.gc = lib.mkIf config.athenix.system.gc.enable {
|
||||||
|
automatic = true;
|
||||||
|
dates = config.athenix.system.gc.frequency;
|
||||||
|
options = "--delete-older-than ${toString config.athenix.system.gc.retentionDays}d";
|
||||||
|
};
|
||||||
|
|
||||||
|
# Optimize storage
|
||||||
|
nix.optimise.automatic = config.athenix.system.gc.optimise;
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -12,7 +12,11 @@
|
|||||||
# It reconstructs the terminfo database from the provided definition and
|
# It reconstructs the terminfo database from the provided definition and
|
||||||
# adds it to the system packages.
|
# adds it to the system packages.
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
let
|
let
|
||||||
|
cfg = config.athenix.sw;
|
||||||
|
|
||||||
ghostty-terminfo = pkgs.runCommand "ghostty-terminfo" { } ''
|
ghostty-terminfo = pkgs.runCommand "ghostty-terminfo" { } ''
|
||||||
mkdir -p $out/share/terminfo
|
mkdir -p $out/share/terminfo
|
||||||
cat > ghostty.info <<'EOF'
|
cat > ghostty.info <<'EOF'
|
||||||
@@ -101,5 +105,7 @@ let
|
|||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
environment.systemPackages = [ ghostty-terminfo ];
|
config = mkIf cfg.enable {
|
||||||
|
environment.systemPackages = [ ghostty-terminfo ];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,21 +11,53 @@
|
|||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
|
|
||||||
lib.mkMerge [
|
with lib;
|
||||||
(import ./programs.nix {
|
|
||||||
inherit
|
let
|
||||||
config
|
cfg = config.athenix.sw.headless;
|
||||||
lib
|
in
|
||||||
pkgs
|
{
|
||||||
inputs
|
options.athenix.sw.headless = mkOption {
|
||||||
;
|
type = lib.types.submodule {
|
||||||
})
|
options = {
|
||||||
(import ./services.nix {
|
enable = mkOption {
|
||||||
inherit
|
type = lib.types.bool;
|
||||||
config
|
default = false;
|
||||||
lib
|
description = ''
|
||||||
pkgs
|
Enable minimal headless server configuration.
|
||||||
inputs
|
|
||||||
;
|
Includes:
|
||||||
})
|
- SSH server with password authentication
|
||||||
]
|
- Minimal CLI tools (tmux, man)
|
||||||
|
- Systemd-networkd for networking
|
||||||
|
- No graphical environment
|
||||||
|
|
||||||
|
Recommended for: Servers, containers (LXC), WSL, remote systems
|
||||||
|
'';
|
||||||
|
example = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
description = "Headless server configuration (SSH, minimal CLI tools).";
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable (mkMerge [
|
||||||
|
(import ./programs.nix {
|
||||||
|
inherit
|
||||||
|
config
|
||||||
|
lib
|
||||||
|
pkgs
|
||||||
|
inputs
|
||||||
|
;
|
||||||
|
})
|
||||||
|
(import ./services.nix {
|
||||||
|
inherit
|
||||||
|
config
|
||||||
|
lib
|
||||||
|
pkgs
|
||||||
|
inputs
|
||||||
|
;
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
inputs,
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
{
|
{
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
|
|
||||||
|
|||||||
@@ -18,10 +18,27 @@ let
|
|||||||
cfg = config.athenix.sw.python;
|
cfg = config.athenix.sw.python;
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.athenix.sw.python = {
|
options.athenix.sw.python = lib.mkOption {
|
||||||
enable = mkEnableOption "Python development tools (pixi, uv)" // {
|
type = lib.types.submodule {
|
||||||
default = true;
|
options = {
|
||||||
|
enable = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Enable Python development tools (pixi, uv).
|
||||||
|
|
||||||
|
Provides:
|
||||||
|
- pixi: Fast, cross-platform package manager for Python
|
||||||
|
- uv: Extremely fast Python package installer and resolver
|
||||||
|
|
||||||
|
These tools manage project-based dependencies rather than global
|
||||||
|
Python packages, avoiding conflicts and improving reproducibility.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
default = { };
|
||||||
|
description = "Python development environment configuration.";
|
||||||
};
|
};
|
||||||
|
|
||||||
config = mkIf cfg.enable {
|
config = mkIf cfg.enable {
|
||||||
|
|||||||
230
sw/secrets.nix
Normal file
230
sw/secrets.nix
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# Automatic Secret Management with Agenix
|
||||||
|
# ============================================================================
|
||||||
|
# This module automatically loads age-encrypted secrets from ./secrets based on
|
||||||
|
# the hostname. Secrets are organized by directory:
|
||||||
|
# - ./secrets/global/ -> Installed on ALL systems
|
||||||
|
# - ./secrets/{hostname}/ -> Installed only on matching host
|
||||||
|
#
|
||||||
|
# Secret files should be .age encrypted files. Public keys (.pub) are ignored.
|
||||||
|
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.athenix.sw;
|
||||||
|
secretsPath = ../secrets;
|
||||||
|
|
||||||
|
# Get the fleet-assigned hostname (avoids issues with LXC empty hostnames)
|
||||||
|
hostname = config.athenix.host.name;
|
||||||
|
|
||||||
|
# Read all directories in ./secrets
|
||||||
|
secretDirs = if builtins.pathExists secretsPath then builtins.readDir secretsPath else { };
|
||||||
|
|
||||||
|
# Filter to only directories (excludes files)
|
||||||
|
isDirectory = name: type: type == "directory";
|
||||||
|
directories = lib.filterAttrs isDirectory secretDirs;
|
||||||
|
|
||||||
|
# Read secrets from a specific directory
|
||||||
|
readSecretsFromDir =
|
||||||
|
dirName:
|
||||||
|
let
|
||||||
|
dirPath = secretsPath + "/${dirName}";
|
||||||
|
files = builtins.readDir dirPath;
|
||||||
|
|
||||||
|
# Check if there's a default.nix with custom secret configurations
|
||||||
|
hasDefaultNix = files ? "default.nix";
|
||||||
|
customConfigs = if hasDefaultNix then import (dirPath + "/default.nix") else { };
|
||||||
|
|
||||||
|
# Only include .age files (exclude .pub public keys and other files)
|
||||||
|
secretFiles = lib.filterAttrs (name: type: type == "regular" && lib.hasSuffix ".age" name) files;
|
||||||
|
in
|
||||||
|
lib.mapAttrs' (
|
||||||
|
name: _:
|
||||||
|
let
|
||||||
|
# Remove .age extension for the secret name
|
||||||
|
secretName = lib.removeSuffix ".age" name;
|
||||||
|
|
||||||
|
# Get custom config for this secret if defined
|
||||||
|
customConfig = customConfigs.${secretName} or { };
|
||||||
|
|
||||||
|
# Base configuration with file path
|
||||||
|
baseConfig = {
|
||||||
|
file = dirPath + "/${name}";
|
||||||
|
};
|
||||||
|
in
|
||||||
|
lib.nameValuePair secretName (baseConfig // customConfig)
|
||||||
|
) secretFiles;
|
||||||
|
|
||||||
|
# Read public keys from a specific directory and map to private key paths
|
||||||
|
readIdentityPathsFromDir =
|
||||||
|
dirName:
|
||||||
|
let
|
||||||
|
dirPath = secretsPath + "/${dirName}";
|
||||||
|
files = if builtins.pathExists dirPath then builtins.readDir dirPath else { };
|
||||||
|
# Only include .pub public key files
|
||||||
|
pubKeyFiles = lib.filterAttrs (name: type: type == "regular" && lib.hasSuffix ".pub" name) files;
|
||||||
|
in
|
||||||
|
lib.mapAttrsToList (
|
||||||
|
name: _:
|
||||||
|
let
|
||||||
|
# Map public key filename to expected private key location
|
||||||
|
baseName = lib.removeSuffix ".pub" name;
|
||||||
|
filePath = dirPath + "/${name}";
|
||||||
|
fileContent = builtins.readFile filePath;
|
||||||
|
# Check if it's an SSH key by looking at the content
|
||||||
|
isSSHKey = lib.hasPrefix "ssh-" fileContent || lib.hasPrefix "ecdsa-" fileContent;
|
||||||
|
in
|
||||||
|
if lib.hasPrefix "ssh_host_" name then
|
||||||
|
# SSH host keys: ssh_host_ed25519_key.pub -> /etc/ssh/ssh_host_ed25519_key
|
||||||
|
"/etc/ssh/${baseName}"
|
||||||
|
else if name == "identity.pub" then
|
||||||
|
# Standard age identity: identity.pub -> /etc/age/identity.key
|
||||||
|
"/etc/age/identity.key"
|
||||||
|
else if isSSHKey then
|
||||||
|
# Other SSH keys (user keys, etc.): hunter_halloran_key.pub -> /etc/ssh/hunter_halloran_key
|
||||||
|
"/etc/ssh/${baseName}"
|
||||||
|
else
|
||||||
|
# Generic age keys: key.pub -> /etc/age/key
|
||||||
|
"/etc/age/${baseName}"
|
||||||
|
) pubKeyFiles;
|
||||||
|
|
||||||
|
# Determine which secrets apply to this host
|
||||||
|
applicableSecrets =
|
||||||
|
let
|
||||||
|
# Global secrets apply to all hosts
|
||||||
|
globalSecrets = if directories ? "global" then readSecretsFromDir "global" else { };
|
||||||
|
|
||||||
|
# Host-specific secrets
|
||||||
|
hostSecrets = if directories ? ${hostname} then readSecretsFromDir hostname else { };
|
||||||
|
in
|
||||||
|
globalSecrets // hostSecrets; # Host-specific secrets override global if same name
|
||||||
|
|
||||||
|
# Determine which identity paths (private keys) to use for decryption
|
||||||
|
identityPaths =
|
||||||
|
let
|
||||||
|
# Global identity paths (keys in global/ that all hosts can use)
|
||||||
|
globalPaths = if directories ? "global" then readIdentityPathsFromDir "global" else [ ];
|
||||||
|
|
||||||
|
# Host-specific identity paths
|
||||||
|
hostPaths = if directories ? ${hostname} then readIdentityPathsFromDir hostname else [ ];
|
||||||
|
|
||||||
|
# Default paths that NixOS/agenix use
|
||||||
|
defaultPaths = [
|
||||||
|
"/etc/ssh/ssh_host_rsa_key"
|
||||||
|
"/etc/ssh/ssh_host_ed25519_key"
|
||||||
|
"/etc/age/identity.key"
|
||||||
|
];
|
||||||
|
|
||||||
|
# Combine all paths and remove duplicates
|
||||||
|
allPaths = lib.unique (defaultPaths ++ globalPaths ++ hostPaths);
|
||||||
|
in
|
||||||
|
allPaths;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.athenix.sw.secrets = {
|
||||||
|
enable = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
description = ''
|
||||||
|
Enable automatic secret management using agenix.
|
||||||
|
|
||||||
|
Secrets are loaded from ./secrets based on directory structure:
|
||||||
|
- ./secrets/global/ -> All systems
|
||||||
|
- ./secrets/{hostname}/ -> Specific host only
|
||||||
|
|
||||||
|
Only .age encrypted files are loaded; .pub files are ignored.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
extraSecrets = mkOption {
|
||||||
|
type = types.attrsOf (
|
||||||
|
types.submodule {
|
||||||
|
options = {
|
||||||
|
file = mkOption {
|
||||||
|
type = types.path;
|
||||||
|
description = "Path to the encrypted secret file";
|
||||||
|
};
|
||||||
|
mode = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "0400";
|
||||||
|
description = "Permissions mode for the decrypted secret";
|
||||||
|
};
|
||||||
|
owner = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "root";
|
||||||
|
description = "Owner of the decrypted secret file";
|
||||||
|
};
|
||||||
|
group = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "root";
|
||||||
|
description = "Group of the decrypted secret file";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
default = { };
|
||||||
|
description = ''
|
||||||
|
Additional secrets to define manually, beyond the auto-discovered ones.
|
||||||
|
Use this for secrets that need custom permissions or are stored elsewhere.
|
||||||
|
'';
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
{
|
||||||
|
"my-secret" = {
|
||||||
|
file = ./secrets/custom/secret.age;
|
||||||
|
mode = "0440";
|
||||||
|
owner = "nginx";
|
||||||
|
group = "nginx";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf (cfg.enable && cfg.secrets.enable) {
|
||||||
|
# Auto-discovered secrets with default permissions
|
||||||
|
age.secrets = applicableSecrets // cfg.secrets.extraSecrets;
|
||||||
|
|
||||||
|
# Generate age identity files from SSH host keys at boot
|
||||||
|
# This is needed because age can't reliably use OpenSSH private keys directly
|
||||||
|
# Must run before agenix tries to decrypt secrets
|
||||||
|
system.activationScripts.convertSshToAge = {
|
||||||
|
deps = [
|
||||||
|
"users"
|
||||||
|
"groups"
|
||||||
|
];
|
||||||
|
text = ''
|
||||||
|
mkdir -p /etc/age
|
||||||
|
if [ -f /etc/ssh/ssh_host_ed25519_key ]; then
|
||||||
|
${pkgs.ssh-to-age}/bin/ssh-to-age -private-key -i /etc/ssh/ssh_host_ed25519_key > /etc/age/ssh_host_ed25519.age || true
|
||||||
|
chmod 600 /etc/age/ssh_host_ed25519.age 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
if [ -f /etc/ssh/ssh_host_rsa_key ]; then
|
||||||
|
${pkgs.ssh-to-age}/bin/ssh-to-age -private-key -i /etc/ssh/ssh_host_rsa_key > /etc/age/ssh_host_rsa.age 2>/dev/null || true
|
||||||
|
chmod 600 /etc/age/ssh_host_rsa.age 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
# Add the converted age keys to identity paths (in addition to auto-discovered ones)
|
||||||
|
age.identityPaths = identityPaths ++ [
|
||||||
|
"/etc/age/ssh_host_ed25519.age"
|
||||||
|
"/etc/age/ssh_host_rsa.age"
|
||||||
|
];
|
||||||
|
|
||||||
|
# Optional: Add assertion to warn if no secrets found
|
||||||
|
warnings =
|
||||||
|
let
|
||||||
|
hasSecrets = (builtins.length (builtins.attrNames applicableSecrets)) > 0;
|
||||||
|
in
|
||||||
|
lib.optional (
|
||||||
|
!hasSecrets
|
||||||
|
) "No age-encrypted secrets found in ./secrets/global/ or ./secrets/${hostname}/";
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -7,37 +7,83 @@
|
|||||||
inputs,
|
inputs,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
lib.mkMerge [
|
|
||||||
(import ./kiosk-browser.nix {
|
with lib;
|
||||||
inherit
|
|
||||||
config
|
let
|
||||||
lib
|
cfg = config.athenix.sw.stateless-kiosk;
|
||||||
pkgs
|
in
|
||||||
inputs
|
{
|
||||||
;
|
options.athenix.sw.stateless-kiosk = mkOption {
|
||||||
})
|
type = lib.types.submodule {
|
||||||
(import ./services.nix {
|
options = {
|
||||||
inherit
|
enable = mkOption {
|
||||||
config
|
type = lib.types.bool;
|
||||||
lib
|
default = false;
|
||||||
pkgs
|
description = ''
|
||||||
inputs
|
Enable stateless kiosk mode for diskless PXE boot systems.
|
||||||
;
|
|
||||||
})
|
Includes:
|
||||||
(import ./net.nix {
|
- Sway (Wayland compositor)
|
||||||
inherit
|
- Chromium in fullscreen kiosk mode
|
||||||
config
|
- MAC address-based URL routing
|
||||||
lib
|
- Network-only boot (no local storage)
|
||||||
pkgs
|
- Auto-start browser on boot
|
||||||
inputs
|
|
||||||
;
|
Recommended for: Assembly line stations, diskless kiosks, PXE boot displays
|
||||||
})
|
'';
|
||||||
(import ./programs.nix {
|
example = true;
|
||||||
inherit
|
};
|
||||||
config
|
|
||||||
lib
|
kioskUrl = mkOption {
|
||||||
pkgs
|
type = lib.types.str;
|
||||||
inputs
|
default = "https://ha.factory.uga.edu";
|
||||||
;
|
description = ''
|
||||||
})
|
Default URL to display in the kiosk browser.
|
||||||
]
|
|
||||||
|
Note: For stateless-kiosk, MAC address-based routing may override this.
|
||||||
|
See sw/stateless-kiosk/mac-hostmap.nix for MAC-to-URL mappings.
|
||||||
|
'';
|
||||||
|
example = "https://homeassistant.lan:8123/lovelace/dashboard";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
description = "Stateless kiosk configuration (PXE boot, Sway, MAC-based routing).";
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable (mkMerge [
|
||||||
|
(import ./kiosk-browser.nix {
|
||||||
|
inherit
|
||||||
|
config
|
||||||
|
lib
|
||||||
|
pkgs
|
||||||
|
inputs
|
||||||
|
;
|
||||||
|
})
|
||||||
|
(import ./services.nix {
|
||||||
|
inherit
|
||||||
|
config
|
||||||
|
lib
|
||||||
|
pkgs
|
||||||
|
inputs
|
||||||
|
;
|
||||||
|
})
|
||||||
|
(import ./net.nix {
|
||||||
|
inherit
|
||||||
|
config
|
||||||
|
lib
|
||||||
|
pkgs
|
||||||
|
inputs
|
||||||
|
;
|
||||||
|
})
|
||||||
|
(import ./programs.nix {
|
||||||
|
inherit
|
||||||
|
config
|
||||||
|
lib
|
||||||
|
pkgs
|
||||||
|
inputs
|
||||||
|
;
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
# This module configures Chromium for kiosk mode under Sway.
|
# This module configures Chromium for kiosk mode under Sway.
|
||||||
# It includes a startup script that determines the kiosk URL based on the machine's MAC address.
|
# It includes a startup script that determines the kiosk URL based on the machine's MAC address.
|
||||||
{
|
{
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
pkgs,
|
||||||
|
inputs,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
macCaseBuilder = (import ./mac-hostmap.nix { inherit lib; }).macCaseBuilder;
|
macCaseBuilder = inputs.self.lib.macCaseBuilder;
|
||||||
macCases = macCaseBuilder {
|
macCases = macCaseBuilder {
|
||||||
varName = "STATION";
|
varName = "STATION";
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
# Shared MAC address to station mapping and case builder for stateless-kiosk modules
|
|
||||||
{ lib }:
|
|
||||||
let
|
|
||||||
hostmap = {
|
|
||||||
"00:e0:4c:46:0b:32" = "1";
|
|
||||||
"00:e0:4c:46:07:26" = "2";
|
|
||||||
"00:e0:4c:46:05:94" = "3";
|
|
||||||
"00:e0:4c:46:07:11" = "4";
|
|
||||||
"00:e0:4c:46:08:02" = "5";
|
|
||||||
"00:e0:4c:46:08:5c" = "6";
|
|
||||||
};
|
|
||||||
# macCaseBuilder: builds a shell case statement from a hostmap
|
|
||||||
# varName: the shell variable to assign
|
|
||||||
# prefix: optional string to prepend to the value (default: "")
|
|
||||||
# attrset: attribute set to use (default: hostmap)
|
|
||||||
macCaseBuilder =
|
|
||||||
{
|
|
||||||
varName,
|
|
||||||
prefix ? "",
|
|
||||||
attrset ? hostmap,
|
|
||||||
}:
|
|
||||||
lib.concatStringsSep "\n" (
|
|
||||||
lib.mapAttrsToList (mac: val: " ${mac}) ${varName}=${prefix}${val} ;;") attrset
|
|
||||||
);
|
|
||||||
in
|
|
||||||
{
|
|
||||||
inherit hostmap macCaseBuilder;
|
|
||||||
}
|
|
||||||
@@ -26,5 +26,5 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
# Disable systemd-networkd and systemd-hostnamed
|
# Disable systemd-networkd and systemd-hostnamed
|
||||||
systemd.network.enable = false;
|
systemd.network.enable = lib.mkForce false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
{
|
{
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
{
|
{
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
pkgs,
|
||||||
|
inputs,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
macCaseBuilder = (import ./mac-hostmap.nix { inherit lib; }).macCaseBuilder;
|
macCaseBuilder = inputs.self.lib.macCaseBuilder;
|
||||||
shellCases = macCaseBuilder {
|
shellCases = macCaseBuilder {
|
||||||
varName = "NEW_HOST";
|
varName = "NEW_HOST";
|
||||||
prefix = "nix-station";
|
prefix = "nix-station";
|
||||||
|
|||||||
@@ -5,29 +5,74 @@
|
|||||||
inputs,
|
inputs,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
lib.mkMerge [
|
|
||||||
(import ./programs.nix {
|
with lib;
|
||||||
inherit
|
|
||||||
config
|
let
|
||||||
lib
|
cfg = config.athenix.sw.tablet-kiosk;
|
||||||
pkgs
|
in
|
||||||
inputs
|
{
|
||||||
;
|
options.athenix.sw.tablet-kiosk = mkOption {
|
||||||
})
|
type = lib.types.submodule {
|
||||||
(import ./services.nix {
|
options = {
|
||||||
inherit
|
enable = mkOption {
|
||||||
config
|
type = lib.types.bool;
|
||||||
lib
|
default = false;
|
||||||
pkgs
|
description = ''
|
||||||
inputs
|
Enable tablet kiosk mode with touch-optimized interface.
|
||||||
;
|
|
||||||
})
|
Includes:
|
||||||
(import ./gsettings.nix {
|
- Phosh mobile desktop environment
|
||||||
inherit
|
- Chromium in fullscreen kiosk mode
|
||||||
config
|
- On-screen keyboard (Squeekboard)
|
||||||
lib
|
- Auto-login and auto-start browser
|
||||||
pkgs
|
- Touch gesture support
|
||||||
inputs
|
- Optimized for Surface Pro tablets
|
||||||
;
|
|
||||||
})
|
Recommended for: Surface tablets, touchscreen kiosks, interactive displays
|
||||||
]
|
'';
|
||||||
|
example = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
kioskUrl = mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "https://ha.factory.uga.edu";
|
||||||
|
description = ''
|
||||||
|
URL to display in the kiosk browser on startup.
|
||||||
|
The browser will automatically navigate to this URL in fullscreen mode.
|
||||||
|
'';
|
||||||
|
example = "https://dashboard.example.com";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
default = { };
|
||||||
|
description = "Tablet kiosk configuration (Phosh, touch interface).";
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable (mkMerge [
|
||||||
|
(import ./programs.nix {
|
||||||
|
inherit
|
||||||
|
config
|
||||||
|
lib
|
||||||
|
pkgs
|
||||||
|
inputs
|
||||||
|
;
|
||||||
|
})
|
||||||
|
(import ./services.nix {
|
||||||
|
inherit
|
||||||
|
config
|
||||||
|
lib
|
||||||
|
pkgs
|
||||||
|
inputs
|
||||||
|
;
|
||||||
|
})
|
||||||
|
(import ./gsettings.nix {
|
||||||
|
inherit
|
||||||
|
config
|
||||||
|
lib
|
||||||
|
pkgs
|
||||||
|
inputs
|
||||||
|
;
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|||||||
@@ -155,7 +155,7 @@
|
|||||||
--noerrdialogs \
|
--noerrdialogs \
|
||||||
--disable-session-crashed-bubble \
|
--disable-session-crashed-bubble \
|
||||||
--disable-infobars \
|
--disable-infobars \
|
||||||
${config.athenix.sw.kioskUrl}
|
${config.athenix.sw.tablet-kiosk.kioskUrl}
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
13
sw/theme.nix
13
sw/theme.nix
@@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
pkgs,
|
pkgs,
|
||||||
config,
|
config,
|
||||||
osConfig,
|
|
||||||
lib,
|
lib,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
@@ -33,6 +32,18 @@ in
|
|||||||
programs.zsh = {
|
programs.zsh = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
||||||
|
initContent = ''
|
||||||
|
bindkey '^[[H' beginning-of-line # Home key
|
||||||
|
bindkey '^[[F' end-of-line # End key
|
||||||
|
bindkey '^[[3~' delete-char # Delete key
|
||||||
|
bindkey '^[[1~' beginning-of-line # Alternative Home key
|
||||||
|
bindkey '^[[4~' end-of-line # Alternative End key
|
||||||
|
bindkey '^[[2~' overwrite-mode # Insert key
|
||||||
|
bindkey '^[[5~' up-line-or-history # Page Up
|
||||||
|
bindkey '^[[6~' down-line-or-history # Page Down
|
||||||
|
bindkey -e
|
||||||
|
'';
|
||||||
|
|
||||||
# Plugins
|
# Plugins
|
||||||
historySubstringSearch = {
|
historySubstringSearch = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|||||||
@@ -1,487 +1,524 @@
|
|||||||
{ pkgs, ... }:
|
|
||||||
{
|
{
|
||||||
environment.systemPackages = with pkgs; [
|
config,
|
||||||
python3
|
lib,
|
||||||
git
|
pkgs,
|
||||||
(pkgs.writeShellScriptBin "update-ref" ''
|
...
|
||||||
set -euo pipefail
|
}:
|
||||||
|
|
||||||
RED='\033[31m'; YEL='\033[33m'; NC='\033[0m'
|
with lib;
|
||||||
die() { printf "''${RED}error:''${NC} %s\n" "$*" >&2; exit 2; }
|
|
||||||
warn() { printf "''${YEL}warning:''${NC} %s\n" "$*" >&2; }
|
|
||||||
|
|
||||||
usage() {
|
let
|
||||||
cat >&2 <<'EOF'
|
cfg = config.athenix.sw;
|
||||||
usage:
|
in
|
||||||
update-ref [-R PATH|--athenix-repo=PATH] [-b BRANCH|--athenix-branch=BRANCH]
|
{
|
||||||
[-m "msg"|--message "msg"]
|
config = mkIf cfg.enable {
|
||||||
[-p[=false] [remote[=URL]]|--push[=false] [remote[=URL]]]
|
environment.systemPackages = with pkgs; [
|
||||||
[--make-local|-l] [--make-remote|-r]
|
python3
|
||||||
user=<username> | system=<device-type>:<hostkey>
|
git
|
||||||
EOF
|
(pkgs.writeShellScriptBin "update-ref" ''
|
||||||
exit 2
|
set -euo pipefail
|
||||||
}
|
|
||||||
|
|
||||||
# --- must be in a git repo (current dir) ---
|
RED='\033[31m'; YEL='\033[33m'; NC='\033[0m'
|
||||||
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || die "This directory is not a git project"
|
die() { printf "''${RED}error:''${NC} %s\n" "$*" >&2; exit 2; }
|
||||||
CUR_REPO_ROOT="$(git rev-parse --show-toplevel)"
|
warn() { printf "''${YEL}warning:''${NC} %s\n" "$*" >&2; }
|
||||||
CUR_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
|
||||||
|
|
||||||
# --- athenix checkout (working tree) ---
|
usage() {
|
||||||
ATHENIX_DIR="$HOME/athenix"
|
cat >&2 <<'EOF'
|
||||||
ATHENIX_BRANCH=""
|
usage:
|
||||||
|
update-ref [-R PATH|--athenix-repo=PATH] [-b BRANCH|--athenix-branch=BRANCH]
|
||||||
|
[-m "msg"|--message "msg"]
|
||||||
|
[-p[=false] [remote[=URL]]|--push[=false] [remote[=URL]]]
|
||||||
|
[--make-local|-l] [--make-remote|-r] [--ssh]
|
||||||
|
user=<username> | system=<device-type>:<hostkey>
|
||||||
|
EOF
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
# --- current repo automation ---
|
# --- must be in a git repo (current dir) ---
|
||||||
COMMIT_MSG=""
|
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || die "This directory is not a git project"
|
||||||
PUSH_SPEC=""
|
CUR_REPO_ROOT="$(git rev-parse --show-toplevel)"
|
||||||
|
CUR_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
|
||||||
|
|
||||||
# --- push / url mode ---
|
# --- athenix checkout (working tree) ---
|
||||||
PUSH_SET=0
|
ATHENIX_DIR="$HOME/athenix"
|
||||||
DO_PUSH=0
|
ATHENIX_BRANCH=""
|
||||||
MODE_FORCE="" # "", local, remote
|
|
||||||
|
|
||||||
TARGET=""
|
# --- current repo automation ---
|
||||||
|
COMMIT_MSG=""
|
||||||
|
PUSH_SPEC=""
|
||||||
|
|
||||||
is_remote_url() {
|
# --- push / url mode ---
|
||||||
# https://, http://, ssh://, or scp-style git@host:org/repo
|
PUSH_SET=0
|
||||||
printf "%s" "$1" | grep -qE '^(https?|ssh)://|^[^/@:]+@[^/:]+:'
|
DO_PUSH=0
|
||||||
}
|
MODE_FORCE="" # "", local, remote
|
||||||
|
|
||||||
derive_full_hostname() {
|
TARGET=""
|
||||||
devtype="$1"; hostkey="$2"
|
|
||||||
if printf "%s" "$hostkey" | grep -q '-' || printf "%s" "$hostkey" | grep -q "^$devtype"; then
|
|
||||||
printf "%s" "$hostkey"
|
|
||||||
elif printf "%s" "$hostkey" | grep -qE '^[0-9]+$'; then
|
|
||||||
printf "%s" "$devtype$hostkey"
|
|
||||||
else
|
|
||||||
printf "%s" "$devtype-$hostkey"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
extract_existing_fetch_url() {
|
is_remote_url() {
|
||||||
# args: mode file username key
|
# https://, http://, ssh://, or scp-style git@host:org/repo
|
||||||
python3 - "$1" "$2" "$3" "$4" <<'PY'
|
printf "%s" "$1" | grep -qE '^(https?|ssh)://|^[^/@:]+@[^/:]+:'
|
||||||
import sys, re, pathlib
|
}
|
||||||
mode, file, username, key = sys.argv[1:5]
|
|
||||||
t = pathlib.Path(file).read_text()
|
|
||||||
|
|
||||||
def url_from_block(block: str) -> str:
|
derive_full_hostname() {
|
||||||
if not block:
|
devtype="$1"; hostkey="$2"
|
||||||
return ""
|
if printf "%s" "$hostkey" | grep -q '-' || printf "%s" "$hostkey" | grep -q "^$devtype"; then
|
||||||
m = re.search(r'url\s*=\s*"([^"]+)"\s*;', block)
|
printf "%s" "$hostkey"
|
||||||
return m.group(1) if m else ""
|
elif printf "%s" "$hostkey" | grep -qE '^[0-9]+$'; then
|
||||||
|
printf "%s" "$devtype$hostkey"
|
||||||
|
else
|
||||||
|
printf "%s" "$devtype-$hostkey"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
if mode == "user":
|
extract_existing_fetch_url() {
|
||||||
m = re.search(r'(?s)\n\s*' + re.escape(username) + r'\.external\s*=\s*builtins\.fetchGit\s*\{(.*?)\n\s*\};', t)
|
# args: mode file username key
|
||||||
block = m.group(1) if m else ""
|
python3 - "$1" "$2" "$3" "$4" "$5"<<'PY'
|
||||||
print(url_from_block(block))
|
import sys, re, pathlib
|
||||||
else:
|
mode, file, username, key, use_ssh = sys.argv[1:5]
|
||||||
m = re.search(r'(?s)\n\s*"' + re.escape(key) + r'"\s*=\s*builtins\.fetchGit\s*\{(.*?)\n\s*\};', t)
|
t = pathlib.Path(file).read_text()
|
||||||
block = m.group(1) if m else ""
|
|
||||||
print(url_from_block(block))
|
|
||||||
PY
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- parse args ---
|
def url_from_block(block: str) -> str:
|
||||||
while [ "$#" -gt 0 ]; do
|
if not block:
|
||||||
case "$1" in
|
return ""
|
||||||
user=*|system=*)
|
m = re.search(r'url\s*=\s*"([^"]+)"\s*;', block)
|
||||||
[ -z "$TARGET" ] || die "Only one subcommand allowed (user=... or system=...)"
|
url = m.group(1) if m else ""
|
||||||
TARGET="$1"; shift
|
|
||||||
;;
|
|
||||||
--athenix-repo=*)
|
|
||||||
ATHENIX_DIR="''${1#*=}"; shift
|
|
||||||
;;
|
|
||||||
-R)
|
|
||||||
[ "$#" -ge 2 ] || usage
|
|
||||||
ATHENIX_DIR="$2"; shift 2
|
|
||||||
;;
|
|
||||||
--athenix-branch=*)
|
|
||||||
ATHENIX_BRANCH="''${1#*=}"; shift
|
|
||||||
;;
|
|
||||||
-b)
|
|
||||||
[ "$#" -ge 2 ] || usage
|
|
||||||
ATHENIX_BRANCH="$2"; shift 2
|
|
||||||
;;
|
|
||||||
-m|--message)
|
|
||||||
[ "$#" -ge 2 ] || usage
|
|
||||||
COMMIT_MSG="$2"; shift 2
|
|
||||||
;;
|
|
||||||
|
|
||||||
-p|--push)
|
if use_ssh = "true":
|
||||||
PUSH_SET=1
|
return url
|
||||||
DO_PUSH=1
|
|
||||||
PUSH_SPEC=""
|
|
||||||
|
|
||||||
# If there is a next token, only consume it if it is a remote spec
|
# Already https
|
||||||
# and not another flag or the subcommand.
|
if url.startswith("https://"):
|
||||||
if [ "$#" -ge 2 ]; then
|
return url
|
||||||
nxt="$2"
|
|
||||||
|
|
||||||
if printf "%s" "$nxt" | grep -qE '^(user=|system=)'; then
|
# ssh://git@host/org/repo.git
|
||||||
# next token is the subcommand; don't consume it
|
m = re.match(r"ssh://(?:.+?)@([^/]+)/(.+)", url)
|
||||||
shift
|
if m:
|
||||||
elif printf "%s" "$nxt" | grep -qE '^-'; then
|
host, path = m.groups()
|
||||||
# next token is another flag; don't consume it
|
return f"https://{host}/{path}"
|
||||||
shift
|
|
||||||
elif printf "%s" "$nxt" | grep -qE '^[A-Za-z0-9._-]+$'; then
|
# git@host:org/repo.git
|
||||||
# remote name
|
m = re.match(r"(?:.+?)@([^:]+):(.+)", url)
|
||||||
PUSH_SPEC="$nxt"
|
if m:
|
||||||
shift 2
|
host, path = m.groups()
|
||||||
elif printf "%s" "$nxt" | grep -qE '^[A-Za-z0-9._-]+=.+$'; then
|
return f"https://{host}/{path}"
|
||||||
# remote=URL
|
|
||||||
PUSH_SPEC="$nxt"
|
# If you gave me something cursed
|
||||||
shift 2
|
raise ValueError(f"Unrecognized SSH git URL format: {url}")
|
||||||
|
|
||||||
|
|
||||||
|
if mode == "user":
|
||||||
|
m = re.search(r'(?s)\n\s*' + re.escape(username) + r'\.external\s*=\s*builtins\.fetchGit\s*\{(.*?)\n\s*\};', t)
|
||||||
|
block = m.group(1) if m else ""
|
||||||
|
print(url_from_block(block))
|
||||||
|
else:
|
||||||
|
m = re.search(r'(?s)\n\s*"' + re.escape(key) + r'"\s*=\s*builtins\.fetchGit\s*\{(.*?)\n\s*\};', t)
|
||||||
|
block = m.group(1) if m else ""
|
||||||
|
print(url_from_block(block))
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- parse args ---
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
user=*|system=*)
|
||||||
|
[ -z "$TARGET" ] || die "Only one subcommand allowed (user=... or system=...)"
|
||||||
|
TARGET="$1"; shift
|
||||||
|
;;
|
||||||
|
--athenix-repo=*)
|
||||||
|
ATHENIX_DIR="''${1#*=}"; shift
|
||||||
|
;;
|
||||||
|
-R)
|
||||||
|
[ "$#" -ge 2 ] || usage
|
||||||
|
ATHENIX_DIR="$2"; shift 2
|
||||||
|
;;
|
||||||
|
--athenix-branch=*)
|
||||||
|
ATHENIX_BRANCH="''${1#*=}"; shift
|
||||||
|
;;
|
||||||
|
-b)
|
||||||
|
[ "$#" -ge 2 ] || usage
|
||||||
|
ATHENIX_BRANCH="$2"; shift 2
|
||||||
|
;;
|
||||||
|
-m|--message)
|
||||||
|
[ "$#" -ge 2 ] || usage
|
||||||
|
COMMIT_MSG="$2"; shift 2
|
||||||
|
;;
|
||||||
|
|
||||||
|
-p|--push)
|
||||||
|
PUSH_SET=1
|
||||||
|
DO_PUSH=1
|
||||||
|
PUSH_SPEC=""
|
||||||
|
|
||||||
|
# If there is a next token, only consume it if it is a remote spec
|
||||||
|
# and not another flag or the subcommand.
|
||||||
|
if [ "$#" -ge 2 ]; then
|
||||||
|
nxt="$2"
|
||||||
|
|
||||||
|
if printf "%s" "$nxt" | grep -qE '^(user=|system=)'; then
|
||||||
|
# next token is the subcommand; don't consume it
|
||||||
|
shift
|
||||||
|
elif printf "%s" "$nxt" | grep -qE '^-'; then
|
||||||
|
# next token is another flag; don't consume it
|
||||||
|
shift
|
||||||
|
elif printf "%s" "$nxt" | grep -qE '^[A-Za-z0-9._-]+$'; then
|
||||||
|
# remote name
|
||||||
|
PUSH_SPEC="$nxt"
|
||||||
|
shift 2
|
||||||
|
elif printf "%s" "$nxt" | grep -qE '^[A-Za-z0-9._-]+=.+$'; then
|
||||||
|
# remote=URL
|
||||||
|
PUSH_SPEC="$nxt"
|
||||||
|
shift 2
|
||||||
|
else
|
||||||
|
# unknown token; treat as not-a-push-spec and don't consume it
|
||||||
|
shift
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
# unknown token; treat as not-a-push-spec and don't consume it
|
|
||||||
shift
|
shift
|
||||||
fi
|
fi
|
||||||
else
|
;;
|
||||||
|
|
||||||
|
-p=*|--push=*)
|
||||||
|
PUSH_SET=1
|
||||||
|
val="''${1#*=}"
|
||||||
|
case "$val" in
|
||||||
|
false|0|no|off) DO_PUSH=0 ;;
|
||||||
|
true|1|yes|on|"") DO_PUSH=1 ;;
|
||||||
|
*) die "Invalid value for --push: $val (use true/false)" ;;
|
||||||
|
esac
|
||||||
shift
|
shift
|
||||||
|
;;
|
||||||
|
|
||||||
|
--make-local|-l) MODE_FORCE="local"; shift ;;
|
||||||
|
--make-remote|-r) MODE_FORCE="remote"; shift ;;
|
||||||
|
--ssh) USE_SSH="true"; shift ;;
|
||||||
|
-h|--help) usage ;;
|
||||||
|
*) die "Unknown argument: $1" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
[ -n "$TARGET" ] || die "Missing required subcommand: user=<username> or system=<device-type>:<hostkey>"
|
||||||
|
|
||||||
|
# --- validate athenix working tree path ---
|
||||||
|
[ -d "$ATHENIX_DIR" ] || die "$ATHENIX_DIR does not exist"
|
||||||
|
git -C "$ATHENIX_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1 || die "$ATHENIX_DIR is not a git project (athenix checkout)"
|
||||||
|
|
||||||
|
# --- -b behavior: fork/switch athenix working tree into branch ---
|
||||||
|
if [ -n "$ATHENIX_BRANCH" ]; then
|
||||||
|
ATH_CUR_BRANCH="$(git -C "$ATHENIX_DIR" rev-parse --abbrev-ref HEAD)"
|
||||||
|
if [ "$ATH_CUR_BRANCH" != "$ATHENIX_BRANCH" ]; then
|
||||||
|
if git -C "$ATHENIX_DIR" show-ref --verify --quiet "refs/heads/$ATHENIX_BRANCH"; then
|
||||||
|
warn "Branch '$ATHENIX_BRANCH' already exists in $ATHENIX_DIR."
|
||||||
|
warn "Delete and recreate it from current branch '$ATH_CUR_BRANCH' state? [y/N] "
|
||||||
|
read -r ans || true
|
||||||
|
case "''${ans:-N}" in
|
||||||
|
y|Y|yes|YES)
|
||||||
|
git -C "$ATHENIX_DIR" branch -D "$ATHENIX_BRANCH"
|
||||||
|
git -C "$ATHENIX_DIR" switch -c "$ATHENIX_BRANCH"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
git -C "$ATHENIX_DIR" switch "$ATHENIX_BRANCH"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
git -C "$ATHENIX_DIR" switch -c "$ATHENIX_BRANCH"
|
||||||
fi
|
fi
|
||||||
;;
|
|
||||||
|
|
||||||
-p=*|--push=*)
|
|
||||||
PUSH_SET=1
|
|
||||||
val="''${1#*=}"
|
|
||||||
case "$val" in
|
|
||||||
false|0|no|off) DO_PUSH=0 ;;
|
|
||||||
true|1|yes|on|"") DO_PUSH=1 ;;
|
|
||||||
*) die "Invalid value for --push: $val (use true/false)" ;;
|
|
||||||
esac
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
|
|
||||||
--make-local|-l) MODE_FORCE="local"; shift ;;
|
|
||||||
--make-remote|-r) MODE_FORCE="remote"; shift ;;
|
|
||||||
-h|--help) usage ;;
|
|
||||||
*) die "Unknown argument: $1" ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
[ -n "$TARGET" ] || die "Missing required subcommand: user=<username> or system=<device-type>:<hostkey>"
|
|
||||||
|
|
||||||
# --- validate athenix working tree path ---
|
|
||||||
[ -d "$ATHENIX_DIR" ] || die "$ATHENIX_DIR does not exist"
|
|
||||||
git -C "$ATHENIX_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1 || die "$ATHENIX_DIR is not a git project (athenix checkout)"
|
|
||||||
|
|
||||||
# --- -b behavior: fork/switch athenix working tree into branch ---
|
|
||||||
if [ -n "$ATHENIX_BRANCH" ]; then
|
|
||||||
ATH_CUR_BRANCH="$(git -C "$ATHENIX_DIR" rev-parse --abbrev-ref HEAD)"
|
|
||||||
if [ "$ATH_CUR_BRANCH" != "$ATHENIX_BRANCH" ]; then
|
|
||||||
if git -C "$ATHENIX_DIR" show-ref --verify --quiet "refs/heads/$ATHENIX_BRANCH"; then
|
|
||||||
warn "Branch '$ATHENIX_BRANCH' already exists in $ATHENIX_DIR."
|
|
||||||
warn "Delete and recreate it from current branch '$ATH_CUR_BRANCH' state? [y/N] "
|
|
||||||
read -r ans || true
|
|
||||||
case "''${ans:-N}" in
|
|
||||||
y|Y|yes|YES)
|
|
||||||
git -C "$ATHENIX_DIR" branch -D "$ATHENIX_BRANCH"
|
|
||||||
git -C "$ATHENIX_DIR" switch -c "$ATHENIX_BRANCH"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
git -C "$ATHENIX_DIR" switch "$ATHENIX_BRANCH"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
else
|
|
||||||
git -C "$ATHENIX_DIR" switch -c "$ATHENIX_BRANCH"
|
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
|
|
||||||
# --- target file + identifiers ---
|
# --- target file + identifiers ---
|
||||||
MODE=""; FILE=""; USERNAME=""; DEVTYPE=""; HOSTKEY=""
|
MODE=""; FILE=""; USERNAME=""; DEVTYPE=""; HOSTKEY=""
|
||||||
case "$TARGET" in
|
case "$TARGET" in
|
||||||
user=*)
|
user=*)
|
||||||
MODE="user"
|
MODE="user"
|
||||||
USERNAME="''${TARGET#user=}"
|
USERNAME="''${TARGET#user=}"
|
||||||
[ -n "$USERNAME" ] || die "user=<username>: username missing"
|
[ -n "$USERNAME" ] || die "user=<username>: username missing"
|
||||||
FILE="$ATHENIX_DIR/users.nix"
|
FILE="$ATHENIX_DIR/users.nix"
|
||||||
;;
|
;;
|
||||||
system=*)
|
system=*)
|
||||||
MODE="system"
|
MODE="system"
|
||||||
RHS="''${TARGET#system=}"
|
RHS="''${TARGET#system=}"
|
||||||
printf "%s" "$RHS" | grep -q ':' || die "system=... must be system=<device-type>:<hostkey>"
|
printf "%s" "$RHS" | grep -q ':' || die "system=... must be system=<device-type>:<hostkey>"
|
||||||
DEVTYPE="''${RHS%%:*}"
|
DEVTYPE="''${RHS%%:*}"
|
||||||
HOSTKEY="''${RHS#*:}"
|
HOSTKEY="''${RHS#*:}"
|
||||||
[ -n "$DEVTYPE" ] || die "system=<device-type>:<hostkey>: device-type missing"
|
[ -n "$DEVTYPE" ] || die "system=<device-type>:<hostkey>: device-type missing"
|
||||||
[ -n "$HOSTKEY" ] || die "system=<device-type>:<hostkey>: hostkey missing"
|
[ -n "$HOSTKEY" ] || die "system=<device-type>:<hostkey>: hostkey missing"
|
||||||
FILE="$ATHENIX_DIR/inventory.nix"
|
FILE="$ATHENIX_DIR/inventory.nix"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
[ -f "$FILE" ] || die "File not found: $FILE"
|
[ -f "$FILE" ] || die "File not found: $FILE"
|
||||||
|
|
||||||
# --- push default based on existing entry url in the target file ---
|
# --- push default based on existing entry url in the target file ---
|
||||||
EXISTING_URL=""
|
EXISTING_URL=""
|
||||||
ENTRY_EXISTS=0
|
ENTRY_EXISTS=0
|
||||||
if [ "$MODE" = "user" ]; then
|
if [ "$MODE" = "user" ]; then
|
||||||
EXISTING_URL="$(extract_existing_fetch_url user "$FILE" "$USERNAME" "")"
|
EXISTING_URL="$(extract_existing_fetch_url user "$FILE" "$USERNAME" "" "false")"
|
||||||
[ -n "$EXISTING_URL" ] && ENTRY_EXISTS=1 || true
|
|
||||||
else
|
|
||||||
FULL="$(derive_full_hostname "$DEVTYPE" "$HOSTKEY")"
|
|
||||||
EXISTING_URL="$(extract_existing_fetch_url system "$FILE" "" "$HOSTKEY")"
|
|
||||||
if [ -n "$EXISTING_URL" ]; then
|
|
||||||
ENTRY_EXISTS=1
|
|
||||||
elif [ "$FULL" != "$HOSTKEY" ]; then
|
|
||||||
EXISTING_URL="$(extract_existing_fetch_url system "$FILE" "" "$FULL")"
|
|
||||||
[ -n "$EXISTING_URL" ] && ENTRY_EXISTS=1 || true
|
[ -n "$EXISTING_URL" ] && ENTRY_EXISTS=1 || true
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$PUSH_SET" -eq 0 ]; then
|
|
||||||
if [ "$ENTRY_EXISTS" -eq 1 ] && is_remote_url "$EXISTING_URL"; then
|
|
||||||
DO_PUSH=1
|
|
||||||
else
|
else
|
||||||
DO_PUSH=0
|
FULL="$(derive_full_hostname "$DEVTYPE" "$HOSTKEY")"
|
||||||
[ "$MODE_FORCE" = "remote" ] && DO_PUSH=1 || true
|
EXISTING_URL="$(extract_existing_fetch_url system "$FILE" "" "$HOSTKEY")"
|
||||||
|
if [ -n "$EXISTING_URL" ]; then
|
||||||
|
ENTRY_EXISTS=1
|
||||||
|
elif [ "$FULL" != "$HOSTKEY" ]; then
|
||||||
|
EXISTING_URL="$(extract_existing_fetch_url system "$FILE" "" "$FULL")"
|
||||||
|
[ -n "$EXISTING_URL" ] && ENTRY_EXISTS=1 || true
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
if [ "$MODE_FORCE" = "local" ] && [ "$PUSH_SET" -eq 0 ]; then
|
|
||||||
DO_PUSH=0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- if current repo dirty, prompt ---
|
if [ "$PUSH_SET" -eq 0 ]; then
|
||||||
if [ -n "$(git status --porcelain)" ]; then
|
if [ "$ENTRY_EXISTS" -eq 1 ] && is_remote_url "$EXISTING_URL"; then
|
||||||
warn "This branch has untracked or uncommitted changes. Would you like to add, commit''${DO_PUSH:+, and push}? [y/N] "
|
DO_PUSH=1
|
||||||
read -r ans || true
|
|
||||||
case "''${ans:-N}" in
|
|
||||||
y|Y|yes|YES)
|
|
||||||
git add -A
|
|
||||||
if ! git diff --cached --quiet; then
|
|
||||||
if [ -n "$COMMIT_MSG" ]; then git commit -m "$COMMIT_MSG"; else git commit; fi
|
|
||||||
else
|
|
||||||
warn "No staged changes to commit."
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
*) warn "Proceeding without committing. (rev will be last committed HEAD.)" ;;
|
|
||||||
esac
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- push current repo if requested ---
|
|
||||||
PUSH_REMOTE_URL=""
|
|
||||||
if [ "$DO_PUSH" -eq 1 ]; then
|
|
||||||
if [ -n "$PUSH_SPEC" ]; then
|
|
||||||
if printf "%s" "$PUSH_SPEC" | grep -q '='; then
|
|
||||||
REM_NAME="''${PUSH_SPEC%%=*}"
|
|
||||||
REM_URL="''${PUSH_SPEC#*=}"
|
|
||||||
[ -n "$REM_NAME" ] || die "--push remote-name=URL: remote-name missing"
|
|
||||||
[ -n "$REM_URL" ] || die "--push remote-name=URL: URL missing"
|
|
||||||
if git remote get-url "$REM_NAME" >/dev/null 2>&1; then
|
|
||||||
git remote set-url "$REM_NAME" "$REM_URL"
|
|
||||||
else
|
|
||||||
git remote add "$REM_NAME" "$REM_URL"
|
|
||||||
fi
|
|
||||||
git push -u "$REM_NAME" "$CUR_BRANCH"
|
|
||||||
PUSH_REMOTE_URL="$REM_URL"
|
|
||||||
else
|
else
|
||||||
REM_NAME="$PUSH_SPEC"
|
DO_PUSH=0
|
||||||
git push -u "$REM_NAME" "$CUR_BRANCH"
|
[ "$MODE_FORCE" = "remote" ] && DO_PUSH=1 || true
|
||||||
PUSH_REMOTE_URL="$(git remote get-url "$REM_NAME")"
|
|
||||||
fi
|
fi
|
||||||
else
|
|
||||||
if ! git rev-parse --abbrev-ref --symbolic-full-name @{u} >/dev/null 2>&1; then
|
|
||||||
die "No upstream is set. Set a default upstream with \"git branch -u <remote>/<remote_branch_name>\""
|
|
||||||
fi
|
|
||||||
git push
|
|
||||||
UPSTREAM_REMOTE="$(git rev-parse --abbrev-ref --symbolic-full-name @{u} | cut -d/ -f1)"
|
|
||||||
PUSH_REMOTE_URL="$(git remote get-url "$UPSTREAM_REMOTE")"
|
|
||||||
fi
|
fi
|
||||||
fi
|
if [ "$MODE_FORCE" = "local" ] && [ "$PUSH_SET" -eq 0 ]; then
|
||||||
|
DO_PUSH=0
|
||||||
|
fi
|
||||||
|
|
||||||
CUR_REV="$(git -C "$CUR_REPO_ROOT" rev-parse HEAD)"
|
# --- if current repo dirty, prompt ---
|
||||||
|
if [ -n "$(git status --porcelain)" ]; then
|
||||||
|
warn "This branch has untracked or uncommitted changes. Would you like to add, commit''${DO_PUSH:+, and push}? [y/N] "
|
||||||
|
read -r ans || true
|
||||||
|
case "''${ans:-N}" in
|
||||||
|
y|Y|yes|YES)
|
||||||
|
git add -A
|
||||||
|
if ! git diff --cached --quiet; then
|
||||||
|
if [ -n "$COMMIT_MSG" ]; then git commit -m "$COMMIT_MSG"; else git commit; fi
|
||||||
|
else
|
||||||
|
warn "No staged changes to commit."
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*) warn "Proceeding without committing. (rev will be last committed HEAD.)" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
# --- choose URL to write into fetchGit ---
|
# --- push current repo if requested ---
|
||||||
if [ "$MODE_FORCE" = "local" ]; then
|
PUSH_REMOTE_URL=""
|
||||||
FETCH_URL="file://$CUR_REPO_ROOT"
|
|
||||||
elif [ "$MODE_FORCE" = "remote" ]; then
|
|
||||||
if [ "$DO_PUSH" -eq 1 ]; then
|
if [ "$DO_PUSH" -eq 1 ]; then
|
||||||
FETCH_URL="$PUSH_REMOTE_URL"
|
if [ -n "$PUSH_SPEC" ]; then
|
||||||
elif [ "$ENTRY_EXISTS" -eq 1 ] && [ -n "$EXISTING_URL" ] && is_remote_url "$EXISTING_URL"; then
|
if printf "%s" "$PUSH_SPEC" | grep -q '='; then
|
||||||
FETCH_URL="$EXISTING_URL"
|
REM_NAME="''${PUSH_SPEC%%=*}"
|
||||||
else
|
REM_URL="''${PUSH_SPEC#*=}"
|
||||||
CUR_ORIGIN="$(git remote get-url origin 2>/dev/null || true)"
|
[ -n "$REM_NAME" ] || die "--push remote-name=URL: remote-name missing"
|
||||||
[ -n "$CUR_ORIGIN" ] && is_remote_url "$CUR_ORIGIN" || die "--make-remote requires a remote url (set origin or use -p remote=URL)"
|
[ -n "$REM_URL" ] || die "--push remote-name=URL: URL missing"
|
||||||
FETCH_URL="$CUR_ORIGIN"
|
if git remote get-url "$REM_NAME" >/dev/null 2>&1; then
|
||||||
|
git remote set-url "$REM_NAME" "$REM_URL"
|
||||||
|
else
|
||||||
|
git remote add "$REM_NAME" "$REM_URL"
|
||||||
|
fi
|
||||||
|
git push -u "$REM_NAME" "$CUR_BRANCH"
|
||||||
|
PUSH_REMOTE_URL="$REM_URL"
|
||||||
|
else
|
||||||
|
REM_NAME="$PUSH_SPEC"
|
||||||
|
git push -u "$REM_NAME" "$CUR_BRANCH"
|
||||||
|
PUSH_REMOTE_URL="$(git remote get-url "$REM_NAME")"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if ! git rev-parse --abbrev-ref --symbolic-full-name @{u} >/dev/null 2>&1; then
|
||||||
|
die "No upstream is set. Set a default upstream with \"git branch -u <remote>/<remote_branch_name>\""
|
||||||
|
fi
|
||||||
|
git push
|
||||||
|
UPSTREAM_REMOTE="$(git rev-parse --abbrev-ref --symbolic-full-name @{u} | cut -d/ -f1)"
|
||||||
|
PUSH_REMOTE_URL="$(git remote get-url "$UPSTREAM_REMOTE")"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
else
|
|
||||||
if [ "$DO_PUSH" -eq 1 ]; then FETCH_URL="$PUSH_REMOTE_URL"; else FETCH_URL="file://$CUR_REPO_ROOT"; fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# --- rewrite users.nix or inventory.nix ---
|
CUR_REV="$(git -C "$CUR_REPO_ROOT" rev-parse HEAD)"
|
||||||
python3 - "$MODE" "$FILE" "$FETCH_URL" "$CUR_REV" "$USERNAME" "$DEVTYPE" "$HOSTKEY" <<'PY'
|
|
||||||
import sys, re, pathlib
|
|
||||||
|
|
||||||
mode = sys.argv[1]
|
# --- choose URL to write into fetchGit ---
|
||||||
path = pathlib.Path(sys.argv[2])
|
if [ "$MODE_FORCE" = "local" ]; then
|
||||||
fetch_url = sys.argv[3]
|
FETCH_URL="file://$CUR_REPO_ROOT"
|
||||||
rev = sys.argv[4]
|
elif [ "$MODE_FORCE" = "remote" ]; then
|
||||||
username = sys.argv[5]
|
if [ "$DO_PUSH" -eq 1 ]; then
|
||||||
devtype = sys.argv[6]
|
FETCH_URL="$PUSH_REMOTE_URL"
|
||||||
hostkey = sys.argv[7]
|
elif [ "$ENTRY_EXISTS" -eq 1 ] && [ -n "$EXISTING_URL" ] && is_remote_url "$EXISTING_URL"; then
|
||||||
text = path.read_text()
|
FETCH_URL="$EXISTING_URL"
|
||||||
|
else
|
||||||
|
CUR_ORIGIN="$(git remote get-url origin 2>/dev/null || true)"
|
||||||
|
[ -n "$CUR_ORIGIN" ] && is_remote_url "$CUR_ORIGIN" || die "--make-remote requires a remote url (set origin or use -p remote=URL)"
|
||||||
|
FETCH_URL="$CUR_ORIGIN"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ "$DO_PUSH" -eq 1 ]; then FETCH_URL="$PUSH_REMOTE_URL"; else FETCH_URL="file://$CUR_REPO_ROOT"; fi
|
||||||
|
fi
|
||||||
|
|
||||||
def find_matching_brace(s: str, start: int) -> int:
|
# --- rewrite users.nix or inventory.nix ---
|
||||||
depth = 0
|
python3 - "$MODE" "$FILE" "$FETCH_URL" "$CUR_REV" "$USERNAME" "$DEVTYPE" "$HOSTKEY" <<'PY'
|
||||||
i = start
|
import sys, re, pathlib
|
||||||
in_str = False
|
|
||||||
while i < len(s):
|
|
||||||
ch = s[i]
|
|
||||||
if in_str:
|
|
||||||
if ch == '\\':
|
|
||||||
i += 2
|
|
||||||
continue
|
|
||||||
if ch == '"':
|
|
||||||
in_str = False
|
|
||||||
i += 1
|
|
||||||
continue
|
|
||||||
if ch == '"':
|
|
||||||
in_str = True
|
|
||||||
i += 1
|
|
||||||
continue
|
|
||||||
if ch == '{':
|
|
||||||
depth += 1
|
|
||||||
elif ch == '}':
|
|
||||||
depth -= 1
|
|
||||||
if depth == 0:
|
|
||||||
return i
|
|
||||||
i += 1
|
|
||||||
raise ValueError("Could not find matching '}'")
|
|
||||||
|
|
||||||
def mk_fetch(entry_indent: str) -> str:
|
mode = sys.argv[1]
|
||||||
# entry_indent is indentation for the whole `"key" = <here>;` line.
|
path = pathlib.Path(sys.argv[2])
|
||||||
# The attrset contents should be indented one level deeper.
|
fetch_url = sys.argv[3]
|
||||||
inner = entry_indent + " "
|
rev = sys.argv[4]
|
||||||
return (
|
username = sys.argv[5]
|
||||||
'builtins.fetchGit {\n'
|
devtype = sys.argv[6]
|
||||||
f'{inner}url = "{fetch_url}";\n'
|
hostkey = sys.argv[7]
|
||||||
f'{inner}rev = "{rev}";\n'
|
text = path.read_text()
|
||||||
f'{inner}submodules = true;\n'
|
|
||||||
f'{entry_indent}}}'
|
|
||||||
)
|
|
||||||
|
|
||||||
def full_hostname(devtype: str, hostkey: str) -> str:
|
def find_matching_brace(s: str, start: int) -> int:
|
||||||
if hostkey.startswith(devtype) or "-" in hostkey:
|
depth = 0
|
||||||
return hostkey
|
i = start
|
||||||
if hostkey.isdigit():
|
in_str = False
|
||||||
return f"{devtype}{hostkey}"
|
while i < len(s):
|
||||||
return f"{devtype}-{hostkey}"
|
ch = s[i]
|
||||||
|
if in_str:
|
||||||
|
if ch == '\\':
|
||||||
|
i += 2
|
||||||
|
continue
|
||||||
|
if ch == '"':
|
||||||
|
in_str = False
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
if ch == '"':
|
||||||
|
in_str = True
|
||||||
|
i += 1
|
||||||
|
continue
|
||||||
|
if ch == '{':
|
||||||
|
depth += 1
|
||||||
|
elif ch == '}':
|
||||||
|
depth -= 1
|
||||||
|
if depth == 0:
|
||||||
|
return i
|
||||||
|
i += 1
|
||||||
|
raise ValueError("Could not find matching '}'")
|
||||||
|
|
||||||
def update_user(t: str) -> str:
|
def mk_fetch(entry_indent: str) -> str:
|
||||||
mblock = re.search(r"(?s)athenix\.users\s*=\s*\{(.*?)\n\s*\};", t)
|
# entry_indent is indentation for the whole `"key" = <here>;` line.
|
||||||
if not mblock:
|
# The attrset contents should be indented one level deeper.
|
||||||
raise SystemExit("error: could not locate `athenix.users = { ... };` block")
|
inner = entry_indent + " "
|
||||||
|
return (
|
||||||
# locate the full span of the users block to edit inside it
|
'builtins.fetchGit {\n'
|
||||||
# (re-find with groups for reconstruction)
|
f'{inner}url = "{fetch_url}";\n'
|
||||||
m2 = re.search(r"(?s)(athenix\.users\s*=\s*\{)(.*?)(\n\s*\};)", t)
|
f'{inner}rev = "{rev}";\n'
|
||||||
head, body, tail = m2.group(1), m2.group(2), m2.group(3)
|
f'{inner}submodules = true;\n'
|
||||||
|
f'{entry_indent}}}'
|
||||||
entry_re = re.search(
|
|
||||||
r"(?s)(\n[ \t]*" + re.escape(username) + r"\.external\s*=\s*)builtins\.fetchGit\s*\{",
|
|
||||||
body
|
|
||||||
)
|
)
|
||||||
if entry_re:
|
|
||||||
brace = body.rfind("{", 0, entry_re.end())
|
|
||||||
end = find_matching_brace(body, brace)
|
|
||||||
semi = re.match(r"\s*;", body[end+1:])
|
|
||||||
if not semi:
|
|
||||||
raise SystemExit("error: expected ';' after fetchGit attrset")
|
|
||||||
semi_end = end + 1 + semi.end()
|
|
||||||
|
|
||||||
line_start = body.rfind("\n", 0, entry_re.start()) + 1
|
def full_hostname(devtype: str, hostkey: str) -> str:
|
||||||
indent = re.match(r"[ \t]*", body[line_start:entry_re.start()]).group(0)
|
if hostkey.startswith(devtype) or "-" in hostkey:
|
||||||
|
return hostkey
|
||||||
|
if hostkey.isdigit():
|
||||||
|
return f"{devtype}{hostkey}"
|
||||||
|
return f"{devtype}-{hostkey}"
|
||||||
|
|
||||||
new_body = body[:entry_re.start()] + entry_re.group(1) + mk_fetch(indent) + ";" + body[semi_end:]
|
def update_user(t: str) -> str:
|
||||||
else:
|
mblock = re.search(r"(?s)athenix\.users\s*=\s*\{(.*?)\n\s*\};", t)
|
||||||
indent = " "
|
if not mblock:
|
||||||
new_body = body + f"\n{indent}{username}.external = {mk_fetch(indent)};\n"
|
raise SystemExit("error: could not locate `athenix.users = { ... };` block")
|
||||||
|
|
||||||
return t[:m2.start()] + head + new_body + tail + t[m2.end():]
|
# locate the full span of the users block to edit inside it
|
||||||
|
# (re-find with groups for reconstruction)
|
||||||
|
m2 = re.search(r"(?s)(athenix\.users\s*=\s*\{)(.*?)(\n\s*\};)", t)
|
||||||
|
head, body, tail = m2.group(1), m2.group(2), m2.group(3)
|
||||||
|
|
||||||
def update_system(t: str) -> str:
|
entry_re = re.search(
|
||||||
# Find devtype block robustly: start-of-file or newline.
|
r"(?s)(\n[ \t]*" + re.escape(username) + r"\.external\s*=\s*)builtins\.fetchGit\s*\{",
|
||||||
m = re.search(r"(?s)(^|\n)[ \t]*" + re.escape(devtype) + r"\s*=\s*\{", t)
|
body
|
||||||
if not m:
|
)
|
||||||
raise SystemExit(f"error: could not locate `{devtype} = {{ ... }};` block")
|
if entry_re:
|
||||||
|
brace = body.rfind("{", 0, entry_re.end())
|
||||||
dev_open = t.find("{", m.end() - 1)
|
end = find_matching_brace(body, brace)
|
||||||
dev_close = find_matching_brace(t, dev_open)
|
semi = re.match(r"\s*;", body[end+1:])
|
||||||
dev = t[dev_open:dev_close+1]
|
|
||||||
|
|
||||||
# Find devices attrset inside dev
|
|
||||||
dm = re.search(r"(?s)(^|\n)[ \t]*devices\s*=\s*\{", dev)
|
|
||||||
if not dm:
|
|
||||||
raise SystemExit(f"error: could not locate `devices = {{ ... }};` inside `{devtype}`")
|
|
||||||
|
|
||||||
devices_open = dev.find("{", dm.end() - 1)
|
|
||||||
devices_close = find_matching_brace(dev, devices_open)
|
|
||||||
devices = dev[devices_open:devices_close+1]
|
|
||||||
|
|
||||||
# indentation for entries in devices
|
|
||||||
# find indent of the 'devices' line, then add 2 spaces
|
|
||||||
|
|
||||||
candidates = [hostkey, full_hostname(devtype, hostkey)]
|
|
||||||
seen = set()
|
|
||||||
candidates = [c for c in candidates if not (c in seen or seen.add(c))]
|
|
||||||
|
|
||||||
for key in candidates:
|
|
||||||
entry = re.search(
|
|
||||||
r'(?s)\n([ ]*)"' + re.escape(key) + r'"\s*=\s*builtins\.fetchGit\s*\{',
|
|
||||||
devices
|
|
||||||
)
|
|
||||||
if entry:
|
|
||||||
entry_indent = entry.group(1)
|
|
||||||
|
|
||||||
# find the '{' we matched
|
|
||||||
brace = devices.find("{", entry.end() - 1)
|
|
||||||
end = find_matching_brace(devices, brace)
|
|
||||||
|
|
||||||
semi = re.match(r"\s*;", devices[end+1:])
|
|
||||||
if not semi:
|
if not semi:
|
||||||
raise SystemExit("error: expected ';' after fetchGit attrset in devices")
|
raise SystemExit("error: expected ';' after fetchGit attrset")
|
||||||
semi_end = end + 1 + semi.end()
|
semi_end = end + 1 + semi.end()
|
||||||
|
|
||||||
# Reconstruct the prefix: newline + indent + "key" =
|
line_start = body.rfind("\n", 0, entry_re.start()) + 1
|
||||||
prefix = f'\n{entry_indent}"{key}" = '
|
indent = re.match(r"[ \t]*", body[line_start:entry_re.start()]).group(0)
|
||||||
|
|
||||||
new_devices = (
|
new_body = body[:entry_re.start()] + entry_re.group(1) + mk_fetch(indent) + ";" + body[semi_end:]
|
||||||
devices[:entry.start()]
|
else:
|
||||||
+ prefix
|
indent = " "
|
||||||
+ mk_fetch(entry_indent)
|
new_body = body + f"\n{indent}{username}.external = {mk_fetch(indent)};\n"
|
||||||
+ ";"
|
|
||||||
+ devices[semi_end:]
|
return t[:m2.start()] + head + new_body + tail + t[m2.end():]
|
||||||
|
|
||||||
|
def update_system(t: str) -> str:
|
||||||
|
# Find devtype block robustly: start-of-file or newline.
|
||||||
|
m = re.search(r"(?s)(^|\n)[ \t]*" + re.escape(devtype) + r"\s*=\s*\{", t)
|
||||||
|
if not m:
|
||||||
|
raise SystemExit(f"error: could not locate `{devtype} = {{ ... }};` block")
|
||||||
|
|
||||||
|
dev_open = t.find("{", m.end() - 1)
|
||||||
|
dev_close = find_matching_brace(t, dev_open)
|
||||||
|
dev = t[dev_open:dev_close+1]
|
||||||
|
|
||||||
|
# Find devices attrset inside dev
|
||||||
|
dm = re.search(r"(?s)(^|\n)[ \t]*devices\s*=\s*\{", dev)
|
||||||
|
if not dm:
|
||||||
|
raise SystemExit(f"error: could not locate `devices = {{ ... }};` inside `{devtype}`")
|
||||||
|
|
||||||
|
devices_open = dev.find("{", dm.end() - 1)
|
||||||
|
devices_close = find_matching_brace(dev, devices_open)
|
||||||
|
devices = dev[devices_open:devices_close+1]
|
||||||
|
|
||||||
|
# indentation for entries in devices
|
||||||
|
# find indent of the 'devices' line, then add 2 spaces
|
||||||
|
|
||||||
|
candidates = [hostkey, full_hostname(devtype, hostkey)]
|
||||||
|
seen = set()
|
||||||
|
candidates = [c for c in candidates if not (c in seen or seen.add(c))]
|
||||||
|
|
||||||
|
for key in candidates:
|
||||||
|
entry = re.search(
|
||||||
|
r'(?s)\n([ ]*)"' + re.escape(key) + r'"\s*=\s*builtins\.fetchGit\s*\{',
|
||||||
|
devices
|
||||||
)
|
)
|
||||||
new_dev = dev[:devices_open] + new_devices + dev[devices_close+1:]
|
if entry:
|
||||||
|
entry_indent = entry.group(1)
|
||||||
|
|
||||||
return t[:dev_open] + new_dev + t[dev_close+1:]
|
# find the '{' we matched
|
||||||
|
brace = devices.find("{", entry.end() - 1)
|
||||||
|
end = find_matching_brace(devices, brace)
|
||||||
|
|
||||||
# Not found: append into devices (exact hostkey)
|
semi = re.match(r"\s*;", devices[end+1:])
|
||||||
# Indent for new entries: take indent of the closing '}' of devices, add 2 spaces.
|
if not semi:
|
||||||
close_line_start = devices.rfind("\n", 0, len(devices)-1) + 1
|
raise SystemExit("error: expected ';' after fetchGit attrset in devices")
|
||||||
close_indent = re.match(r"[ ]*", devices[close_line_start:]).group(0)
|
semi_end = end + 1 + semi.end()
|
||||||
entry_indent = close_indent + " "
|
|
||||||
|
|
||||||
insertion = f'\n{entry_indent}"{hostkey}" = {mk_fetch(entry_indent)};\n'
|
# Reconstruct the prefix: newline + indent + "key" =
|
||||||
new_devices = devices[:-1].rstrip() + insertion + close_indent + "}"
|
prefix = f'\n{entry_indent}"{key}" = '
|
||||||
new_dev = dev[:devices_open] + new_devices + dev[devices_close+1:]
|
|
||||||
return t[:dev_open] + new_dev + t[dev_close+1:]
|
|
||||||
|
|
||||||
if mode == "user":
|
new_devices = (
|
||||||
out = update_user(text)
|
devices[:entry.start()]
|
||||||
elif mode == "system":
|
+ prefix
|
||||||
out = update_system(text)
|
+ mk_fetch(entry_indent)
|
||||||
else:
|
+ ";"
|
||||||
raise SystemExit("error: unknown mode")
|
+ devices[semi_end:]
|
||||||
|
)
|
||||||
|
new_dev = dev[:devices_open] + new_devices + dev[devices_close+1:]
|
||||||
|
|
||||||
path.write_text(out)
|
return t[:dev_open] + new_dev + t[dev_close+1:]
|
||||||
PY
|
|
||||||
|
|
||||||
cd $ATHENIX_DIR
|
# Not found: append into devices (exact hostkey)
|
||||||
nix fmt **/*.nix
|
# Indent for new entries: take indent of the closing '}' of devices, add 2 spaces.
|
||||||
cd $CUR_REPO_ROOT
|
close_line_start = devices.rfind("\n", 0, len(devices)-1) + 1
|
||||||
|
close_indent = re.match(r"[ ]*", devices[close_line_start:]).group(0)
|
||||||
|
entry_indent = close_indent + " "
|
||||||
|
|
||||||
printf "updated %s\n" "$FILE" >&2
|
insertion = f'\n{entry_indent}"{hostkey}" = {mk_fetch(entry_indent)};\n'
|
||||||
printf " url = %s\n" "$FETCH_URL" >&2
|
new_devices = devices[:-1].rstrip() + insertion + close_indent + "}"
|
||||||
printf " rev = %s\n" "$CUR_REV" >&2
|
new_dev = dev[:devices_open] + new_devices + dev[devices_close+1:]
|
||||||
'')
|
return t[:dev_open] + new_dev + t[dev_close+1:]
|
||||||
];
|
|
||||||
|
if mode == "user":
|
||||||
|
out = update_user(text)
|
||||||
|
elif mode == "system":
|
||||||
|
out = update_system(text)
|
||||||
|
else:
|
||||||
|
raise SystemExit("error: unknown mode")
|
||||||
|
|
||||||
|
path.write_text(out)
|
||||||
|
PY
|
||||||
|
|
||||||
|
cd $ATHENIX_DIR
|
||||||
|
nix fmt **/*.nix
|
||||||
|
cd $CUR_REPO_ROOT
|
||||||
|
|
||||||
|
printf "updated %s\n" "$FILE" >&2
|
||||||
|
printf " url = %s\n" "$FETCH_URL" >&2
|
||||||
|
printf " rev = %s\n" "$CUR_REV" >&2
|
||||||
|
'')
|
||||||
|
];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,27 +9,47 @@ with lib;
|
|||||||
|
|
||||||
{
|
{
|
||||||
options.athenix.sw.remoteBuild = lib.mkOption {
|
options.athenix.sw.remoteBuild = lib.mkOption {
|
||||||
type = types.submodule {
|
type = lib.types.submodule {
|
||||||
options = {
|
options = {
|
||||||
hosts = mkOption {
|
hosts = mkOption {
|
||||||
type = types.listOf types.str;
|
type = lib.types.listOf lib.types.str;
|
||||||
default = [ "engr-ugaif@192.168.11.133 x86_64-linux" ];
|
default = [ "engr-ugaif@192.168.11.133 x86_64-linux" ];
|
||||||
description = "List of remote build hosts for system rebuilding.";
|
description = ''
|
||||||
|
List of remote build hosts for system rebuilding.
|
||||||
|
|
||||||
|
Format: "user@hostname architecture"
|
||||||
|
Each host must have SSH access and nix-daemon available.
|
||||||
|
|
||||||
|
Useful for offloading builds from low-power devices (tablets, laptops)
|
||||||
|
to more powerful build servers.
|
||||||
|
'';
|
||||||
|
example = lib.literalExpression ''
|
||||||
|
[
|
||||||
|
"builder@nix-builder x86_64-linux"
|
||||||
|
"user@192.168.1.100 aarch64-linux"
|
||||||
|
]'';
|
||||||
};
|
};
|
||||||
|
|
||||||
enable = mkOption {
|
enable = mkOption {
|
||||||
type = types.bool;
|
type = lib.types.bool;
|
||||||
default = false;
|
default = false;
|
||||||
description = "Whether to enable remote build for 'update-system' command.";
|
description = ''
|
||||||
|
Whether to enable remote builds for the 'update-system' command.
|
||||||
|
|
||||||
|
When enabled, 'update-system' will use the configured remote hosts
|
||||||
|
to build the new system configuration instead of building locally.
|
||||||
|
|
||||||
|
Automatically enabled for tablet-kiosk systems.
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
default = { };
|
default = { };
|
||||||
description = "Remote build configuration";
|
description = "Remote build configuration for system updates.";
|
||||||
};
|
};
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
athenix.sw.remoteBuild.enable = lib.mkDefault (config.athenix.sw.type == "tablet-kiosk");
|
athenix.sw.remoteBuild.enable = lib.mkDefault (config.athenix.sw.tablet-kiosk.enable);
|
||||||
|
|
||||||
environment.systemPackages = [
|
environment.systemPackages = [
|
||||||
(pkgs.writeShellScriptBin "update-system" ''
|
(pkgs.writeShellScriptBin "update-system" ''
|
||||||
@@ -201,8 +221,10 @@ with lib;
|
|||||||
description = "System daemon to one-shot run the Nix updater from fleet flake as root";
|
description = "System daemon to one-shot run the Nix updater from fleet flake as root";
|
||||||
path = with pkgs; [
|
path = with pkgs; [
|
||||||
git
|
git
|
||||||
|
openssh
|
||||||
nixos-rebuild
|
nixos-rebuild
|
||||||
nix
|
nix
|
||||||
|
coreutils
|
||||||
];
|
];
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
Type = "oneshot";
|
Type = "oneshot";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{ inputs, ... }:
|
{ ... }:
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# User Configuration
|
# User Configuration
|
||||||
@@ -15,7 +15,6 @@
|
|||||||
# nixos-systems configuration (nixpkgs, home-manager, etc.).
|
# nixos-systems configuration (nixpkgs, home-manager, etc.).
|
||||||
|
|
||||||
{
|
{
|
||||||
config,
|
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
pkgs,
|
||||||
osConfig ? null, # Only available in home-manager context
|
osConfig ? null, # Only available in home-manager context
|
||||||
@@ -60,7 +59,7 @@
|
|||||||
fd
|
fd
|
||||||
bat
|
bat
|
||||||
]
|
]
|
||||||
++ lib.optional (osConfig.athenix.sw.type or null == "desktop") firefox;
|
++ lib.optional (osConfig.athenix.sw.desktop.enable or false) firefox;
|
||||||
# Conditionally add packages based on system type
|
# Conditionally add packages based on system type
|
||||||
|
|
||||||
# ========== Programs ==========
|
# ========== Programs ==========
|
||||||
|
|||||||
14
users.nix
14
users.nix
@@ -1,4 +1,4 @@
|
|||||||
{ pkgs, ... }:
|
{ ... }:
|
||||||
{
|
{
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# User Definitions
|
# User Definitions
|
||||||
@@ -13,8 +13,9 @@
|
|||||||
#
|
#
|
||||||
# External User Configuration:
|
# External User Configuration:
|
||||||
# Users can specify external configuration modules via the 'external' attribute:
|
# Users can specify external configuration modules via the 'external' attribute:
|
||||||
# external = builtins.fetchGit { url = "..."; rev = "..."; };
|
# external = { url = "..."; rev = "..."; submodules? = false; };
|
||||||
# external = /path/to/local/config;
|
# external = /path/to/local/config;
|
||||||
|
# external = builtins.fetchGit { ... }; # legacy, still supported
|
||||||
#
|
#
|
||||||
# External repositories should contain:
|
# External repositories should contain:
|
||||||
# - user.nix (required): Defines athenix.users.<name> options AND home-manager config
|
# - user.nix (required): Defines athenix.users.<name> options AND home-manager config
|
||||||
@@ -26,7 +27,7 @@
|
|||||||
#
|
#
|
||||||
# User options can be set in users.nix OR in the external module's user.nix.
|
# User options can be set in users.nix OR in the external module's user.nix.
|
||||||
# External module options take precedence over users.nix defaults.
|
# External module options take precedence over users.nix defaults.
|
||||||
athenix.users = {
|
config.athenix.users = {
|
||||||
root = {
|
root = {
|
||||||
isNormalUser = false;
|
isNormalUser = false;
|
||||||
hashedPassword = "!";
|
hashedPassword = "!";
|
||||||
@@ -47,9 +48,10 @@
|
|||||||
enable = true; # Default user, enabled everywhere
|
enable = true; # Default user, enabled everywhere
|
||||||
};
|
};
|
||||||
hdh20267 = {
|
hdh20267 = {
|
||||||
external = builtins.fetchGit {
|
external = {
|
||||||
url = "https://git.factory.uga.edu/hdh20267/hdh20267-nix";
|
url = "https://git.factory.uga.edu/hdh20267/hdh20267-nix";
|
||||||
rev = "c538e0c0510045b58264627bb897fc499dc7c490";
|
rev = "dbdf65c7bd59e646719f724a3acd2330e0c922ec";
|
||||||
|
# submodules = false; # optional, defaults to false
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
sv22900 = {
|
sv22900 = {
|
||||||
@@ -58,7 +60,7 @@
|
|||||||
"networkmanager"
|
"networkmanager"
|
||||||
"wheel"
|
"wheel"
|
||||||
];
|
];
|
||||||
shell = pkgs.zsh;
|
shell = "zsh";
|
||||||
# enable = false by default, set to true per-system
|
# enable = false by default, set to true per-system
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user