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>
249 lines
10 KiB
Markdown
249 lines
10 KiB
Markdown
# 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:
|
|
|
|
```nix
|
|
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](#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](#authoring-a-new-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:
|
|
|
|
```sh
|
|
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`:
|
|
|
|
```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](#compatibility-bridge):
|
|
|
|
```nix
|
|
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:
|
|
|
|
```nix
|
|
# 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](#the-bitcoin-pack).
|
|
|
|
## Compatibility bridge
|
|
|
|
`modules/packs/compat.nix` maps the legacy `omni.features.*` booleans
|
|
onto the new pack toggles with `mkDefault`:
|
|
|
|
```nix
|
|
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](https://github.com/fort-nix/nix-bitcoin):
|
|
|
|
```nix
|
|
{
|
|
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:
|
|
|
|
```nix
|
|
{ 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:
|
|
|
|
```sh
|
|
# 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.
|