84 Commits

Author SHA1 Message Date
UGA Innovation Factory
6f7e95b9f9 fix: Fail the CI if formatting fails
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m40s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 15s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 8s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 9s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 20s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 13s
CI / Build and Publish Documentation (push) Successful in 11s
2026-01-30 18:26:26 -05:00
UGA Innovation Factory
7c07727150 feat: USDA-dash now uses encrypted .env files
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m42s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 14s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 8s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 9s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 20s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 13s
CI / Build and Publish Documentation (push) Successful in 10s
2026-01-30 23:19:38 +00:00
UGA Innovation Factory
7e6e8d5e0f chore: Update flake lock
Some checks failed
CI / Format Check (push) Waiting to run
CI / Flake Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-builder) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been cancelled
CI / Build and Publish Documentation (push) Has been cancelled
2026-01-30 23:07:40 +00:00
UGA Innovation Factory
c6e0a0aedf chore: Update flake lock
Some checks failed
CI / Flake Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-builder) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been cancelled
CI / Build and Publish Documentation (push) Has been cancelled
CI / Format Check (push) Has been cancelled
2026-01-30 22:55:30 +00:00
UGA Innovation Factory
4b4e6a2873 chore: Update flake lock
Some checks failed
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been cancelled
CI / Build and Publish Documentation (push) Has been cancelled
CI / Flake Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-builder) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been cancelled
CI / Format Check (push) Has been cancelled
2026-01-30 22:53:59 +00:00
UGA Innovation Factory
40a9f9f5a6 chore: Update flake lock
Some checks failed
CI / Flake Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-builder) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been cancelled
CI / Build and Publish Documentation (push) Has been cancelled
CI / Format Check (push) Has been cancelled
2026-01-30 22:52:27 +00:00
UGA Innovation Factory
14a61da9ed chore: Update flake lock
Some checks failed
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been cancelled
CI / Build and Publish Documentation (push) Has been cancelled
CI / Flake Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-builder) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been cancelled
CI / Format Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been cancelled
2026-01-30 22:38:14 +00:00
UGA Innovation Factory
a3c8e0640a chore: Update flake lock
Some checks failed
CI / Flake Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-builder) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been cancelled
CI / Build and Publish Documentation (push) Has been cancelled
CI / Format Check (push) Has been cancelled
2026-01-30 22:26:29 +00:00
UGA Innovation Factory
01fc5518c1 chore: Update flake lock
Some checks failed
CI / Flake Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-builder) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been cancelled
CI / Build and Publish Documentation (push) Has been cancelled
CI / Format Check (push) Has been cancelled
2026-01-30 22:24:31 +00:00
UGA Innovation Factory
a2d4f71a77 chore: Update flake lock
Some checks failed
CI / Flake Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-builder) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been cancelled
CI / Build and Publish Documentation (push) Has been cancelled
CI / Format Check (push) Has been cancelled
2026-01-30 22:21:13 +00:00
UGA Innovation Factory
e0cafb7f66 chore: Update usda-docker hash
Some checks failed
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been cancelled
CI / Flake Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-builder) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been cancelled
CI / Build and Publish Documentation (push) Has been cancelled
CI / Format Check (push) Has been cancelled
2026-01-30 22:18:59 +00:00
UGA Innovation Factory
ffbd7a221d Set default timezone for LXC containers to fix Docker /etc/localtime mounts
Some checks failed
CI / Evaluate Key Configurations (nix-builder) (push) Has been cancelled
CI / Flake Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been cancelled
CI / Build and Publish Documentation (push) Has been cancelled
CI / Format Check (push) Has been cancelled
2026-01-30 22:00:15 +00:00
UGA Innovation Factory
d7922247d2 Fix activation script to always regenerate age keys
Some checks failed
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been cancelled
CI / Flake Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-builder) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been cancelled
CI / Build and Publish Documentation (push) Has been cancelled
CI / Format Check (push) Has been cancelled
2026-01-30 21:51:19 +00:00
UGA Innovation Factory
31c829f502 Add SSH-to-age conversion activation script for reliable secret decryption
Some checks failed
CI / Flake Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-builder) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been cancelled
CI / Build and Publish Documentation (push) Has been cancelled
CI / Format Check (push) Has been cancelled
2026-01-30 21:48:57 +00:00
UGA Innovation Factory
e3bae02f58 Re-encrypt usda-vision-env with correct host key
Some checks failed
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been cancelled
CI / Flake Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-builder) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been cancelled
CI / Build and Publish Documentation (push) Has been cancelled
CI / Format Check (push) Has been cancelled
2026-01-30 21:46:02 +00:00
UGA Innovation Factory
aa6d9d5691 Revert experimental changes, use ragenix defaults
Some checks failed
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been cancelled
CI / Build and Publish Documentation (push) Has been cancelled
CI / Flake Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-builder) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been cancelled
CI / Format Check (push) Has been cancelled
2026-01-30 21:45:55 +00:00
UGA Innovation Factory
87045a518f Use rage instead of age for SSH key decryption support
Some checks failed
CI / Flake Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-builder) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been cancelled
CI / Build and Publish Documentation (push) Has been cancelled
CI / Format Check (push) Has been cancelled
2026-01-30 21:40:04 +00:00
UGA Innovation Factory
dffe817e47 Update usda-dash host key and re-encrypt secret
Some checks failed
CI / Flake Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-builder) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been cancelled
CI / Build and Publish Documentation (push) Has been cancelled
CI / Format Check (push) Has been cancelled
2026-01-30 21:29:39 +00:00
UGA Innovation Factory
23da829033 feat: Use age for env secret managment
Some checks failed
CI / Flake Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-builder) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been cancelled
CI / Build and Publish Documentation (push) Has been cancelled
CI / Format Check (push) Has been cancelled
2026-01-30 20:54:31 +00:00
UGA Innovation Factory
dd19d1488a fix: Convert ssh keys to age keys
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m42s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 14s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 7s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 8s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 20s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 13s
CI / Build and Publish Documentation (push) Successful in 11s
2026-01-30 19:41:34 +00:00
UGA Innovation Factory
862ae2c864 chore: Run nix fmt
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m42s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 13s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 7s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 8s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 22s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 14s
CI / Build and Publish Documentation (push) Successful in 10s
2026-01-30 19:19:38 +00:00
UGA Innovation Factory
3efba93424 feat: Ragenix secret management per host
Some checks failed
CI / Flake Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-builder) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been cancelled
CI / Build and Publish Documentation (push) Has been cancelled
CI / Format Check (push) Has been cancelled
2026-01-30 19:19:20 +00:00
UGA Innovation Factory
2e4602cbf3 refactor: Move macCaseBuilder into athenix.lib
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m44s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 14s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 8s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 8s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 19s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 13s
CI / Build and Publish Documentation (push) Successful in 10s
2026-01-27 22:13:32 +00:00
UGA Innovation Factory
ab3710b5f6 chore: Run nix fmt
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m43s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 14s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 8s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 8s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 20s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 13s
CI / Build and Publish Documentation (push) Successful in 11s
2026-01-27 21:44:23 +00:00
UGA Innovation Factory
863cd1ea95 fix: Remove unused or broken config outputs for nix eval of flake components
Some checks failed
CI / Flake Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-builder) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been cancelled
CI / Build and Publish Documentation (push) Has been cancelled
CI / Format Check (push) Has been cancelled
2026-01-27 21:43:58 +00:00
UGA Innovation Factory
d8cee7e79b refactor: Make hw definitions modules with mkIf guards
Some checks failed
CI / Flake Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-builder) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been cancelled
CI / Build and Publish Documentation (push) Has been cancelled
CI / Format Check (push) Has been cancelled
2026-01-27 16:30:54 -05:00
UGA Innovation Factory
063336f736 refactor: Fleet and sw behind mkIf guards 2026-01-27 16:11:36 -05:00
UGA Innovation Factory
85653e632f fix: Enable sw by default when imported
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m47s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 11s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 7s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 7s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 18s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 11s
CI / Build and Publish Documentation (push) Successful in 8s
2026-01-27 15:36:31 -05:00
Hunter David Halloran
1533382ff2 Merge pull request 'fix: Lazily fetch external modules only if needed' (#32) from external-refactor into main
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m45s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 11s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 7s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 8s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 18s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 12s
CI / Build and Publish Documentation (push) Successful in 8s
Reviewed-on: http://git.factory.uga.edu/UGA-Innovation-Factory/athenix/pulls/32
2026-01-27 20:06:09 +00:00
UGA Innovation Factory
540f5feb78 fix: Lazily fetch external modules only if needed 2026-01-27 15:05:52 -05:00
UGA Innovation Factory
1a7bf29448 docs: Update inline code docs for LSP help
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m39s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 8s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 7s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 7s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 14s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 8s
CI / Build and Publish Documentation (push) Successful in 5s
2026-01-27 14:48:07 -05:00
UGA Innovation Factory
13fdc3a7a1 feat: Update auto-docs
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m39s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 9s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 7s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 7s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 14s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 9s
CI / Build and Publish Documentation (push) Successful in 6s
2026-01-27 14:25:37 -05:00
Hunter David Halloran
01fdfbf913 Merge pull request 'fix: Change CI to ssh git' (#31) from sw-refactor into main
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m40s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 9s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 7s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 8s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 15s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 8s
CI / Build and Publish Documentation (push) Successful in 6s
Reviewed-on: http://git.factory.uga.edu/UGA-Innovation-Factory/athenix/pulls/31
2026-01-27 19:07:29 +00:00
UGA Innovation Factory
9d0683165f fix: Change CI to ssh git 2026-01-27 14:07:03 -05:00
Hunter David Halloran
b1bc354160 Merge pull request 'refactor: Move sw into properly nested modules with unconditional import' (#30) from sw-refactor into main
Some checks failed
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m39s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 9s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 7s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 7s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 14s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 8s
CI / Build and Publish Documentation (push) Has been cancelled
Reviewed-on: http://git.factory.uga.edu/UGA-Innovation-Factory/athenix/pulls/30
2026-01-27 19:00:23 +00:00
UGA Innovation Factory
f669845bf7 refactor: Move sw into properly nested modules with unconditional import 2026-01-27 13:59:57 -05:00
UGA Innovation Factory
bd50f894ae chore: Remove unused variables and imports
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m34s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 8s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 6s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 6s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 13s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 7s
2026-01-13 21:07:39 -05:00
UGA Innovation Factory
92e3940644 chore: Run nix fmt
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m35s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 8s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 7s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 6s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 14s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 8s
2026-01-13 20:56:55 -05:00
UGA Innovation Factory
1c767ed4c8 fix: Ensure all users are read from and that the config is shared between module levels
Some checks failed
CI / Flake Check (push) Has been cancelled
CI / Evaluate Key Configurations (nix-builder) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been cancelled
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been cancelled
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been cancelled
CI / Format Check (push) Has been cancelled
2026-01-13 20:56:30 -05:00
UGA Innovation Factory
ffa434e720 chore: Run nix fmt
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m26s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 6s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 5s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 7s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 13s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 6s
2026-01-13 18:33:43 -05:00
UGA Innovation Factory
5f5698f608 chore: Update hdh20267 user to use new shell selection 2026-01-13 18:33:43 -05:00
UGA Innovation Factory
f606ea731c feat: Refactor to use flake-parts and import inventory and users thru the flake parts 2026-01-13 18:33:43 -05:00
UGA Innovation Factory
b1d4fe8d68 fix: Refactor flake structure to properly use flake-parts
- Remove incorrect parts/fleet-data.nix import from flake.nix
- Create flake-parts wrappers for fleet-option.nix and users.nix
- Import inventory.nix through fleet-option wrapper
- Fix module argument passing (remove pkgs from mkFleet call)
- Move NixOS-specific modules out of flake-parts imports

This addresses the 'path does not exist' error but introduces infinite recursion that needs to be resolved.
2026-01-13 18:33:43 -05:00
UGA Innovation Factory
cbddecfeb4 fix: Remove incorrect parts/fleet-data.nix import from flake.nix
fleet-data.nix is a NixOS module imported by fleet/common.nix, not a flake-parts module. It should not be imported at the flake level.
2026-01-13 18:33:37 -05:00
UGA Innovation Factory
005207d3e4 flake.lock: Update
Flake lock file updates:

• Updated input 'home-manager':
    'github:nix-community/home-manager/6bd04da47cfb48dfd15eabf08364b78ad894f5b2?narHash=sha256-KpoCBPvwHz3gAQtIUkohE2InRBFK3r0/FM6z5SPWfvM%3D' (2026-01-05)
  → 'github:nix-community/home-manager/82fb7dedaad83e5e279127a38ef410bcfac6d77c?narHash=sha256-MOU5YdVu4DVwuT5ztXgQpPuRRBjSjUGIdUzOQr9iQOY%3D' (2026-01-08)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/30a3c519afcf3f99e2c6df3b359aec5692054d92?narHash=sha256-8IQQUorUGiSmFaPnLSo2%2BT%2BrjHtiNWc%2BOAzeHck7N48%3D' (2026-01-03)
  → 'github:NixOS/nixpkgs/1327e798cb055f96f92685df444e9a2c326ab5ed?narHash=sha256-F4IIxa5xDHjtrmMcayM8lHctUq1oGltfBQu2%2BoqDWP4%3D' (2026-01-12)
• Updated input 'nixpkgs-old-kernel':
    'github:NixOS/nixpkgs/40ee5e1944bebdd128f9fbada44faefddfde29bd?narHash=sha256-0MnuWoN%2Bn1UYaGBIpqpPs9I9ZHW4kynits4mrnh1Pk4%3D' (2025-12-29)
  → 'github:NixOS/nixpkgs/ac62194c3917d5f474c1a844b6fd6da2db95077d?narHash=sha256-16KkgfdYqjaeRGBaYsNrhPRRENs0qzkQVUooNHtoy2w%3D' (2026-01-02)
2026-01-13 16:21:42 -05:00
UGA Innovation Factory
d34325de53 fix: Remove incorrect ./parts/fleet-data.nix import from flake.nix and use correct flake-parts structure 2026-01-13 16:21:20 -05:00
UGA Innovation Factory
67e7a57402 fix: Set proper keybindings for zsh
All checks were successful
CI / Format Check (push) Successful in 3s
CI / Flake Check (push) Successful in 1m42s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 10s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 11s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 7s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 16s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 11s
2026-01-13 12:16:46 -05:00
UGA Innovation Factory
dcc3dde702 fix: Repair Surface tablet kernel specifics that got lost in the refactor
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m35s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 11s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 12s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 7s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 16s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 11s
2026-01-07 21:20:10 -05:00
UGA Innovation Factory
14fb79231f docs: Fix the file reference in comments in fleet-option.nix
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m34s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 11s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 12s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 8s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 17s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 11s
2026-01-07 18:37:35 -05:00
Hunter David Halloran
ea4c2df776 Merge pull request 'Refactored the repository structure by renaming variants/ to hw and glue/ to fleet, fixing an infinite recursion bug by separating fleet options evaluation, and adding a lib.mkFleet function to enable external flakes to reuse Athenix's fleet generation' (#29) from inventory-as-module into main
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m34s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 11s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 13s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 8s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 17s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 10s
Reviewed-on: http://git.factory.uga.edu/UGA-Innovation-Factory/athenix/pulls/29
2026-01-07 23:20:24 +00:00
UGA Innovation Factory
d89caa8a6b chore: run nix fmt 2026-01-07 18:15:37 -05:00
UGA Innovation Factory
4cb8d8ef13 chore: remove refactored-out files 2026-01-07 18:15:19 -05:00
UGA Innovation Factory
97646f3229 docs: update documentation for refactored structure
- Update NAMESPACE.md: filesystem.device and swapSize defaults now null
- Update README.md: add 'Using Athenix as a Library' section with lib.mkFleet
- Update copilot-instructions.md: reflect new directory structure and defaults
- Update EXTERNAL_MODULES.md: correct paths from variants/ and glue/ to hw/ and fleet/
- Update PROXMOX_LXC.md: update hardware type path reference
2026-01-07 18:12:39 -05:00
UGA Innovation Factory
d3788be951 chore: update inventory.nix formatting
- Remove trailing whitespace and ensure consistent formatting
2026-01-07 18:12:28 -05:00
UGA Innovation Factory
cda1975631 fix: use lib.mkForce for systemd.network.enable in stateless-kiosk
- Prevent conflicts when disabling systemd-networkd in stateless kiosk config
- Ensures the disable takes precedence over other module settings
2026-01-07 18:12:19 -05:00
UGA Innovation Factory
7145f5b3d8 fix: update artifacts.nix to use fleet.modules
- Use fleet.modules instead of trying to access mkFleet output directly
- Fix netboot artifact generation to correctly access module list
- Ensure artifacts can access both nixosConfigurations and modules from fleet
2026-01-07 18:12:07 -05:00
UGA Innovation Factory
1ce7334a73 feat: add lib.mkFleet for external flake consumption
- Create lib/mkFleet.nix to expose fleet generator as library function
- Allow external flakes to use Athenix's fleet logic with custom inventory
- Export lib output in flake.nix with proper input passing
- Enable usage: nixosConfigurations = athenix.lib.mkFleet { fleet = ...; hwTypes = ...; }
2026-01-07 18:11:59 -05:00
UGA Innovation Factory
775080949d refactor: move GC and buildMethods options to sw module
- Move athenix.system.gc options from fleet/common.nix to sw/gc.nix
- Move athenix.host.buildMethods option to sw/gc.nix
- Move home-manager, agenix, disko imports to sw/default.nix
- Better separation: fleet/ handles generation, sw/ handles software config
2026-01-07 18:11:46 -05:00
UGA Innovation Factory
d15b4d8067 feat: add fleet configuration options module
- Create fleet/fleet-option.nix to define athenix.fleet and athenix.hwTypes options
- Use lib.evalModules to evaluate inventory separately from host configs
- Enable external overrides of fleet and hardware types via lib.mkForce
- Fix infinite recursion issue by avoiding config references in imports
2026-01-07 18:11:29 -05:00
UGA Innovation Factory
5875725ca2 refactor: reorganize directory structure (variants -> hw, glue -> fleet)
- Rename variants/ to hw/ for clearer hardware module naming
- Rename glue/ to fleet/ for more intuitive fleet management
- Move boot/fs configuration from glue/boot.nix to separate fleet/boot.nix and fleet/fs.nix
- Improve separation of concerns between boot, filesystem, and common config
2026-01-07 18:11:19 -05:00
Hunter David Halloran
9e066d395b Merge pull request 'updater-ssh' (#28) from updater-ssh into main
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m38s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 12s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 13s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 8s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 18s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 12s
Reviewed-on: http://git.factory.uga.edu/UGA-Innovation-Factory/athenix/pulls/28
2026-01-07 00:16:38 +00:00
UGA Innovation Factory
825e90c581 chore: run nix fmt 2026-01-06 19:15:38 -05:00
UGA Innovation Factory
6a9807a688 fix: system-update should respect ssh requirements 2026-01-06 19:14:58 -05:00
Hunter David Halloran
c4ff0d7fd3 Merge pull request 'feat: add '--ssh' flag to update-ref tool to choose ssh url or default to https url' (#27) from update-ref-https into main
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m39s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 12s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 14s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 8s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 18s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 12s
Reviewed-on: http://git.factory.uga.edu/UGA-Innovation-Factory/athenix/pulls/27
2026-01-07 00:03:46 +00:00
Hunter Halloran
cca3e39af0 feat: add '--ssh' flag to update-ref tool to choose ssh url or default to https url 2026-01-06 19:02:27 -05:00
Hunter David Halloran
917275409f Merge pull request 'Merge branch 'options-refactor' to ensure options are defined where they are used, standardize the module input of 'variants' (formerly 'hosts'), and add a 'glue' directory for piecing together the final flake outputs' (#26) from options-refactor into main
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m38s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 12s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 13s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 8s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 18s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 12s
Reviewed-on: http://git.factory.uga.edu/UGA-Innovation-Factory/athenix/pulls/26
2026-01-06 23:39:55 +00:00
UGA Innovation Factory
b3e274484f chore: remove unused assets directory 2026-01-06 18:36:01 -05:00
UGA Innovation Factory
55c49d84b5 chore: run nix fmt 2026-01-06 18:34:21 -05:00
UGA Innovation Factory
6972a999ca docs: update all references from hosts/ to glue/ and variants/
- Update README.md structure section
- Update DEVELOPMENT.md, EXTERNAL_MODULES.md, INVENTORY.md
- Update GitHub Copilot instructions
- Update PROXMOX_LXC.md references
- Clarify new directory organization and purpose
2026-01-06 18:32:18 -05:00
UGA Innovation Factory
faf7bb635e feat: add lazy evaluation for external modules in inventory
- External modules now use 'external' field for lazy evaluation
- Only fetched when building specific host (not during flake check)
- Improves rebuild performance for unrelated hosts
- Update examples and documentation in inventory.nix header
2026-01-06 18:32:06 -05:00
UGA Innovation Factory
c3bbf6f8be refactor: update imports to use glue/ and variants/
- flake.nix: import glue/fleet.nix instead of hosts/
- installer/artifacts.nix: use 'fleet' parameter instead of 'hosts'
- installer/modules.nix: auto-import from variants/ directory
2026-01-06 18:31:58 -05:00
UGA Innovation Factory
77cea838a1 chore: remove old hosts/ directory
- Replaced by glue/ and variants/ structure
- Fleet generation moved to glue/fleet.nix
- Hardware types moved to variants/
2026-01-06 18:31:50 -05:00
UGA Innovation Factory
cb37fad70e refactor: create glue/ and variants/ directories
- Add glue/ for fleet generation logic and common configuration
- Add variants/ for hardware type modules
- Improves separation of concerns and module organization
2026-01-06 18:31:40 -05:00
UGA Innovation Factory
03f532e867 refactor: define options where they are used 2026-01-06 14:43:45 -05:00
Hunter David Halloran
9a2f167efe Merge pull request 'feat: add age packages for fido2 and other secret management' (#25) from age-packages into main
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m40s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 12s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 14s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 9s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 19s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 13s
Reviewed-on: http://git.factory.uga.edu/UGA-Innovation-Factory/athenix/pulls/25
2026-01-06 18:41:34 +00:00
UGA Innovation Factory
6edf858a4e feat: add age packages for fido2 and other secret management 2026-01-06 12:57:34 -05:00
UGA Innovation Factory
3f1801fd84 fix: auto-installer works fully offline
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m35s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 10s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 12s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 8s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 17s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 11s
chore: run nix fmt

refactor: change usage of targetSystem to targetSystemBuild.toplevel

chore: run nix fmt

refactor: change usage of targetSystem to targetSystemBuild.toplevel
2026-01-05 16:19:03 -05:00
UGA Innovation Factory
f68c63590b fix: work on making the installer work offline
fix: ensure system closure is installed for derivations needed by the installer

fix: build closure in build-step instead of on iso
2026-01-05 16:17:33 -05:00
UGA Innovation Factory
c6f4a39eee fix: work on making the installer work offline 2026-01-05 11:58:38 -05:00
UGA Innovation Factory
c2b5e4eafe feat: add zima1 thru zima3 to hosts 2026-01-05 11:58:08 -05:00
UGA Innovation Factory
f07ccc071e docs: Copilot update all docs files
All checks were successful
CI / Format Check (push) Successful in 6s
CI / Flake Check (push) Successful in 1m25s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 10s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 11s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 8s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 16s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 10s
2026-01-05 10:05:41 -05:00
Hunter Halloran
0378268dcc fix: Disable firewall for camera discovery via external module for usda-dash
All checks were successful
CI / Format Check (push) Successful in 2s
CI / Flake Check (push) Successful in 1m16s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 10s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 12s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 9s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 17s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 10s
2025-12-29 20:31:14 -05:00
Hunter Halloran
cad9cb35ef chore: Update firewall settings via external module for usda-dash
All checks were successful
CI / Format Check (push) Successful in 1s
CI / Flake Check (push) Successful in 1m25s
CI / Evaluate Key Configurations (nix-builder) (push) Successful in 11s
CI / Evaluate Key Configurations (nix-desktop1) (push) Successful in 13s
CI / Evaluate Key Configurations (nix-laptop1) (push) Successful in 9s
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Successful in 17s
CI / Evaluate Artifacts (lxc-nix-builder) (push) Successful in 11s
2025-12-29 20:23:19 -05:00
Hunter Halloran
c8c3894e65 chore: Update firewall settings via external module for usda-dash
Some checks failed
CI / Format Check (push) Successful in 9s
CI / Flake Check (push) Failing after 14s
CI / Evaluate Key Configurations (nix-laptop1) (push) Has been skipped
CI / Evaluate Artifacts (installer-iso-nix-laptop1) (push) Has been skipped
CI / Evaluate Key Configurations (nix-builder) (push) Has been skipped
CI / Evaluate Key Configurations (nix-desktop1) (push) Has been skipped
CI / Evaluate Artifacts (lxc-nix-builder) (push) Has been skipped
2025-12-29 20:19:44 -05:00
92 changed files with 6556 additions and 3088 deletions

View File

@@ -26,18 +26,23 @@ jobs:
format-check:
name: Format Check
runs-on: [self-hosted, nix-builder]
timeout-minutes: 5
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Check formatting
timeout-minutes: 3
run: |
nix fmt **/*.nix
if ! git diff --quiet; then
set -euo pipefail
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."
git diff
echo "$output"
exit 1
fi
echo "All files are properly formatted"
eval-configs:
name: Evaluate Key Configurations
@@ -79,3 +84,39 @@ jobs:
echo "Evaluating artifact ${{ matrix.artifact }}"
nix eval .#${{ matrix.artifact }}.drvPath \
--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

View File

@@ -26,8 +26,9 @@ This is a **NixOS system configuration repository** that uses:
- **`flake.nix`**: Entry point - inputs and outputs only
- **`inventory.nix`**: Fleet definitions - host configurations
- **`users.nix`**: User account definitions
- **`hosts/`**: Host generation logic and hardware types
- **`sw/`**: Software configurations organized by system type
- **`hw/`**: Hardware type modules (desktop, laptop, surface, lxc, wsl, etc.)
- **`fleet/`**: Fleet generation logic and common system configuration
- **`sw/`**: Software configurations by system type
- **`installer/`**: Build artifact generation (ISO, LXC, etc.)
- **`templates/`**: Templates for external configurations
@@ -44,9 +45,12 @@ All Innovation Factory-specific options MUST use the `athenix` namespace:
### Host Options (`athenix.host.*`)
```nix
athenix.host = {
filesystem.device = "/dev/sda"; # Boot disk
filesystem.swapSize = "32G"; # Swap size
buildMethods = [ "iso" ]; # Artifact types
filesystem.device = "/dev/nvme0n1"; # Boot disk (default: null)
filesystem.swapSize = "32G"; # Swap size (default: null)
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
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, ... }`
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
### What NOT to Do

34
.github/workflows/docs.yml vendored Normal file
View 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
View 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
View File

@@ -1,156 +1,159 @@
# UGA Innovation Factory - Athenix
# Athenix - UGA Innovation Factory NixOS Configuration
[![CI](https://git.factory.uga.edu/UGA-Innovation-Factory/athenix/actions/workflows/ci.yml/badge.svg)](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)** - Configure hosts and fleet inventory
- **[docs/NAMESPACE.md](docs/NAMESPACE.md)** - Configuration options reference (`athenix.*`)
- **[docs/USER_CONFIGURATION.md](docs/USER_CONFIGURATION.md)** - User account management
- **[docs/EXTERNAL_MODULES.md](docs/EXTERNAL_MODULES.md)** - External configuration modules
- **[docs/BUILDING.md](docs/BUILDING.md)** - Build ISOs and container images
- **[docs/DEVELOPMENT.md](docs/DEVELOPMENT.md)** - Development and testing workflow
- **[docs/INVENTORY.md](docs/INVENTORY.md)** - Define and configure hosts
- **[docs/NAMESPACE.md](docs/NAMESPACE.md)** - All `athenix.*` options reference
- **[docs/USER_CONFIGURATION.md](docs/USER_CONFIGURATION.md)** - User accounts and dotfiles
- **[docs/EXTERNAL_MODULES.md](docs/EXTERNAL_MODULES.md)** - External system and user configurations
- **[docs/BUILDING.md](docs/BUILDING.md)** - Build ISOs, containers, and artifacts
- **[docs/DEVELOPMENT.md](docs/DEVELOPMENT.md)** - Development workflow and testing
## Quick Start
## Getting Started
### For End Users
Update your system to the latest configuration:
Update your system:
```bash
update-system
```
This command automatically fetches the latest configuration, rebuilds your system, and uses remote builders on Surface tablets to speed up builds.
**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
```
This automatically rebuilds your system with the latest configuration from the repository.
### For Administrators
Make configuration changes:
```bash
# 1. Make changes to configuration files
# Edit inventory
vim inventory.nix
# 2. Test configuration
# Validate changes
nix flake check
# 3. Format code
# Format code
nix fmt
# 4. Commit and push
git add .
git commit -m "Description of changes"
git push
# Commit and push
git add . && git commit -m "Your message" && git push
```
Users can now run `update-system` to get the changes.
**See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for detailed development workflow.**
Users automatically get changes when they run `update-system`.
## Repository Structure
```
nixos-systems/
├── flake.nix # Flake entry point
├── inventory.nix # Fleet inventory - Define hosts here
├── users.nix # User accounts - Define users here
├── hosts/ # Host generation logic
│ ├── types/ # Hardware types (desktop, laptop, surface, lxc, wsl, ephemeral)
│ └── ...
├── sw/ # Software configurations by system type
├── desktop/ # Full desktop environment
│ ├── tablet-kiosk/ # Surface kiosk mode
│ ├── stateless-kiosk/# Diskless PXE kiosks
│ ├── headless/ # Servers and containers
│ └── ...
├── installer/ # ISO and container builds
── templates/ # Templates for external configs
│ ├── system/ # System configuration template
│ └── user/ # User configuration template
├── docs/ # Documentation
│ ├── INVENTORY.md # Host configuration guide
│ ├── NAMESPACE.md # Option reference
│ ├── BUILDING.md # Building artifacts
│ └── DEVELOPMENT.md # Development guide
└── assets/ # Assets (Plymouth theme, etc.)
flake.nix # Flake entry point (inputs + outputs)
inventory.nix # Fleet inventory and host definitions
users.nix # User account definitions
flake.lock # Locked dependency versions
hw/ # Hardware type modules (exportable as nixosModules)
├── default.nix # Auto-exports all variant types
├── nix-desktop.nix # Desktop workstations
├── nix-laptop.nix # Laptop systems
├── nix-surface.nix # Surface Pro tablets
├── nix-lxc.nix # LXC containers
├── nix-wsl.nix # WSL instances
├── nix-zima.nix # ZimaBoard systems
── nix-ephemeral.nix # Diskless/netboot systems
fleet/ # Fleet generation and common configuration
├── default.nix # Processes inventory.nix to generate all hosts
├── common.nix # Common NixOS configuration (all hosts)
├── boot.nix # Boot and filesystem configuration
└── user-config.nix # User account and home-manager integration
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
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
# Host configuration
athenix.host.filesystem.device = "/dev/nvme0n1";
athenix.host.filesystem.swapSize = "64G";
# Host filesystem and hardware
athenix.host = {
filesystem.device = "/dev/sda";
filesystem.swapSize = "32G";
buildMethods = [ "installer-iso" ];
useHostPrefix = true;
};
# Software configuration
athenix.sw.type = "desktop"; # or "headless", "tablet-kiosk"
athenix.sw.extraPackages = with pkgs; [ vim docker ];
# System type and packages
athenix.sw = {
type = "desktop"; # desktop, tablet-kiosk, stateless-kiosk, headless, builders
extraPackages = with pkgs; [ vim docker ];
};
# User management
athenix.users.myuser.enable = true;
athenix.forUser = "myuser"; # Convenience shortcut
```
## Prerequisites
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
```
See [docs/NAMESPACE.md](docs/NAMESPACE.md) for complete option reference.
## 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
Edit `inventory.nix`:
```nix
# Simple: Create 5 laptops
# Simple: Create 5 identical laptops
nix-laptop = {
devices = 5; # Creates nix-laptop1 through nix-laptop5
devices = 5;
};
# With configuration
# With custom configuration per device
nix-surface = {
devices = {
"1".athenix.sw.kioskUrl = "https://dashboard1.example.com";
@@ -158,107 +161,163 @@ nix-surface = {
};
};
# With overrides for all devices
# With common overrides
nix-desktop = {
devices = 3;
overrides = {
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
Users and systems can reference external Git repositories for configuration:
Reference external repositories for user dotfiles or system configurations:
```nix
# In users.nix - External dotfiles with user configuration
myuser.external = builtins.fetchGit {
url = "https://git.factory.uga.edu/username/dotfiles";
# User dotfiles (in users.nix)
hdh20267.external = builtins.fetchGit {
url = "https://git.factory.uga.edu/hdh20267/dotfiles";
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 = {
devices."server" = builtins.fetchGit {
devices."special" = builtins.fetchGit {
url = "https://git.factory.uga.edu/org/server-config";
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.**
### Building Installation Media
```bash
# Build installer ISO
nix build git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git#installer-iso-nix-laptop1
# Build installer ISO for a specific host
nix build .#installer-iso-nix-laptop1
# Build LXC container
nix build .#lxc-nix-builder
# 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
Set via `athenix.sw.type`:
- **`desktop`** - Full GNOME desktop environment
- **`tablet-kiosk`** - Surface tablets in kiosk mode
- **`stateless-kiosk`** - Diskless PXE boot kiosks
- **`headless`** - Servers and containers (no GUI)
- **`tablet-kiosk`** - Surface tablets with Firefox kiosk browser
- **`stateless-kiosk`** - Diskless PXE-booted systems
- **`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
nix flake check # Validate all configurations
nix fmt # Format code
nix flake update # Update dependencies
nix build .#installer-iso-nix-laptop1 # Build specific artifact
# Check all configurations
nix flake check
# 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
**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
- **External modules not loading:** Check repository access and module structure (see templates)
- **Remote build failures:** Test SSH access: `ssh engr-ugaif@nix-builder`
- **Out of disk space:** Run `nix-collect-garbage -d && nix store optimise`
## Prerequisites
Nix with flakes support:
**Useful commands:**
```bash
nix flake show # List all available outputs
nix flake metadata # Show flake info
nix eval .#nixosConfigurations --apply builtins.attrNames # List all hosts
# Recommended: Determinate Systems installer
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
# 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
- Review documentation in `docs/` directory
- Check templates: `templates/user/` and `templates/system/`
- Contact Innovation Factory IT team
- [docs/INVENTORY.md](docs/INVENTORY.md) - Host configuration
- [docs/NAMESPACE.md](docs/NAMESPACE.md) - All option references
- [docs/USER_CONFIGURATION.md](docs/USER_CONFIGURATION.md) - User management
- [docs/EXTERNAL_MODULES.md](docs/EXTERNAL_MODULES.md) - External modules
- [docs/BUILDING.md](docs/BUILDING.md) - Building and deployment
- [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) - Development guide

Submodule assets/plymouth-theme deleted from 8658f4fb40

View File

@@ -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
- [Quick Start](#quick-start)
- [Available Artifacts](#available-artifacts)
- [Building Locally](#building-locally)
- [Building from Remote](#building-from-remote)
- [Installer ISOs](#installer-isos)
- [Live ISOs](#live-isos)
- [Container Images](#container-images)
@@ -15,116 +17,194 @@ This guide covers building installer ISOs, live images, and container artifacts
## Quick Start
```bash
# Build an installer ISO for a specific host
nix build git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git#installer-iso-nix-laptop1
# List all available artifacts
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/
```
## Available Artifacts
List all available build outputs:
Athenix can build multiple artifact types for deployment:
```bash
nix flake show git+https://git.factory.uga.edu/UGA-Innovation-Factory/athenix.git
| Type | Description | Location | Use Case |
|------|-------------|----------|----------|
| `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 |
|--------------|-------------|---------|
| `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
Build artifacts on your local machine:
```bash
# Build installer for a specific host
# Build installer ISO
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
nix build .#iso-nix-ephemeral1
# Result location
ls -lh result/iso/nixos-*.iso
# Build LXC container
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
# Build iPXE artifacts
nix build .#ipxe-nix-ephemeral1
# Get list of all hosts
nix eval .#nixosConfigurations --apply builtins.attrNames
# Result contains:
# - bzImage (kernel)
# - initrd (initial ramdisk)
# - netboot.ipxe (iPXE script)
ls -lh result/
# Build specific host
nix build .#nixosConfigurations.nix-laptop1.config.system.build.toplevel
```
## 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
### LXC Containers
Build LXC container tarballs for Proxmox or other LXC hosts:
Build LXC container tarballs for Proxmox or standalone LXC:
```bash
# Build LXC tarball
nix build .#lxc-nix-builder
# Result location
ls -lh result/tarball/nixos-*.tar.xz
ls -lh result/tarball/
```
**Importing to Proxmox:**
#### Importing to Proxmox
1. Copy tarball to Proxmox host:
```bash
# Copy tarball to Proxmox host
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 \
--hostname nix-builder \
--memory 4096 \
@@ -132,25 +212,59 @@ pct create 100 local:vztmpl/nixos-*.tar.xz \
--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
Build Proxmox-specific VMA archives:
```bash
# Build Proxmox VMA
nix build .#proxmox-nix-builder
# Result location
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
Speed up builds by offloading to build servers.
### One-Time Remote Build
### One-Time Build
```bash
nix build .#installer-iso-nix-laptop1 \
@@ -159,7 +273,7 @@ nix build .#installer-iso-nix-laptop1 \
### Persistent Configuration
Add to `~/.config/nix/nix.conf` or `/etc/nix/nix.conf`:
Add to `~/.config/nix/nix.conf`:
```conf
builders = ssh://engr-ugaif@nix-builder x86_64-linux
@@ -171,12 +285,12 @@ Then build normally:
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
# Generate SSH key if needed
# Generate key if needed
ssh-keygen -t ed25519
# Copy to builder
@@ -188,77 +302,86 @@ ssh engr-ugaif@nix-builder
### Multiple Builders
Configure multiple build servers:
```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
### Build Errors
**Check configuration validity:**
Get detailed error information:
```bash
# Verbose error traces
nix build .#installer-iso-nix-laptop1 --show-trace
# Check all configurations first
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
**Clean up Nix store:**
```bash
# Clean up Nix store
nix-collect-garbage -d
# Optimize store
nix store optimise
```
**Check space:**
### Build Hangs
```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
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:**
```bash
# Use correct block size and sync
sudo dd if=result/iso/nixos-*.iso of=/dev/sdX bs=4M status=progress && sync
```
### Build Not Creating Expected File
**Try alternative boot mode:**
- UEFI systems: Try legacy BIOS mode
- Legacy BIOS: Try UEFI mode
```bash
# Check build log
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
- [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
- [INVENTORY.md](INVENTORY.md) - Host configuration guide
- [installer/PROXMOX_LXC.md](../installer/PROXMOX_LXC.md) - Proxmox deployment guide

View File

@@ -1,464 +1,456 @@
# 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
- [Prerequisites](#prerequisites)
- [Development Workflow](#development-workflow)
- [Testing Changes](#testing-changes)
- [Continuous Integration](#continuous-integration)
- [System Rebuilds](#system-rebuilds)
- [Updating Dependencies](#updating-dependencies)
- [Adding Packages](#adding-packages)
- [Python Development](#python-development)
- [Contributing](#contributing)
- [Common Tasks](#common-tasks)
- [Debugging](#debugging)
- [Troubleshooting](#troubleshooting)
## Prerequisites
Install Nix with flakes support:
### Install Nix with Flakes
```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
# Alternative: Official installer (requires enabling flakes manually)
# Or official installer
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
Always test configuration changes before committing.
### Validate Configuration Syntax
### Validate All Configurations
Always run before committing:
```bash
# Check all configurations build correctly
nix flake check
```
# Check with verbose error traces
nix flake check --show-trace
Shows any configuration errors across all ~50+ hosts. Output:
```
checking 50 configurations...
✓ All checks passed
```
### Test Specific Host Build
```bash
# Build a specific host's configuration
# Build specific host (shows if config actually compiles)
nix build .#nixosConfigurations.nix-laptop1.config.system.build.toplevel
# Build installer for specific host
nix build .#installer-iso-nix-laptop1
# Shorter form
nix build .#nixosConfigurations.nix-laptop1.config.system.build.toplevel -L
```
### Test Local Changes
If you're on a NixOS system managed by this flake:
### Test Installer Build
```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 .
# Apply and switch to new configuration
# Apply and switch (persistent)
sudo nixos-rebuild switch --flake .
# Build without switching
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
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** - Validates all NixOS configurations
- Runs `nix flake check` to ensure all systems build correctly
- 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
1. **Flake Check** - `nix flake check` validates all 50+ configurations
2. **Format Check** - Verifies code formatted with `nix fmt`
3. **Build Key Hosts** - Builds `nix-builder`, `nix-laptop1`, `nix-desktop1`
4. **Build Artifacts** - Tests `lxc-nix-builder` and `installer-iso-nix-laptop1`
### Viewing CI Status
Check the CI status badge at the top of the README or view detailed logs:
```bash
# View workflow status
# Web interface
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
Before pushing changes, run the same checks that CI performs:
Test before pushing:
```bash
# Run all checks
# Flake check
nix flake check --show-trace
# Check formatting
nix fmt
git diff --exit-code # Should return no changes
# Format check
nix fmt --check
# Build specific configuration
nix build .#nixosConfigurations.nix-builder.config.system.build.toplevel
# Format code
nix fmt **/*.nix
# Build artifacts
nix build .#lxc-nix-builder
# Build key configurations
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
- 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`:
Edit `inventory.nix`:
```nix
nix-laptop = {
devices = 2;
nix-surface = {
devices = 3; # Creates nix-surface1, nix-surface2, nix-surface3
overrides = {
athenix.sw.extraPackages = with pkgs; [
vim
docker
kubernetes-helm
];
athenix.sw.type = "tablet-kiosk";
athenix.sw.kioskUrl = "https://dashboard.example.com";
};
};
```
### User-Specific Packages
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
Test:
```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
git add .
git commit -m "Description of changes"
git push
nix build .#installer-iso-nix-surface1 -L
```
### Documentation
### Modifying Software Configuration
Update relevant documentation when making changes:
- `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
Edit appropriate file in `sw/`:
```bash
# Show all available outputs
nix flake show
# Desktop software
vim sw/desktop/programs.nix
# Evaluate specific option
nix eval .#nixosConfigurations.nix-laptop1.config.networking.hostName
# Or for all systems
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
nix eval .#nixosConfigurations --apply builtins.attrNames
# Check flake metadata
nix flake metadata
# Show flake structure
nix flake show | head -50
# Show evaluation trace
nix eval --show-trace .#nixosConfigurations.nix-laptop1
# Check Nix store size
du -sh /nix/store
# Build and enter debug shell
nix develop
# List top space users in store
nix store du --human-readable | head -20
# Clean up old generations
nix-collect-garbage -d
# Optimize Nix store
nix store optimise
# Find store paths for a package
nix store path-info -rS $(which some-package)
```
### 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
- [README.md](../README.md) - Main documentation
- [INVENTORY.md](INVENTORY.md) - Host inventory configuration
- [BUILDING.md](BUILDING.md) - Building installation media
- [BUILDING.md](BUILDING.md) - Building artifacts
- [INVENTORY.md](INVENTORY.md) - Host configuration
- [NAMESPACE.md](NAMESPACE.md) - Configuration options
- [USER_CONFIGURATION.md](USER_CONFIGURATION.md) - User management
- [EXTERNAL_MODULES.md](EXTERNAL_MODULES.md) - External modules
- [README.md](../README.md) - Main documentation

View File

@@ -1,6 +1,6 @@
# 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
@@ -8,116 +8,141 @@ This guide explains how to use external modules for system and user configuratio
- [System Modules](#system-modules)
- [User Modules](#user-modules)
- [Fetch Methods](#fetch-methods)
- [Templates](#templates)
- [Integration Details](#integration-details)
- [Creating External Modules](#creating-external-modules)
- [Best Practices](#best-practices)
## Overview
External modules allow you to maintain configurations in separate Git repositories and reference them from `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:**
- **Separation:** Keep configs in separate repositories
- **Versioning:** Pin to specific commits for reproducibility
- **Reusability:** Share configurations across deployments
- **Flexibility:** Mix external modules with local overrides
- **Separation** - Keep complex configs in separate repositories
- **Reproducibility** - Pin specific commits for deterministic builds
- **Reusability** - Share configurations across multiple deployments
- **Flexibility** - Mix external modules with local configuration
- **Ownership** - Users maintain their own dotfiles
## System Modules
External system modules provide 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-lxc = {
devices = {
# Traditional inline configuration
# Inline configuration (traditional method)
"local-server" = {
athenix.users.admin.enable = true;
athenix.sw.type = "headless";
services.nginx.enable = true;
};
# External module from Git
"remote-server" = builtins.fetchGit {
# External module (lazy evaluation - fetched only when building this host)
"remote-server".external = builtins.fetchGit {
url = "https://git.factory.uga.edu/org/server-config";
rev = "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/
├── default.nix # Required: NixOS module
── README.md # Optional: Documentation
├── default.nix # Required: NixOS module
── README.md # Recommended: Documentation
└── optional/
├── config/ # Optional: Configuration files
└── scripts/ # Optional: Helper scripts
```
**default.nix:**
### Module Content (default.nix)
```nix
# The module receives inputs and standard NixOS module parameters
{ inputs, ... }:
{ config, lib, pkgs, ... }:
{
# Your NixOS configuration
# Use any standard NixOS option or athenix.* options
services.nginx = {
enable = true;
virtualHosts."example.com" = {
root = "/var/www";
forceSSL = true;
enableACME = true;
};
};
# Use athenix namespace options
athenix.users.admin.enable = true;
# Use athenix options
athenix.sw.type = "headless";
athenix.sw.extraPackages = with pkgs; [ git htop ];
# Standard NixOS configuration
networking.firewall.allowedTCPPorts = [ 80 443 ];
services.openssh.enable = true;
}
```
### What External Modules Receive
### What System Modules Receive
- **`inputs`** - All flake inputs (nixpkgs, home-manager, etc.)
- **`config`** - Full NixOS configuration
- **`inputs`** - All flake inputs (nixpkgs, home-manager, disko, etc.)
- **`config`** - Current NixOS configuration (read/write)
- **`lib`** - Nixpkgs library functions
- **`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)
2. Host type module (from `hosts/types/`)
3. Configuration overrides (from `inventory.nix`)
4. Hostname assignment
5. External system module (if using `builtins.fetchGit`)
1. Hardware type module (from `hw/nix-*.nix`)
2. Common system configuration (from `fleet/common.nix`)
3. Software type module (from `sw/{type}/`)
4. User NixOS modules (from `users.nix` - `nixos.nix` files)
5. Device-specific overrides (from `inventory.nix`)
6. External system module (if present)
Later modules can override earlier ones using standard NixOS module precedence.
### 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.
Each later module can override earlier ones using standard NixOS precedence rules.
## 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
athenix.users = {
# External user module (dotfiles, home-manager, and user options)
myuser = builtins.fetchGit {
# External user module
myuser.external = builtins.fetchGit {
url = "https://git.factory.uga.edu/username/dotfiles";
rev = "abc123...";
rev = "abc123def456..."; # Pin to specific commit
};
# Inline user definition
inlineuser = {
description = "Inline User";
otheruser = {
description = "Other User";
extraGroups = [ "wheel" ];
shell = pkgs.zsh;
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-laptop = {
devices = 5;
overrides.athenix.users.myuser.enable = true;
};
```
### Repository Structure
```
my-dotfiles/
├── user.nix # Required: User options + home-manager config
├── nixos.nix # Optional: System-level configuration
├── README.md # Recommended: Documentation
└── config/ # Optional: Your actual dotfiles
├── zshrc
├── vimrc
├── nvim/
└── ...
```
### user.nix (Required)
Provides both user account settings AND home-manager configuration:
```nix
# Receives { inputs } and standard home-manager module parameters
{ inputs, ... }:
{ config, lib, pkgs, osConfig ? null, ... }:
{
# ========== User Account Configuration ==========
# These options define the user account itself
athenix.users.myusername = {
description = "Your Full Name";
description = "My Full Name";
extraGroups = [ "wheel" "docker" ];
shell = pkgs.zsh;
hashedPassword = "!";
opensshKeys = [ "ssh-ed25519 AAAA..." ];
hashedPassword = "!"; # SSH keys only
opensshKeys = [
"ssh-ed25519 AAAA... user@laptop"
];
useZshTheme = true;
useNvimPlugins = true;
};
# ========== Home Manager Configuration ==========
# User environment, packages, and dotfiles
# Packages
home.packages = with pkgs; [
vim
git
htop
ripgrep
fzf
] ++ lib.optional (osConfig.athenix.sw.type or null == "desktop") firefox;
# Programs
programs.git = {
enable = true;
userName = "My Name";
userEmail = "me@example.com";
extraConfig = {
init.defaultBranch = "main";
core.editor = "vim";
};
};
programs.zsh = {
enable = true;
initExtra = ''
# Your Zsh configuration
export EDITOR=vim
'';
};
# Manage dotfiles
home.file.".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
{ inputs, ... }:
{ config, lib, pkgs, ... }:
{
# System-level configuration for this user
users.users.myuser.extraGroups = [ "docker" ];
# System-level configuration
# Only needed if the user requires specific system-wide settings
users.users.myusername.extraGroups = [ "docker" ];
environment.systemPackages = [ pkgs.docker ];
# Security settings
security.sudo.extraRules = [{
users = [ "myusername" ];
commands = [{
command = "/usr/bin/something";
options = [ "NOPASSWD" ];
}];
}];
}
```
### What User Modules Receive
**In user.nix:**
- **`inputs`** - Flake inputs (nixpkgs, home-manager, etc.)
- **`config`** - Home-manager configuration
- **`inputs`** - All flake inputs (nixpkgs, home-manager, etc.)
- **`config`** - Home-manager configuration (read/write)
- **`lib`** - Nixpkgs library functions
- **`pkgs`** - Package set
- **`osConfig`** - OS-level configuration (read-only)
- **`osConfig`** - OS configuration (read-only) - useful for conditional setup
**In nixos.nix:**
- **`inputs`** - Flake inputs
- **`config`** - NixOS configuration
- **`lib`** - Nixpkgs library functions
- **`config`** - NixOS configuration (read/write)
- **`lib`** - Nixpkgs library functions
- **`pkgs`** - Package set
### User Options in users.nix
### Conditional Setup Example
Use `osConfig` to conditionally set up dotfiles based on the system type:
```nix
username = {
# Identity
description = "Full Name";
# External configuration
external = builtins.fetchGit { ... };
# System settings
extraGroups = [ "wheel" "networkmanager" ];
hashedPassword = "$6$...";
opensshKeys = [ "ssh-ed25519 ..." ];
shell = pkgs.zsh;
# Theme integration
useZshTheme = true; # Apply system zsh theme (default: true)
useNvimPlugins = true; # Apply system nvim config (default: true)
# Enable on specific systems (see docs/INVENTORY.md)
enable = false; # Set in inventory.nix via athenix.users.username.enable
};
# In user.nix
{ inputs, ... }:
{ config, lib, pkgs, osConfig ? null, ... }:
{
athenix.users.myuser = { /* ... */ };
# Install Firefox only on desktop systems
home.packages = with pkgs; [
ripgrep
] ++ lib.optional (osConfig.athenix.sw.type or null == "desktop") firefox;
# Different shell config per system
programs.zsh.initExtra = ''
${lib.optionalString (osConfig.athenix.sw.type or null == "headless") "
# Headless-only settings
"}
'';
}
```
### 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
### Recommended: fetchGit with Revision
### builtins.fetchGit (Recommended)
Pin to a specific commit for reproducibility:
Pin to a specific Git revision:
```nix
builtins.fetchGit {
url = "https://github.com/user/repo";
rev = "abc123def456..."; # Full commit hash (40 characters)
ref = "main"; # Optional: branch name
url = "https://git.factory.uga.edu/username/dotfiles";
rev = "abc123def456..."; # Required: specific commit hash
}
```
**Finding the commit hash:**
```bash
# Latest commit on main branch
git ls-remote https://github.com/user/repo main
**Advantages:**
- Reproducible (pinned to exact commit)
- Works with any Git repository
- Supports SSH or HTTPS URLs
# Or from a local clone
git rev-parse HEAD
```
**Important:** Always specify `rev` (commit hash) for reproducibility. Don't use branches which can change.
### fetchGit with Branch (Less Reproducible)
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)
### builtins.fetchTarball
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:
```nix
/home/username/dev/my-config
# users.nix
athenix.users.myuser.external = /home/user/my-dotfiles;
# Or relative to repository
./my-local-config
```
⚠️ **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 = {
# inventory.nix
nix-laptop = {
devices = {
"server" = builtins.fetchGit {
url = "https://git.factory.uga.edu/org/base-config";
rev = "abc123...";
};
};
overrides = {
# Apply to all devices, including external ones
athenix.users.admin.enable = true;
networking.firewall.allowedTCPPorts = [ 80 443 ];
"dev".athenix.users.myuser.enable = true;
};
};
```
### 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
{ inputs, ... }:
{ config, lib, pkgs, osConfig ? null, ... }:
{
# User account options
athenix.users.myusername = {
description = "My Name";
shell = pkgs.zsh;
hashedPassword = "!";
};
# Home-manager config
home.packages = with pkgs; [ vim git ];
builtins.fetchGit {
url = "https://git.factory.uga.edu/username/dotfiles";
# No rev specified or using "main"
}
```
### Full User Module with Dotfiles
```
dotfiles/
├── user.nix
├── nixos.nix
└── config/
├── bashrc
├── vimrc
└── gitconfig
```
**user.nix:**
✅ Correct - using commit hash:
```nix
{ inputs, ... }:
{ config, lib, pkgs, osConfig ? null, ... }:
{
# User account configuration
athenix.users.myusername = {
description = "My Full Name";
shell = pkgs.zsh;
extraGroups = [ "wheel" "networkmanager" ];
hashedPassword = "!";
opensshKeys = [ "ssh-ed25519 AAAA..." ];
useZshTheme = true;
useNvimPlugins = true;
};
# Home-manager configuration
home.packages = with pkgs; [
ripgrep
fd
bat
] ++ lib.optional (osConfig.athenix.sw.type or null == "desktop") firefox;
programs.git = {
enable = true;
userName = "My Full Name";
userEmail = "me@example.com";
extraConfig.init.defaultBranch = "main";
};
home.file = {
".bashrc".source = ./config/bashrc;
".vimrc".source = ./config/vimrc;
".gitconfig".source = ./config/gitconfig;
};
builtins.fetchGit {
url = "https://git.factory.uga.edu/username/dotfiles";
rev = "abc123def456789...";
}
```
### 2. Keep External Modules Focused
Each external module should have a clear purpose:
- User dotfiles (one repo per user)
- System service configuration (one repo per service/cluster)
- Hardware-specific config (one repo per hardware setup)
### 3. Document Your Modules
Include a README with:
- What the module configures
- Required dependencies
- Usage examples
- Configuration options
### 4. Use Semantic Versioning
Tag releases in Git:
```bash
git tag v1.0.0
git push origin v1.0.0
```
Reference specific versions:
```nix
builtins.fetchGit {
url = "https://git.factory.uga.edu/org/server-config";
rev = "v1.0.0"; # Can use tags too
}
```
### 5. Test Before Updating Pins
When updating commit hashes:
```bash
# Test new revision locally
nix flake update
# Validate all configurations
nix flake check --show-trace
# Only commit after validation
git add . && git commit -m "Update module versions"
```
## See Also
- [INVENTORY.md](INVENTORY.md) - Host configuration guide
- [USER_CONFIGURATION.md](USER_CONFIGURATION.md) - User management guide
- [NAMESPACE.md](NAMESPACE.md) - Configuration options reference
- [templates/system/](../templates/system/) - System module template
- [templates/user/](../templates/user/) - User module template
- [USER_CONFIGURATION.md](USER_CONFIGURATION.md) - User management
- [INVENTORY.md](INVENTORY.md) - Host configuration
- [NAMESPACE.md](NAMESPACE.md) - Configuration options
- [README.md](../README.md) - Main documentation
- [templates/user/](../templates/user/) - User module template
- [templates/system/](../templates/system/) - System module template

View File

@@ -1,101 +1,272 @@
# 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
- [Understanding Inventory Structure](#understanding-inventory-structure)
- [Hostname Generation Rules](#hostname-generation-rules)
- [Adding Hosts](#adding-hosts)
- [Device Configuration Options](#device-configuration-options)
- [Overview](#overview)
- [Structure](#structure)
- [Hostname Generation](#hostname-generation)
- [Configuration Methods](#configuration-methods)
- [Options](#options)
- [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`)
- **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)
## Structure
```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-laptop = {
devices = 5;
overrides = {
# Applied to ALL nix-laptop hosts
athenix.users.student.enable = true;
athenix.sw.extraPackages = with pkgs; [ vim git ];
devices = 3; # Generates: nix-laptop1, nix-laptop2, nix-laptop3
};
nix-surface = {
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-surface = {
devices = {
"1".athenix.sw.kioskUrl = "https://dashboard1.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-surface = {
defaultCount = 2; # Creates nix-surface1, nix-surface2
nix-lxc = {
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 = {
"special" = { # Creates nix-surface-special
athenix.sw.kioskUrl = "https://special-dashboard.example.com";
"special" = {
athenix.sw.extraPackages = with pkgs; [ special-software ];
};
};
overrides = {
# Applied to all devices (including "special")
athenix.sw.kioskUrl = "https://default-dashboard.example.com";
# Applied to all devices (default count + custom)
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
"1" = {
# Athenix options
athenix.users.myuser.enable = true;
athenix.host.filesystem.swapSize = "64G";
athenix.sw.extraPackages = with pkgs; [ docker ];
athenix.sw.kioskUrl = "https://example.com";
# Standard NixOS options
networking.firewall.enable = false;
services.openssh.enable = true;
time.timeZone = "America/New_York";
nix-surface = {
devices = {
"1" = {
# athenix.* namespace options
athenix.users.student.enable = true;
athenix.host.filesystem.device = "/dev/sda";
athenix.host.filesystem.swapSize = "16G";
athenix.sw.kioskUrl = "https://dashboard1.example.com";
athenix.sw.extraPackages = with pkgs; [ firefox ];
# Standard NixOS options
networking.firewall.enable = false;
services.openssh.enable = true;
time.timeZone = "America/New_York";
boot.kernelPackages = pkgs.linuxPackages_latest;
};
};
};
```
### 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-wsl = {

View File

@@ -1,53 +1,69 @@
# 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
- [Host Configuration (`athenix.host`)](#host-configuration-athenixhost)
- [Software Configuration (`athenix.sw`)](#software-configuration-athenixsw)
- [User Management (`athenix.users`)](#user-management-athenixusers)
- [System Configuration (`athenix.system`)](#system-configuration-athenixsystem)
- [Convenience Options](#convenience-options)
## 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:**
- `athenix.host.filesystem.device` - Boot disk device (default: `/dev/sda`)
- `athenix.host.filesystem.swapSize` - Swap file size (default: `"32G"`)
**Type:** String or null
**Default:** `null` (must be set by hardware type or per-host)
**Example:**
```nix
athenix.host.filesystem = {
device = "/dev/nvme0n1";
swapSize = "64G";
};
athenix.host.filesystem.device = "/dev/nvme0n1";
```
### `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`
List of supported build artifact types for this host.
Artifact types to build for this host.
**Type:** List of strings
**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:**
```nix
athenix.host.buildMethods = [ "lxc" "proxmox" ];
athenix.host.buildMethods = [ "installer-iso" "lxc" ];
```
### `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
@@ -55,15 +71,19 @@ Whether to prepend the host type prefix to the hostname (used in inventory gener
**Example:**
```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:**
- `athenix.host.wsl.user` - Default WSL user for this instance
**Type:** String (username)
**Example:**
```nix
@@ -72,11 +92,11 @@ athenix.host.wsl.user = "myusername";
## Software Configuration (`athenix.sw`)
System software and application configuration.
System type, packages, and application configuration.
### `athenix.sw.enable`
Enable the software configuration module.
Enable software configuration.
**Type:** Boolean
@@ -84,28 +104,32 @@ Enable the software configuration module.
### `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:**
- `"desktop"` - Full desktop environment (GNOME)
- `"tablet-kiosk"` - Surface tablets with kiosk mode browser
- `"stateless-kiosk"` - Diskless PXE boot kiosks
- `"desktop"` - Full GNOME desktop environment with development tools
- `"tablet-kiosk"` - Surface tablets with Firefox kiosk browser
- `"stateless-kiosk"` - Diskless PXE-booted ephemeral systems
- `"headless"` - Servers and containers without GUI
- `"builders"` - Build servers with build dependencies
**Default:** `"desktop"`
**Example:**
```nix
athenix.sw.type = "headless";
athenix.sw.type = "desktop";
# Multiple types supported
athenix.sw.type = [ "desktop" "headless" ];
```
### `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"`
@@ -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.python`
### `athenix.sw.python.enable`
Python development tools configuration.
Enable Python development tools (pixi, uv, etc.).
**Options:**
- `athenix.sw.python.enable` - Enable Python tools (pixi, uv) (default: `true`)
**Type:** Boolean
**Default:** `true`
**Example:**
```nix
@@ -128,11 +153,13 @@ athenix.sw.python.enable = true;
### `athenix.sw.remoteBuild`
Remote build server configuration for offloading builds.
Configure remote build servers for offloading builds.
**Type:** Attribute set
**Options:**
- `athenix.sw.remoteBuild.enable` - Use remote builders (default: enabled on tablets)
- `athenix.sw.remoteBuild.hosts` - List of build server hostnames
- `enable` - Enable remote builders (Boolean, default: `true` for tablets)
- `hosts` - List of remote builder hostnames (List of strings)
**Example:**
```nix
@@ -144,104 +171,187 @@ athenix.sw.remoteBuild = {
### `athenix.sw.extraPackages`
Additional system packages to install beyond the type defaults.
Additional system packages beyond the type defaults.
**Type:** List of packages
**Default:** `[]`
**Default:** `[ ]`
**Example:**
```nix
athenix.sw.extraPackages = with pkgs; [
vim
htop
docker
htop
ripgrep
];
```
### `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
**Default:** `[]`
**Default:** `[ ]`
**Example:**
```nix
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 account configuration and management.
User account configuration and access control.
### `athenix.users.<username>.enable`
Enable a specific user account on this system.
Enable a user account on this system.
**Type:** Boolean
**Default:** `false` (except `root` and `engr-ugaif` which default to `true`)
**Default:** `false` (except `root` and `engr-ugaif` which are `true`)
**Example:**
```nix
athenix.users = {
myuser.enable = true;
student.enable = true;
# In inventory.nix
nix-laptop = {
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
# Option 1: Define inline in users.nix
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
};
athenix.users.myuser.description = "John Doe";
```
# Option 2: Use external configuration (recommended)
# The external user.nix can set athenix.users.myuser options directly
athenix.users.anotheruser.external = builtins.fetchGit {
#### `extraGroups`
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";
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
athenix.system.gc = {
enable = true;
frequency = "daily";
retentionDays = 14;
optimise = true;
# Option 1: Enable on all devices in a group
nix-laptop = {
devices = 5;
overrides.athenix.users.student.enable = 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`
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
@@ -257,11 +367,7 @@ Quick setup option that enables a user account in one line.
**Example:**
```nix
athenix.forUser = "myusername"; # Equivalent to athenix.users.myusername.enable = true
```
**Usage in inventory.nix:**
```nix
# In inventory.nix - enables the user automatically
nix-wsl = {
devices = {
"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
- [INVENTORY.md](INVENTORY.md) - Host inventory configuration guide
- [USER_CONFIGURATION.md](USER_CONFIGURATION.md) - User management guide
- [EXTERNAL_MODULES.md](EXTERNAL_MODULES.md) - External configuration modules
- [INVENTORY.md](INVENTORY.md) - Host configuration examples
- [USER_CONFIGURATION.md](USER_CONFIGURATION.md) - User account management guide
- [EXTERNAL_MODULES.md](EXTERNAL_MODULES.md) - External module integration
- [README.md](../README.md) - Main documentation

View File

@@ -1,103 +1,585 @@
# User Configuration Guide
Complete guide to managing user accounts in nixos-systems.
Comprehensive guide to managing user accounts in Athenix.
## Table of Contents
- [Overview](#overview)
- [Quick Start](#quick-start)
- [User Account Options](#user-account-options)
- [External User Configurations](#external-user-configurations)
- [Defining Users](#defining-users)
- [Enabling Users on Hosts](#enabling-users-on-hosts)
- [External User Configurations](#external-user-configurations)
- [Password Management](#password-management)
- [SSH Keys](#ssh-keys)
- [User Groups](#user-groups)
- [Examples](#examples)
## 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:**
- `root` - System administrator
- `engr-ugaif` - Innovation Factory default account
**Always-enabled users:**
- `root` - System administrator (enable: true)
- `engr-ugaif` - Innovation Factory default account (enable: true)
All other users are disabled by default and must be explicitly enabled per-host.
## Quick Start
### 1. Define User in users.nix
```nix
athenix.users = {
# Option 1: Inline definition
myuser = {
description = "My Full Name";
extraGroups = [ "wheel" "networkmanager" ];
shell = pkgs.zsh;
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
};
athenix.users.myuser = {
description = "John Doe";
extraGroups = [ "wheel" "networkmanager" ];
shell = pkgs.zsh;
hashedPassword = "$6$..."; # Generate with: mkpasswd -m sha-512
opensshKeys = [ "ssh-ed25519 AAAA..." ];
};
```
### 2. Enable User on Hosts
In `inventory.nix`:
### 2. Enable on Hosts in inventory.nix
```nix
nix-laptop = {
devices = 2;
overrides.athenix.users.myuser.enable = true; # Enables on all nix-laptop hosts
};
# 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
devices = 5;
overrides.athenix.users.myuser.enable = true;
};
```
## 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
username = {
# === Identity ===
description = "Full Name"; # User's full name
athenix.users.myuser = {
description = "My 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 ===
isNormalUser = true; # Default: true (false for root)
extraGroups = [ # Additional Unix groups
"wheel" # Sudo access
"networkmanager" # Network configuration
"docker" # Docker access
"video" # Video device access
"audio" # Audio device access
home.packages = with pkgs; [
vim
ripgrep
fzf
] ++ lib.optional (osConfig.athenix.sw.type or null == "desktop") firefox;
programs.git = {
enable = true;
userName = "My Name";
userEmail = "me@example.com";
extraConfig = {
init.defaultBranch = "main";
core.editor = "vim";
};
};
programs.zsh = {
enable = true;
initExtra = ''
# Your Zsh configuration
'';
};
# 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)
hashedPassword = "$6$..."; # Hashed password (see below)
# === SSH Access ===
opensshKeys = [ # SSH public keys
"ssh-ed25519 AAAA... user@host"
"ssh-rsa AAAA... user@otherhost"
];
# === External Configuration ===
useZshTheme = true;
useNvimPlugins = true;
};
# inventory.nix
nix-desktop = {
devices = 3;
overrides.athenix.users.developer.enable = true;
};
```
### 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)
# === Theme Integration ===

110
flake.lock generated
View File

@@ -115,11 +115,11 @@
]
},
"locked": {
"lastModified": 1765794845,
"narHash": "sha256-YD5QWlGnusNbZCqR3pxG8tRxx9yUXayLZfAJRWspq2s=",
"lastModified": 1766150702,
"narHash": "sha256-P0kM+5o+DKnB6raXgFEk3azw8Wqg5FL6wyl9jD+G5a4=",
"owner": "nix-community",
"repo": "disko",
"rev": "7194cfe5b7a3660726b0fe7296070eaef601cae9",
"rev": "916506443ecd0d0b4a0f4cf9d40a3c22ce39b378",
"type": "github"
},
"original": {
@@ -159,6 +159,26 @@
}
},
"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": {
"nixpkgs-lib": [
"lazyvim-nixvim",
@@ -219,6 +239,24 @@
"inputs": {
"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": {
"lastModified": 1681202837,
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
@@ -318,11 +356,11 @@
]
},
"locked": {
"lastModified": 1765979862,
"narHash": "sha256-/r9/1KamvbHJx6I40H4HsSXnEcBAkj46ZwibhBx9kg0=",
"lastModified": 1767910483,
"narHash": "sha256-MOU5YdVu4DVwuT5ztXgQpPuRRBjSjUGIdUzOQr9iQOY=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "d3135ab747fd9dac250ffb90b4a7e80634eacbe9",
"rev": "82fb7dedaad83e5e279127a38ef410bcfac6d77c",
"type": "github"
},
"original": {
@@ -386,7 +424,7 @@
},
"lazyvim-nixvim": {
"inputs": {
"flake-parts": "flake-parts",
"flake-parts": "flake-parts_2",
"nixpkgs": "nixpkgs",
"nixvim": "nixvim"
},
@@ -464,11 +502,11 @@
},
"nixos-hardware": {
"locked": {
"lastModified": 1764440730,
"narHash": "sha256-ZlJTNLUKQRANlLDomuRWLBCH5792x+6XUJ4YdFRjtO4=",
"lastModified": 1767185284,
"narHash": "sha256-ljDBUDpD1Cg5n3mJI81Hz5qeZAwCGxon4kQW3Ho3+6Q=",
"owner": "NixOS",
"repo": "nixos-hardware",
"rev": "9154f4569b6cdfd3c595851a6ba51bfaa472d9f3",
"rev": "40b1a28dce561bea34858287fbb23052c3ee63fe",
"type": "github"
},
"original": {
@@ -518,11 +556,11 @@
},
"nixpkgs-old-kernel": {
"locked": {
"lastModified": 1765687488,
"narHash": "sha256-7YAJ6xgBAQ/Nr+7MI13Tui1ULflgAdKh63m1tfYV7+M=",
"lastModified": 1767313136,
"narHash": "sha256-16KkgfdYqjaeRGBaYsNrhPRRENs0qzkQVUooNHtoy2w=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "d02bcc33948ca19b0aaa0213fe987ceec1f4ebe1",
"rev": "ac62194c3917d5f474c1a844b6fd6da2db95077d",
"type": "github"
},
"original": {
@@ -534,11 +572,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1765838191,
"narHash": "sha256-m5KWt1nOm76ILk/JSCxBM4MfK3rYY7Wq9/TZIIeGnT8=",
"lastModified": 1768242861,
"narHash": "sha256-F4IIxa5xDHjtrmMcayM8lHctUq1oGltfBQu2+oqDWP4=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c6f52ebd45e5925c188d1a20119978aa4ffd5ef6",
"rev": "1327e798cb055f96f92685df444e9a2c326ab5ed",
"type": "github"
},
"original": {
@@ -608,6 +646,7 @@
"inputs": {
"agenix": "agenix",
"disko": "disko",
"flake-parts": "flake-parts",
"home-manager": "home-manager_2",
"lazyvim-nixvim": "lazyvim-nixvim",
"nixos-generators": "nixos-generators",
@@ -615,6 +654,7 @@
"nixos-wsl": "nixos-wsl",
"nixpkgs": "nixpkgs_2",
"nixpkgs-old-kernel": "nixpkgs-old-kernel",
"usda-vision": "usda-vision",
"vscode-server": "vscode-server"
}
},
@@ -699,6 +739,21 @@
"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": {
"inputs": {
"nixpkgs": [
@@ -721,13 +776,34 @@
"type": "github"
}
},
"vscode-server": {
"usda-vision": {
"inputs": {
"flake-utils": "flake-utils_3",
"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": {
"lastModified": 1753541826,
"narHash": "sha256-foGgZu8+bCNIGeuDqQ84jNbmKZpd+JvnrL2WlyU4tuU=",

View File

@@ -4,7 +4,7 @@
# ============================================================================
# This file defines the inputs (dependencies) and outputs (configurations)
# for Athenix. It ties together the hardware, software, and user
# configurations into deployable systems.
# configurations into deployable systems using flake-parts.
inputs = {
# Core NixOS package repository (Release 25.11)
@@ -13,6 +13,12 @@
# Older kernel packages for Surface compatibility if needed
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 = {
url = "github:nix-community/home-manager/release-25.11";
@@ -53,49 +59,37 @@
url = "github:nix-community/NixOS-WSL/main";
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 =
inputs@{
self,
nixpkgs,
nixpkgs-old-kernel,
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 [
inputs@{ self, flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
# Support all common systems
systems = [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
in
{
# Formatter for 'nix fmt'
formatter = forAllSystems (system: nixpkgs.legacyPackages.${system}.nixfmt-rfc-style);
# Generate NixOS configurations from hosts/default.nix
nixosConfigurations = hosts.nixosConfigurations;
# Expose artifacts to all systems, but they are always built for x86_64-linux
packages = forAllSystems (_: artifacts);
# Expose modules for external use
nixosModules = import ./installer/modules.nix { inherit inputs; };
# Templates for external configurations
templates = import ./templates;
# Import flake-parts modules
imports = [
./parts/formatter.nix
./parts/lib.nix
./parts/nixos-configurations.nix
./parts/nixos-modules.nix
./parts/packages.nix
./parts/templates.nix
./parts/docs.nix
./inventory.nix
./users.nix
];
};
}

64
fleet/boot.nix Normal file
View 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
View 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"
];
}
];
}

View File

@@ -1,36 +1,25 @@
{
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
# defined in inventory.nix. It supports both hostname-based and count-based
# 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
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
mkHost =
{
@@ -38,13 +27,42 @@ let
system ? "x86_64-linux",
hostType,
configOverrides ? { },
externalModulePath ? null,
externalModuleThunk ? null,
}:
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
pkgs = nixpkgs.legacyPackages.${system};
usersData = import ../users.nix { inherit pkgs; };
accounts = usersData.athenix.users or { };
accounts = config.athenix.users or { };
# 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
@@ -53,10 +71,19 @@ let
name: user:
if (user ? external && user.external != null) then
let
# Resolve external path (lazy fetchGit if needed)
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
else
# Direct path
user.external;
nixosModulePath = externalPath + "/nixos.nix";
in
@@ -94,14 +121,6 @@ let
}
) 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
externalPathModule =
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 =
userNixosModules
++ [
typeModule
./common.nix
overrideModule
{ 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;
in
{
system = lib.nixosSystem {
inherit system;
specialArgs = { inherit inputs; };
specialArgs = {
inputs = if self != null then inputs // { inherit self; } else inputs;
};
modules = allModules;
};
modules = allModules;
@@ -153,8 +188,6 @@ let
let
hostType = config.type or prefix;
system = config.system or "x86_64-linux";
devices = config.devices or { };
hasCount = config ? count;
# Helper to generate hostname from prefix and suffix
# Numbers get no dash: "nix-surface1", "nix-surface2"
@@ -192,48 +225,38 @@ let
lib.mapAttrsToList (
deviceKey: deviceConfig:
let
# Check if deviceConfig is a path/derivation (from fetchGit, fetchurl, etc.)
# fetchGit/fetchTarball return an attrset with outPath attribute
isExternalModule =
(builtins.isPath deviceConfig)
|| (builtins.isString deviceConfig && lib.hasPrefix "/" deviceConfig)
|| (lib.isDerivation deviceConfig)
|| (builtins.isAttrs deviceConfig && deviceConfig ? outPath);
# Check if deviceConfig has an 'external' field for lazy evaluation
hasExternalField = builtins.isAttrs deviceConfig && deviceConfig ? external;
# Extract the actual path from fetchGit/fetchTarball results
extractedPath =
if builtins.isAttrs deviceConfig && deviceConfig ? outPath then
deviceConfig.outPath
# Extract external module spec (don't evaluate fetchGit yet!)
externalModuleThunk =
if hasExternalField then
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
deviceConfig;
null;
# If external module, we use base config + overrides as the config
# and pass the module path separately
actualConfig =
if isExternalModule then (lib.recursiveUpdate baseConfig overrides) else deviceConfig;
# Remove 'external' from config to avoid conflicts
cleanDeviceConfig =
if hasExternalField then lib.removeAttrs deviceConfig [ "external" ] else deviceConfig;
# Merge: base config -> overrides -> device-specific config (only if not external module)
mergedConfig =
if isExternalModule then
actualConfig
else
lib.recursiveUpdate (lib.recursiveUpdate baseConfig overrides) deviceConfig;
# Merge: base config -> overrides -> device-specific config
mergedConfig = lib.recursiveUpdate (lib.recursiveUpdate baseConfig overrides) cleanDeviceConfig;
# Check useHostPrefix from the merged config
usePrefix = mergedConfig.athenix.host.useHostPrefix or true;
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
{
name = hostName;
@@ -242,7 +265,7 @@ let
hostName
system
hostType
externalModulePath
externalModuleThunk
;
configOverrides = mergedConfig;
};
@@ -289,10 +312,12 @@ let
{ };
in
lib.recursiveUpdate deviceHosts countHosts
) hosts;
);
fleetData = config.athenix.fleet;
# Flatten the nested structure
allHosts = lib.foldl' lib.recursiveUpdate { } (lib.attrValues processInventory);
allHosts = lib.foldl' lib.recursiveUpdate { } (lib.attrValues (processInventory fleetData));
in
{
nixosConfigurations = lib.mapAttrs (n: v: v.system) allHosts;

227
fleet/fleet-option.nix Normal file
View 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
View 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
View 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;
};
};
}

View File

@@ -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?
}
];
}

View File

@@ -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;
}

View File

@@ -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";
}

View File

@@ -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";
}

View File

@@ -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";
}

View File

@@ -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";
}

View File

@@ -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
}

View File

@@ -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
View 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
View 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
View 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
View 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
View 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
View 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
};
}

View File

@@ -4,27 +4,46 @@
# Configuration for NixOS running in WSL2 on Windows.
# Integrates with nixos-wsl for WSL-specific functionality.
{ inputs, ... }:
{
lib,
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 {
type = lib.types.str;
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.enable = true;
# Use forUser if set, otherwise fall back to wsl.user option
@@ -33,7 +52,7 @@
# ========== Software Profile ==========
athenix.sw.enable = lib.mkDefault true;
athenix.sw.type = lib.mkDefault "headless";
athenix.sw.headless.enable = lib.mkDefault true;
# ========== Remote Development ==========
services.vscode-server.enable = true;
@@ -50,5 +69,8 @@
# Provide dummy values for required options from boot.nix
athenix.host.filesystem.device = "/dev/null";
athenix.host.filesystem.swapSize = "0G";
# WSL doesn't use installer ISOs
athenix.host.buildMethods = lib.mkDefault [ ];
};
}

71
hw/nix-zima.nix Normal file
View 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;
};
}

View File

@@ -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
{

View File

@@ -1,8 +1,9 @@
{
inputs,
hosts,
fleet,
self,
system,
users ? { },
}:
# 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>`
@@ -18,7 +19,7 @@ let
hostName:
let
targetConfig = self.nixosConfigurations.${hostName}.config;
targetSystem = targetConfig.system.build.toplevel;
targetSystemBuild = targetConfig.system.build;
diskoScript = targetConfig.system.build.diskoScript;
in
nixpkgs.lib.nixosSystem {
@@ -27,8 +28,9 @@ let
inherit
inputs
hostName
targetSystem
targetSystemBuild
diskoScript
users
;
hostPlatform = system;
};
@@ -45,7 +47,10 @@ let
nixos-generators.nixosGenerate {
inherit system;
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;
services.upower.enable = lib.mkForce false;
@@ -61,8 +66,11 @@ let
nixpkgs.lib.nixosSystem {
inherit system;
specialArgs = { inherit inputs; };
modules = hosts.modules.${hostName} ++ [
modules = fleet.modules.${hostName} ++ [
"${nixpkgs}/nixos/modules/installer/netboot/netboot.nix"
{
config.athenix.users = lib.mapAttrs (_: user: lib.mapAttrs (_: lib.mkDefault) user) users;
}
{
disko.enableConfig = 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
installerPackages = lib.listToAttrs (
lib.concatMap (
name:
let
cfg = hosts.nixosConfigurations.${name};
cfg = fleet.nixosConfigurations.${name};
in
if lib.elem "installer-iso" cfg.config.athenix.host.buildMethods then
[
@@ -96,7 +104,7 @@ let
lib.concatMap (
name:
let
cfg = hosts.nixosConfigurations.${name};
cfg = fleet.nixosConfigurations.${name};
in
if lib.elem "iso" cfg.config.athenix.host.buildMethods then
[
@@ -115,7 +123,7 @@ let
lib.concatMap (
name:
let
cfg = hosts.nixosConfigurations.${name};
cfg = fleet.nixosConfigurations.${name};
in
if lib.elem "ipxe" cfg.config.athenix.host.buildMethods then
[
@@ -145,7 +153,7 @@ let
lib.concatMap (
name:
let
cfg = hosts.nixosConfigurations.${name};
cfg = fleet.nixosConfigurations.${name};
in
if lib.elem "lxc" cfg.config.athenix.host.buildMethods then
[
@@ -164,7 +172,7 @@ let
lib.concatMap (
name:
let
cfg = hosts.nixosConfigurations.${name};
cfg = fleet.nixosConfigurations.${name};
in
if lib.elem "proxmox" cfg.config.athenix.host.buildMethods then
[

View File

@@ -2,13 +2,10 @@
# 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.
{
config,
lib,
pkgs,
inputs,
hostName,
hostPlatform,
targetSystem,
targetSystemBuild,
diskoScript,
...
}:
@@ -17,11 +14,21 @@
pkgs.git
pkgs.bashInteractive
pkgs.curl
targetSystem
targetSystemBuild.toplevel
];
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 = {
description = "Automatic NixOS install for ${hostName}";
after = [
@@ -44,8 +51,12 @@
echo ">>> Running disko script..."
${diskoScript}
echo ">>> Running nixos-install..."
nixos-install --no-root-passwd --system ${targetSystem}
echo ">>> Setting up NixOS..."
nixos-install \
--system ${targetSystemBuild.toplevel} \
--no-root-passwd \
--no-channel-copy \
--substituters ""
echo ">>> Done. Rebooting."
systemctl reboot

View File

@@ -6,40 +6,26 @@
#
# Usage in another flake:
# # Full host type configurations (includes hardware + software + system config)
# inputs.nixos-systems.nixosModules.nix-desktop
# inputs.nixos-systems.nixosModules.nix-laptop
# inputs.athenix.nixosModules.nix-desktop
# inputs.athenix.nixosModules.nix-laptop
#
# # Software-only configurations (for custom hardware setups)
# # Note: These include theme.nix in home-manager.sharedModules automatically
# 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;
# })
# ];
# # Software-only configuration (for custom hardware setups)
# inputs.athenix.nixosModules.sw
{ 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 ==========
# Complete system configurations including hardware, boot, and software
nix-desktop = import ../hosts/types/nix-desktop.nix { inherit inputs; }; # Desktop workstations
nix-laptop = import ../hosts/types/nix-laptop.nix { inherit inputs; }; # Laptop systems
nix-surface = import ../hosts/types/nix-surface.nix { inherit inputs; }; # Surface tablets
nix-lxc = import ../hosts/types/nix-lxc.nix { inherit inputs; }; # Proxmox containers
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
# ========== 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; }));
# Software configuration module - main module with all athenix.sw options
# Use athenix.sw.<type>.enable to enable software profiles: desktop, tablet-kiosk, headless, stateless-kiosk, builders
hw = hostTypes;
sw =
{
inputs,
...
}@args:
(import ../sw/default.nix (args // { inherit inputs; }));
}

View File

@@ -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";
# };
# };
{ ... }:
{
# ============================================================================
# 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 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";
athenix.fleet = {
# ========== Lab Laptops ==========
# Creates: nix-laptop1, nix-laptop2
# Both get hdh20267 user via overrides
nix-laptop = {
devices = 2;
overrides.athenix.users.hdh20267.enable = true;
};
overrides = {
athenix.sw.kioskUrl = "https://yahoo.com";
};
};
# ========== LXC Containers ==========
# Creates: nix-builder (without lxc prefix)
nix-lxc = {
devices = {
"nix-builder" = {
# Gitea Actions self-hosted runner configuration
athenix.sw = {
type = [
"headless"
"builders"
];
builders.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";
# ========== 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.tablet-kiosk.kioskUrl = "https://google.com";
};
overrides = {
athenix.sw.tablet-kiosk.kioskUrl = "https://yahoo.com";
};
};
# ========== LXC Containers ==========
# Creates: nix-builder (without lxc prefix)
nix-lxc = {
devices = {
"nix-builder" = {
# Gitea Actions self-hosted runner configuration
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 {
url = "git@factory.uga.edu:MODEL/usda-dash-config.git";
rev = "49cded91cff4a956d4e01ac6b8fe4efa86f82182";
submodules = true;
overrides = {
athenix.host.useHostPrefix = false;
};
};
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
View File

@@ -0,0 +1,8 @@
{
lib,
...
}:
{
mkFleet = import ./mkFleet.nix;
macCaseBuilder = import ./macCaseBuilder.nix { inherit lib; };
}

33
lib/macCaseBuilder.nix Normal file
View 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
View 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
View 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
View File

@@ -0,0 +1,9 @@
# Formatter configuration for flake-parts
{ ... }:
{
perSystem =
{ pkgs, ... }:
{
formatter = pkgs.nixfmt-rfc-style;
};
}

8
parts/lib.nix Normal file
View File

@@ -0,0 +1,8 @@
# Library functions for flake-parts
{ inputs, ... }:
{
flake.lib = import ../lib {
inherit inputs;
lib = inputs.nixpkgs.lib;
};
}

View 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
View 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
View 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
View File

@@ -0,0 +1,5 @@
# Templates for external configurations
{ ... }:
{
flake.templates = import ../templates;
}

187
secrets.nix Normal file
View 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
View 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
View 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/)

View File

@@ -0,0 +1 @@
age14emzyraytqzmre58c452t07rtcj87cwqwmd9z3gj7upugtxk8s3sda5tju

BIN
secrets/core Normal file

Binary file not shown.

121
secrets/create-secret.sh Executable file
View 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

View File

@@ -0,0 +1 @@
age1udmpqkedupd33gyut85ud3nvppydzeg04kkuneymkvxcjjej244s4v8xjc

View 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";
};
}

View File

@@ -0,0 +1 @@
age1u5tczg2sx90n03uuz9h549f4h3h7sq5uehhqpampzs7vj8ew7y6s2mjwz0

176
secrets/secrets.nix Normal file
View 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
View 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"

View File

@@ -0,0 +1,8 @@
# Host-specific secret configuration for usda-dash
{
usda-vision-azure-env = {
mode = "0600";
owner = "root";
group = "root";
};
}

View File

@@ -0,0 +1 @@
age1lr24yvk7rdfh5wkle7h32jpxqxm2e8vk85mc4plv370u2sh4yfmszaaejx

View File

@@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHI73LOEK2RgfjhZWpryntlLbx0LouHrhQ6v0vZu4Etr root@usda-dash

Binary file not shown.

View File

@@ -11,21 +11,123 @@
...
}:
lib.mkMerge [
(import ./programs.nix {
inherit
config
lib
pkgs
inputs
;
})
(import ./services.nix {
inherit
config
lib
pkgs
inputs
;
})
]
with lib;
let
cfg = config.athenix.sw.builders;
in
{
options.athenix.sw.builders = mkOption {
type = lib.types.submodule {
options = {
enable = mkOption {
type = lib.types.bool;
default = false;
description = ''
Enable build server configuration.
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
;
})
]);
}

View File

@@ -1,8 +1,6 @@
{
config,
lib,
pkgs,
inputs,
...
}:
@@ -10,7 +8,7 @@ with lib;
let
cfg = config.athenix.sw;
basePackages = with pkgs; [
basePackages = [
# Build-related packages can be added here if needed
];
in

View File

@@ -10,184 +10,98 @@
# Software Module Entry Point
# ============================================================================
# This module manages the software configuration for the system. It provides
# options to select the system type ('desktop' or 'kiosk') and handles
# the conditional importation of the appropriate sub-modules.
# enable options for each system type (desktop, headless, builders, etc.)
# 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;
let
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
{
imports = [
./python.nix
./ghostty.nix
./gc.nix
./updater.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 = {
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 = types.oneOf [
(types.enum [
"desktop"
"tablet-kiosk"
"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.";
type = lib.types.nullOr (lib.types.either lib.types.str (lib.types.listOf lib.types.str));
default = null;
description = "DEPRECATED: Use athenix.sw.<type>.enable instead. Legacy type selection.";
visible = false;
};
extraPackages = mkOption {
type = types.listOf types.package;
type = lib.types.listOf lib.types.package;
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 {
type = types.listOf types.package;
type = lib.types.listOf lib.types.package;
default = [ ];
description = "Packages to exclude from the default list.";
};
kioskUrl = mkOption {
type = types.str;
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";
description = ''
Packages to exclude from the default package list.
Useful for removing unwanted default packages.
'';
example = lib.literalExpression "[ pkgs.htop ]";
};
};
config = mkIf cfg.enable (mkMerge [
{
# ========== System-Wide Configuration ==========
nixpkgs.config.allowUnfree = true;
config = mkIf cfg.enable {
# ========== System-Wide Configuration ==========
nixpkgs.config.allowUnfree = true;
# ========== Shell Configuration ==========
programs.zsh.enable = true;
programs.nix-ld.enable = true; # Allow running non-NixOS binaries
# ========== Shell Configuration ==========
programs.zsh.enable = true;
programs.nix-ld.enable = true; # Allow running non-NixOS binaries
# ========== Base Packages ==========
environment.systemPackages =
with pkgs;
subtractLists cfg.excludePackages [
htop # System monitor
binutils # Binary utilities
zsh # Z shell
git # Version control
oh-my-posh # Shell prompt theme
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
;
}
))
]);
# ========== Base Packages ==========
environment.systemPackages =
with pkgs;
subtractLists cfg.excludePackages [
htop # System monitor
binutils # Binary utilities
zsh # Z shell
git # Version control
oh-my-posh # Shell prompt theme
age # Simple file encryption tool
age-plugin-fido2-hmac # age FIDO2 support
inputs.agenix.packages.${stdenv.hostPlatform.system}.default # Secret management
];
};
}

View File

@@ -10,21 +10,56 @@
inputs,
...
}:
lib.mkMerge [
(import ./programs.nix {
inherit
config
lib
pkgs
inputs
;
})
(import ./services.nix {
inherit
config
lib
pkgs
inputs
;
})
]
with lib;
let
cfg = config.athenix.sw.desktop;
in
{
options.athenix.sw.desktop = mkOption {
type = lib.types.submodule {
options = {
enable = mkOption {
type = lib.types.bool;
default = false;
description = ''
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
;
})
]);
}

View File

@@ -2,7 +2,6 @@
config,
lib,
pkgs,
inputs,
...
}:

View File

@@ -1,5 +1,4 @@
{
config,
lib,
pkgs,
...

75
sw/gc.nix Normal file
View 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;
};
}

View File

@@ -12,7 +12,11 @@
# It reconstructs the terminfo database from the provided definition and
# adds it to the system packages.
with lib;
let
cfg = config.athenix.sw;
ghostty-terminfo = pkgs.runCommand "ghostty-terminfo" { } ''
mkdir -p $out/share/terminfo
cat > ghostty.info <<'EOF'
@@ -101,5 +105,7 @@ let
'';
in
{
environment.systemPackages = [ ghostty-terminfo ];
config = mkIf cfg.enable {
environment.systemPackages = [ ghostty-terminfo ];
};
}

View File

@@ -11,21 +11,53 @@
...
}:
lib.mkMerge [
(import ./programs.nix {
inherit
config
lib
pkgs
inputs
;
})
(import ./services.nix {
inherit
config
lib
pkgs
inputs
;
})
]
with lib;
let
cfg = config.athenix.sw.headless;
in
{
options.athenix.sw.headless = mkOption {
type = lib.types.submodule {
options = {
enable = mkOption {
type = lib.types.bool;
default = false;
description = ''
Enable minimal headless server configuration.
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
;
})
]);
}

View File

@@ -2,7 +2,6 @@
config,
lib,
pkgs,
inputs,
...
}:

View File

@@ -1,7 +1,4 @@
{
config,
lib,
pkgs,
...
}:

View File

@@ -18,10 +18,27 @@ let
cfg = config.athenix.sw.python;
in
{
options.athenix.sw.python = {
enable = mkEnableOption "Python development tools (pixi, uv)" // {
default = true;
options.athenix.sw.python = lib.mkOption {
type = lib.types.submodule {
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 {

230
sw/secrets.nix Normal file
View 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}/";
};
}

View File

@@ -7,37 +7,83 @@
inputs,
...
}:
lib.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
;
})
]
with lib;
let
cfg = config.athenix.sw.stateless-kiosk;
in
{
options.athenix.sw.stateless-kiosk = mkOption {
type = lib.types.submodule {
options = {
enable = mkOption {
type = lib.types.bool;
default = false;
description = ''
Enable stateless kiosk mode for diskless PXE boot systems.
Includes:
- Sway (Wayland compositor)
- Chromium in fullscreen kiosk mode
- MAC address-based URL routing
- Network-only boot (no local storage)
- Auto-start browser on boot
Recommended for: Assembly line stations, diskless kiosks, PXE boot displays
'';
example = true;
};
kioskUrl = mkOption {
type = lib.types.str;
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
;
})
]);
}

View File

@@ -1,14 +1,13 @@
# 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.
{
config,
lib,
pkgs,
inputs,
...
}:
let
macCaseBuilder = (import ./mac-hostmap.nix { inherit lib; }).macCaseBuilder;
macCaseBuilder = inputs.self.lib.macCaseBuilder;
macCases = macCaseBuilder {
varName = "STATION";
};

View File

@@ -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;
}

View File

@@ -26,5 +26,5 @@
};
# Disable systemd-networkd and systemd-hostnamed
systemd.network.enable = false;
systemd.network.enable = lib.mkForce false;
}

View File

@@ -1,7 +1,4 @@
{
config,
lib,
pkgs,
...
}:
{

View File

@@ -1,11 +1,10 @@
{
config,
lib,
pkgs,
inputs,
...
}:
let
macCaseBuilder = (import ./mac-hostmap.nix { inherit lib; }).macCaseBuilder;
macCaseBuilder = inputs.self.lib.macCaseBuilder;
shellCases = macCaseBuilder {
varName = "NEW_HOST";
prefix = "nix-station";

View File

@@ -5,29 +5,74 @@
inputs,
...
}:
lib.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
;
})
]
with lib;
let
cfg = config.athenix.sw.tablet-kiosk;
in
{
options.athenix.sw.tablet-kiosk = mkOption {
type = lib.types.submodule {
options = {
enable = mkOption {
type = lib.types.bool;
default = false;
description = ''
Enable tablet kiosk mode with touch-optimized interface.
Includes:
- Phosh mobile desktop environment
- Chromium in fullscreen kiosk mode
- On-screen keyboard (Squeekboard)
- Auto-login and auto-start browser
- Touch gesture support
- 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
;
})
]);
}

View File

@@ -155,7 +155,7 @@
--noerrdialogs \
--disable-session-crashed-bubble \
--disable-infobars \
${config.athenix.sw.kioskUrl}
${config.athenix.sw.tablet-kiosk.kioskUrl}
'';
};
};

View File

@@ -1,7 +1,6 @@
{
pkgs,
config,
osConfig,
lib,
...
}:
@@ -33,6 +32,18 @@ in
programs.zsh = {
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
historySubstringSearch = {
enable = true;

View File

@@ -1,487 +1,524 @@
{ pkgs, ... }:
{
environment.systemPackages = with pkgs; [
python3
git
(pkgs.writeShellScriptBin "update-ref" ''
set -euo pipefail
config,
lib,
pkgs,
...
}:
RED='\033[31m'; YEL='\033[33m'; NC='\033[0m'
die() { printf "''${RED}error:''${NC} %s\n" "$*" >&2; exit 2; }
warn() { printf "''${YEL}warning:''${NC} %s\n" "$*" >&2; }
with lib;
usage() {
cat >&2 <<'EOF'
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]
user=<username> | system=<device-type>:<hostkey>
EOF
exit 2
}
let
cfg = config.athenix.sw;
in
{
config = mkIf cfg.enable {
environment.systemPackages = with pkgs; [
python3
git
(pkgs.writeShellScriptBin "update-ref" ''
set -euo pipefail
# --- must be in a git repo (current dir) ---
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || die "This directory is not a git project"
CUR_REPO_ROOT="$(git rev-parse --show-toplevel)"
CUR_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
RED='\033[31m'; YEL='\033[33m'; NC='\033[0m'
die() { printf "''${RED}error:''${NC} %s\n" "$*" >&2; exit 2; }
warn() { printf "''${YEL}warning:''${NC} %s\n" "$*" >&2; }
# --- athenix checkout (working tree) ---
ATHENIX_DIR="$HOME/athenix"
ATHENIX_BRANCH=""
usage() {
cat >&2 <<'EOF'
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 ---
COMMIT_MSG=""
PUSH_SPEC=""
# --- must be in a git repo (current dir) ---
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || die "This directory is not a git project"
CUR_REPO_ROOT="$(git rev-parse --show-toplevel)"
CUR_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
# --- push / url mode ---
PUSH_SET=0
DO_PUSH=0
MODE_FORCE="" # "", local, remote
# --- athenix checkout (working tree) ---
ATHENIX_DIR="$HOME/athenix"
ATHENIX_BRANCH=""
TARGET=""
# --- current repo automation ---
COMMIT_MSG=""
PUSH_SPEC=""
is_remote_url() {
# https://, http://, ssh://, or scp-style git@host:org/repo
printf "%s" "$1" | grep -qE '^(https?|ssh)://|^[^/@:]+@[^/:]+:'
}
# --- push / url mode ---
PUSH_SET=0
DO_PUSH=0
MODE_FORCE="" # "", local, remote
derive_full_hostname() {
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
}
TARGET=""
extract_existing_fetch_url() {
# args: mode file username key
python3 - "$1" "$2" "$3" "$4" <<'PY'
import sys, re, pathlib
mode, file, username, key = sys.argv[1:5]
t = pathlib.Path(file).read_text()
is_remote_url() {
# https://, http://, ssh://, or scp-style git@host:org/repo
printf "%s" "$1" | grep -qE '^(https?|ssh)://|^[^/@:]+@[^/:]+:'
}
def url_from_block(block: str) -> str:
if not block:
return ""
m = re.search(r'url\s*=\s*"([^"]+)"\s*;', block)
return m.group(1) if m else ""
derive_full_hostname() {
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
}
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
}
extract_existing_fetch_url() {
# args: mode file username key
python3 - "$1" "$2" "$3" "$4" "$5"<<'PY'
import sys, re, pathlib
mode, file, username, key, use_ssh = sys.argv[1:5]
t = pathlib.Path(file).read_text()
# --- 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
;;
def url_from_block(block: str) -> str:
if not block:
return ""
m = re.search(r'url\s*=\s*"([^"]+)"\s*;', block)
url = m.group(1) if m else ""
-p|--push)
PUSH_SET=1
DO_PUSH=1
PUSH_SPEC=""
if use_ssh = "true":
return url
# 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"
# Already https
if url.startswith("https://"):
return url
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
# ssh://git@host/org/repo.git
m = re.match(r"ssh://(?:.+?)@([^/]+)/(.+)", url)
if m:
host, path = m.groups()
return f"https://{host}/{path}"
# git@host:org/repo.git
m = re.match(r"(?:.+?)@([^:]+):(.+)", url)
if m:
host, path = m.groups()
return f"https://{host}/{path}"
# If you gave me something cursed
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
# unknown token; treat as not-a-push-spec and don't consume it
shift
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
;;
--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
;;
-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
# --- target file + identifiers ---
MODE=""; FILE=""; USERNAME=""; DEVTYPE=""; HOSTKEY=""
case "$TARGET" in
user=*)
MODE="user"
USERNAME="''${TARGET#user=}"
[ -n "$USERNAME" ] || die "user=<username>: username missing"
FILE="$ATHENIX_DIR/users.nix"
;;
system=*)
MODE="system"
RHS="''${TARGET#system=}"
printf "%s" "$RHS" | grep -q ':' || die "system=... must be system=<device-type>:<hostkey>"
DEVTYPE="''${RHS%%:*}"
HOSTKEY="''${RHS#*:}"
[ -n "$DEVTYPE" ] || die "system=<device-type>:<hostkey>: device-type missing"
[ -n "$HOSTKEY" ] || die "system=<device-type>:<hostkey>: hostkey missing"
FILE="$ATHENIX_DIR/inventory.nix"
;;
esac
[ -f "$FILE" ] || die "File not found: $FILE"
# --- target file + identifiers ---
MODE=""; FILE=""; USERNAME=""; DEVTYPE=""; HOSTKEY=""
case "$TARGET" in
user=*)
MODE="user"
USERNAME="''${TARGET#user=}"
[ -n "$USERNAME" ] || die "user=<username>: username missing"
FILE="$ATHENIX_DIR/users.nix"
;;
system=*)
MODE="system"
RHS="''${TARGET#system=}"
printf "%s" "$RHS" | grep -q ':' || die "system=... must be system=<device-type>:<hostkey>"
DEVTYPE="''${RHS%%:*}"
HOSTKEY="''${RHS#*:}"
[ -n "$DEVTYPE" ] || die "system=<device-type>:<hostkey>: device-type missing"
[ -n "$HOSTKEY" ] || die "system=<device-type>:<hostkey>: hostkey missing"
FILE="$ATHENIX_DIR/inventory.nix"
;;
esac
[ -f "$FILE" ] || die "File not found: $FILE"
# --- push default based on existing entry url in the target file ---
EXISTING_URL=""
ENTRY_EXISTS=0
if [ "$MODE" = "user" ]; then
EXISTING_URL="$(extract_existing_fetch_url user "$FILE" "$USERNAME" "")"
[ -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")"
# --- push default based on existing entry url in the target file ---
EXISTING_URL=""
ENTRY_EXISTS=0
if [ "$MODE" = "user" ]; then
EXISTING_URL="$(extract_existing_fetch_url user "$FILE" "$USERNAME" "" "false")"
[ -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
DO_PUSH=0
[ "$MODE_FORCE" = "remote" ] && DO_PUSH=1 || true
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
fi
fi
fi
if [ "$MODE_FORCE" = "local" ] && [ "$PUSH_SET" -eq 0 ]; then
DO_PUSH=0
fi
# --- 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
# --- 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"
if [ "$PUSH_SET" -eq 0 ]; then
if [ "$ENTRY_EXISTS" -eq 1 ] && is_remote_url "$EXISTING_URL"; then
DO_PUSH=1
else
REM_NAME="$PUSH_SPEC"
git push -u "$REM_NAME" "$CUR_BRANCH"
PUSH_REMOTE_URL="$(git remote get-url "$REM_NAME")"
DO_PUSH=0
[ "$MODE_FORCE" = "remote" ] && DO_PUSH=1 || true
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
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 ---
if [ "$MODE_FORCE" = "local" ]; then
FETCH_URL="file://$CUR_REPO_ROOT"
elif [ "$MODE_FORCE" = "remote" ]; then
# --- push current repo if requested ---
PUSH_REMOTE_URL=""
if [ "$DO_PUSH" -eq 1 ]; then
FETCH_URL="$PUSH_REMOTE_URL"
elif [ "$ENTRY_EXISTS" -eq 1 ] && [ -n "$EXISTING_URL" ] && is_remote_url "$EXISTING_URL"; then
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"
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
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
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 ---
python3 - "$MODE" "$FILE" "$FETCH_URL" "$CUR_REV" "$USERNAME" "$DEVTYPE" "$HOSTKEY" <<'PY'
import sys, re, pathlib
CUR_REV="$(git -C "$CUR_REPO_ROOT" rev-parse HEAD)"
mode = sys.argv[1]
path = pathlib.Path(sys.argv[2])
fetch_url = sys.argv[3]
rev = sys.argv[4]
username = sys.argv[5]
devtype = sys.argv[6]
hostkey = sys.argv[7]
text = path.read_text()
# --- choose URL to write into fetchGit ---
if [ "$MODE_FORCE" = "local" ]; then
FETCH_URL="file://$CUR_REPO_ROOT"
elif [ "$MODE_FORCE" = "remote" ]; then
if [ "$DO_PUSH" -eq 1 ]; then
FETCH_URL="$PUSH_REMOTE_URL"
elif [ "$ENTRY_EXISTS" -eq 1 ] && [ -n "$EXISTING_URL" ] && is_remote_url "$EXISTING_URL"; then
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:
depth = 0
i = start
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 '}'")
# --- rewrite users.nix or inventory.nix ---
python3 - "$MODE" "$FILE" "$FETCH_URL" "$CUR_REV" "$USERNAME" "$DEVTYPE" "$HOSTKEY" <<'PY'
import sys, re, pathlib
def mk_fetch(entry_indent: str) -> str:
# entry_indent is indentation for the whole `"key" = <here>;` line.
# The attrset contents should be indented one level deeper.
inner = entry_indent + " "
return (
'builtins.fetchGit {\n'
f'{inner}url = "{fetch_url}";\n'
f'{inner}rev = "{rev}";\n'
f'{inner}submodules = true;\n'
f'{entry_indent}}}'
)
mode = sys.argv[1]
path = pathlib.Path(sys.argv[2])
fetch_url = sys.argv[3]
rev = sys.argv[4]
username = sys.argv[5]
devtype = sys.argv[6]
hostkey = sys.argv[7]
text = path.read_text()
def full_hostname(devtype: str, hostkey: str) -> str:
if hostkey.startswith(devtype) or "-" in hostkey:
return hostkey
if hostkey.isdigit():
return f"{devtype}{hostkey}"
return f"{devtype}-{hostkey}"
def find_matching_brace(s: str, start: int) -> int:
depth = 0
i = start
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 update_user(t: str) -> str:
mblock = re.search(r"(?s)athenix\.users\s*=\s*\{(.*?)\n\s*\};", t)
if not mblock:
raise SystemExit("error: could not locate `athenix.users = { ... };` block")
# 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)
entry_re = re.search(
r"(?s)(\n[ \t]*" + re.escape(username) + r"\.external\s*=\s*)builtins\.fetchGit\s*\{",
body
def mk_fetch(entry_indent: str) -> str:
# entry_indent is indentation for the whole `"key" = <here>;` line.
# The attrset contents should be indented one level deeper.
inner = entry_indent + " "
return (
'builtins.fetchGit {\n'
f'{inner}url = "{fetch_url}";\n'
f'{inner}rev = "{rev}";\n'
f'{inner}submodules = true;\n'
f'{entry_indent}}}'
)
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
indent = re.match(r"[ \t]*", body[line_start:entry_re.start()]).group(0)
def full_hostname(devtype: str, hostkey: str) -> str:
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:]
else:
indent = " "
new_body = body + f"\n{indent}{username}.external = {mk_fetch(indent)};\n"
def update_user(t: str) -> str:
mblock = re.search(r"(?s)athenix\.users\s*=\s*\{(.*?)\n\s*\};", t)
if not mblock:
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:
# 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
)
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:])
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 in devices")
raise SystemExit("error: expected ';' after fetchGit attrset")
semi_end = end + 1 + semi.end()
# Reconstruct the prefix: newline + indent + "key" =
prefix = f'\n{entry_indent}"{key}" = '
line_start = body.rfind("\n", 0, entry_re.start()) + 1
indent = re.match(r"[ \t]*", body[line_start:entry_re.start()]).group(0)
new_devices = (
devices[:entry.start()]
+ prefix
+ mk_fetch(entry_indent)
+ ";"
+ devices[semi_end:]
new_body = body[:entry_re.start()] + entry_re.group(1) + mk_fetch(indent) + ";" + body[semi_end:]
else:
indent = " "
new_body = body + f"\n{indent}{username}.external = {mk_fetch(indent)};\n"
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)
# Indent for new entries: take indent of the closing '}' of devices, add 2 spaces.
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 + " "
semi = re.match(r"\s*;", devices[end+1:])
if not semi:
raise SystemExit("error: expected ';' after fetchGit attrset in devices")
semi_end = end + 1 + semi.end()
insertion = f'\n{entry_indent}"{hostkey}" = {mk_fetch(entry_indent)};\n'
new_devices = devices[:-1].rstrip() + insertion + close_indent + "}"
new_dev = dev[:devices_open] + new_devices + dev[devices_close+1:]
return t[:dev_open] + new_dev + t[dev_close+1:]
# Reconstruct the prefix: newline + indent + "key" =
prefix = f'\n{entry_indent}"{key}" = '
if mode == "user":
out = update_user(text)
elif mode == "system":
out = update_system(text)
else:
raise SystemExit("error: unknown mode")
new_devices = (
devices[:entry.start()]
+ prefix
+ mk_fetch(entry_indent)
+ ";"
+ devices[semi_end:]
)
new_dev = dev[:devices_open] + new_devices + dev[devices_close+1:]
path.write_text(out)
PY
return t[:dev_open] + new_dev + t[dev_close+1:]
cd $ATHENIX_DIR
nix fmt **/*.nix
cd $CUR_REPO_ROOT
# Not found: append into devices (exact hostkey)
# Indent for new entries: take indent of the closing '}' of devices, add 2 spaces.
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
printf " url = %s\n" "$FETCH_URL" >&2
printf " rev = %s\n" "$CUR_REV" >&2
'')
];
insertion = f'\n{entry_indent}"{hostkey}" = {mk_fetch(entry_indent)};\n'
new_devices = devices[:-1].rstrip() + insertion + close_indent + "}"
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
'')
];
};
}

View File

@@ -9,27 +9,47 @@ with lib;
{
options.athenix.sw.remoteBuild = lib.mkOption {
type = types.submodule {
type = lib.types.submodule {
options = {
hosts = mkOption {
type = types.listOf types.str;
type = lib.types.listOf lib.types.str;
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 {
type = types.bool;
type = lib.types.bool;
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 = { };
description = "Remote build configuration";
description = "Remote build configuration for system updates.";
};
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 = [
(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";
path = with pkgs; [
git
openssh
nixos-rebuild
nix
coreutils
];
serviceConfig = {
Type = "oneshot";

View File

@@ -1,4 +1,4 @@
{ inputs, ... }:
{ ... }:
# ============================================================================
# User Configuration
@@ -15,7 +15,6 @@
# nixos-systems configuration (nixpkgs, home-manager, etc.).
{
config,
lib,
pkgs,
osConfig ? null, # Only available in home-manager context
@@ -60,7 +59,7 @@
fd
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
# ========== Programs ==========

View File

@@ -1,4 +1,4 @@
{ pkgs, ... }:
{ ... }:
{
# ============================================================================
# User Definitions
@@ -13,8 +13,9 @@
#
# External User Configuration:
# 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 = builtins.fetchGit { ... }; # legacy, still supported
#
# External repositories should contain:
# - 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.
# External module options take precedence over users.nix defaults.
athenix.users = {
config.athenix.users = {
root = {
isNormalUser = false;
hashedPassword = "!";
@@ -47,9 +48,10 @@
enable = true; # Default user, enabled everywhere
};
hdh20267 = {
external = builtins.fetchGit {
external = {
url = "https://git.factory.uga.edu/hdh20267/hdh20267-nix";
rev = "c538e0c0510045b58264627bb897fc499dc7c490";
rev = "dbdf65c7bd59e646719f724a3acd2330e0c922ec";
# submodules = false; # optional, defaults to false
};
};
sv22900 = {
@@ -58,7 +60,7 @@
"networkmanager"
"wheel"
];
shell = pkgs.zsh;
shell = "zsh";
# enable = false by default, set to true per-system
};
};