omnixient/docs/packs.md
Padreug a31369fea8 docs: point template + install docs at the real Omnixient repo
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>
2026-06-28 11:45:27 +02:00

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 / bitcoin are standalone-importable — usable on a non-Omnixient NixOS via inputs.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.bitcoin additionally needs inputs.nix-bitcoin available in your specialArgs (Omnixient's own hosts get it via lib/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):

  • generateSecrets is the simplest path; for unified secret management, wire sops-nix into nix-bitcoin's secretsDir (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 the follows in flake.nix.

Authoring a new pack

  1. Create modules/packs/<name>.nix using 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; [  ];
      };
    }
    
  2. 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, global disabledModules — like nix-bitcoin), do not add it to the aggregator; expose it only as a nixosModules output for explicit per-host import.

  3. Expose it as a flake output in flake.nix: nixosModules.<name> = import ./modules/packs/<name>.nix;.

  4. 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 why dev-env was decoupled from omni.*; the lnbits pack inherits that.)

  5. (Optional) add a bridge line in modules/packs/compat.nix if a legacy omni.features.* flag should imply the new pack.

  6. Sub-options live alongside enable, e.g. omni.packs.bitcoin.operatorName.

Internals & wiring

  • Aggregator: modules/packs/default.nix imports every (inert) pack + compat.nix.
  • Wired in once: lib/mksystem.nix imports ../modules/packs, so all hosts built through mkSystem get the pack option namespace.
  • ISO stays pack-free: the live ISO (iso.nix) is a direct nixosSystem call that bypasses mksystem, so it never loads packs. Any core/ISO-shared module that references config.omni.packs.* must guard with or false.
  • Reusable outputs: nixosModules.{media,development,gaming,office, containers,lnbits,aiolabs,bitcoin} in flake.nix.
  • dev-env relationship: the lnbits pack imports = [ ../dev-env ] and enables it; configure via the standard dev-env.* options (see modules/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.