Replace the <your-org> placeholders with the actual public URL (git+https://git.atitlan.io/aiolabs/omnixient, https so forkers need no SSH), and switch the packs standalone-import example from git+ssh to the same https URL. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
10 KiB
Packs — the core + opt-in goodies model
Omnixient is structured as a core that every host gets, plus opt-in packs you switch on for the goodies you want:
omni.packs.media.enable = true; # gimp, inkscape, mpv, obs, …
omni.packs.development.enable = true; # editors, LSPs, compilers, container CLIs
omni.packs.bitcoin.enable = true; # a real bitcoind + lightning node
The same pack definitions are exposed as reusable flake.nixosModules,
so a second Omnixient host (or someone else's NixOS entirely) can consume
them with a different subset — install the core, add the goodies you want.
TL;DR
- Core = base system + Hyprland desktop + theming (always on).
- Packs = opt-in bundles of packages/services/config under
omni.packs.<name>.enable.lnbits/aiolabs/bitcoinare standalone-importable — usable on a non-Omnixient NixOS viainputs.omni.nixosModules.<pack>.- The legacy
omni.preset/omni.features.*knobs still work — they're now a friendly front-end that drives packs (see Compatibility bridge).
What's core vs. what's a pack
| Layer | Contents | Toggle |
|---|---|---|
| Core (always) | base packages + fonts, Hyprland desktop, theming/colors, walker/menus, boot, security, services, hardware, users, secrets, nh, MCP |
— |
| Packs (opt-in) | see catalog below | omni.packs.<name>.enable |
Theming and the Hyprland desktop are deliberately core (Omnixient is a desktop distro; there's no headless target yet). If a headless/server target is ever needed, the desktop becomes its own pack — see Authoring a pack.
Pack catalog
| Pack | Provides | Standalone-importable? |
|---|---|---|
media |
players (mpv/vlc), image/video editing (gimp, inkscape, krita, kdenlive), screen capture, PDF, morph-py, openscad |
no (desktop bundle) |
development |
editors/IDEs, LSPs, debuggers, build tools, compilers, container CLIs, DB clients, cloud tooling, git/npm, dev manpages, and the richer modules/development.nix dev module |
no |
gaming |
Steam (+ firewall), Lutris, Wine, performance tools | no |
office |
browsers, comms (Signal/Element/Ferdium), office suites, notes, password managers, sync/backup | no |
containers |
Docker + Podman (unified daemon config) | no |
lnbits |
the LNbits multi-project dev environment (modules/dev-env: worktrees, regtest, tmux, pre-commit hook) |
yes |
aiolabs |
the aiolabs project list + deploy targets + tmux sessions, on top of lnbits |
yes |
bitcoin |
a real Bitcoin/Lightning node via nix-bitcoin (bitcoind + clightning) | yes (opt-in import) |
Each pack's enable carries a one-line description — discover them with:
nix eval .#nixosConfigurations.omni.options.omni.packs --apply builtins.attrNames
man configuration.nix # then search /omni.packs
Enabling packs
On an Omnixient host
Set them in your host config (hosts/<name>/default.nix) or
configuration.nix:
omni.packs = {
development.enable = true;
containers.enable = true;
media.enable = true;
};
gizmo (a laptop) and omni (the desktop) can enable different
subsets from the same definitions — that's the point.
Via presets / features (the friendly front-end)
The legacy knobs still work and now drive packs through the compatibility bridge:
omni.preset = "developer"; # → development, containers, media, office packs + theming
omni.features.media = true; # → omni.packs.media.enable
Use whichever you prefer; explicit omni.packs.*.enable always wins
over a preset/feature default.
Standalone — on a non-Omnixient NixOS (the reusable path)
lnbits, aiolabs, and bitcoin don't depend on Omnixient core. Add this
flake as an input and import just the pack you want:
# your own flake.nix
inputs.omni.url = "git+https://git.atitlan.io/aiolabs/omnixient";
# your host's modules
{
imports = [ inputs.omni.nixosModules.lnbits ];
omni.packs.lnbits.enable = true;
dev-env.user = "alice"; # configure via the standard dev-env.* options
dev-env.root = "/home/alice/dev";
}
You get the full LNbits dev environment without Hyprland, themes, or any
Omnixient host config. This is what makes the separate lnbits-sensei
scaffold obsolete — the dev-env is now a clean importable module.
nixosModules.bitcoinadditionally needsinputs.nix-bitcoinavailable in yourspecialArgs(Omnixient's own hosts get it vialib/mksystem.nix). See The bitcoin pack.
Compatibility bridge
modules/packs/compat.nix maps the legacy omni.features.* booleans
onto the new pack toggles with mkDefault:
omni.packs.media.enable = mkDefault (cfg.features.media or false);
omni.packs.development.enable = mkDefault (cfg.features.coding or false);
omni.packs.containers.enable = mkDefault (cfg.features.containers or false);
omni.packs.gaming.enable = mkDefault (cfg.features.gaming or false);
omni.packs.office.enable = mkDefault ((cfg.features.office or false) || (cfg.features.communication or false));
So the chain is preset → features → packs. mkDefault means an
explicit omni.packs.<x>.enable = true|false on a host always wins.
The bridge lives in the aggregator (loaded only with full Omnixient), so a
standalone consumer importing a single pack never pulls in the
omni.features dependency.
Non-pack features (customThemes, wallpaperEffects, virtualization,
backup) remain plain omni.features.* — they're core knobs, not
packs.
The bitcoin pack
omni.packs.bitcoin runs a real node via
nix-bitcoin:
{
imports = [ inputs.omni.nixosModules.bitcoin ]; # or hosts/<name> imports modules/packs/bitcoin.nix
omni.packs.bitcoin.enable = true;
omni.packs.bitcoin.operatorName = "padreug"; # account granted node CLI access
}
It imports only nix-bitcoin.nixosModules.default (NOT the
secure-node/hardened presets, which are server-oriented and hostile to
a daily-driver desktop), enables bitcoind + clightning, sets
nix-bitcoin.generateSecrets = true (auto-generates node secrets at
activation), and grants the operator CLI access.
Why it's not in the always-loaded aggregator: nix-bitcoin's default
module fires a secrets assertion merely from being imported — even with no
services enabled. Importing it inert on every host would break eval. So
bitcoin is not in modules/packs/default.nix; it's exposed as
nixosModules.bitcoin and a host opts in by importing it explicitly.
(inputs.nix-bitcoin is supplied to Omnixient hosts via lib/mksystem.nix's
specialArgs.)
Caveats (see also docs/system-map.md):
generateSecretsis the simplest path; for unified secret management, wire sops-nix into nix-bitcoin'ssecretsDir(secretsSetupMethod = "manual") — note LND has an open issue under manual secrets.- nix-bitcoin follows our
nixpkgs; it pins its own upstream for tested builds, so if a service fails to build after enabling, consider dropping thefollowsinflake.nix.
Authoring a new pack
-
Create
modules/packs/<name>.nixusing the canonical template:{ config, lib, pkgs, ... }: let cfg = config.omni.packs.<name>; in { options.omni.packs.<name>.enable = lib.mkEnableOption "<one-line description>"; config = lib.mkIf cfg.enable { # ONLY standard NixOS options + this pack's own bundled modules. environment.systemPackages = with pkgs; [ … ]; }; } -
Register it in
modules/packs/default.nix(the aggregator) so the option namespace always exists and the body is inert until enabled. Exception: if the pack imports a module with import-time side effects (assertions, globaldisabledModules— like nix-bitcoin), do not add it to the aggregator; expose it only as anixosModulesoutput for explicit per-host import. -
Expose it as a flake output in
flake.nix:nixosModules.<name> = import ./modules/packs/<name>.nix;. -
If it should be standalone-importable, the module must declare its own option namespace and touch only standard NixOS options + its own bundled modules — never read
config.omni.<core>. (This is whydev-envwas decoupled fromomni.*; thelnbitspack inherits that.) -
(Optional) add a bridge line in
modules/packs/compat.nixif a legacyomni.features.*flag should imply the new pack. -
Sub-options live alongside
enable, e.g.omni.packs.bitcoin.operatorName.
Internals & wiring
- Aggregator:
modules/packs/default.niximports every (inert) pack +compat.nix. - Wired in once:
lib/mksystem.niximports../modules/packs, so all hosts built throughmkSystemget the pack option namespace. - ISO stays pack-free: the live ISO (
iso.nix) is a directnixosSystemcall that bypassesmksystem, so it never loads packs. Any core/ISO-shared module that referencesconfig.omni.packs.*must guard withor false. - Reusable outputs:
nixosModules.{media,development,gaming,office, containers,lnbits,aiolabs,bitcoin}inflake.nix. - dev-env relationship: the
lnbitspackimports = [ ../dev-env ]and enables it; configure via the standarddev-env.*options (seemodules/dev-env/README.md).
Verifying a pack change
Pack edits should be closure-neutral unless you intend a behavior
change. Work in a worktree, build-only (never switch), and diff against
a baseline:
# baseline (once)
nix build .#nixosConfigurations.omni.config.system.build.toplevel -o ~/omni-baseline
# after your change
nix build .#nixosConfigurations.omni.config.system.build.toplevel -o ~/omni-after
nix store diff-closures ~/omni-baseline ~/omni-after # empty = behavior-identical
nix flake check --keep-going # schema + dev-env smoke + VM boot
For risky work on this live daily-driver, run Claude under
scripts/sandbox-claude.sh <worktree> — it hard-denies nixos-rebuild switch|test|boot, omni-rebuild, sudo, push, and network, leaving
build/eval/in-worktree-git allowed. You apply the real switch only once
the gates are green.