Architecture¶
How a host is built¶
flake.nix exposes lib.mkHost:
mkHost = hostname: { hostPath ? ./hosts + "/${hostname}", extraModules ? [ ] }:
nixpkgs.lib.nixosSystem { ... };
Every host — GISNIX's own example, or one in a downstream flake — goes
through this one function. It wires in disko, agenix, home-manager, stylix,
the bundle-resolution module (profiles/bundles.nix), and the overlay set,
then imports hostPath (a directory containing at least default.nix and
config.nix).
Three specialArgs that make a host portable¶
A host's own files sit in hostPath, wherever that is. But a host's
default.nix also needs to reach things that live in GISNIX, not in its
own directory — shared profiles, the locale modules, the fleet registry.
Three specialArgs make that possible regardless of where hostPath is:
| specialArg | What it is | Used for |
|---|---|---|
hostPath |
The host's own directory | A profile that needs a per-host override file (e.g. cosmic-desktop.nix wanting desktop.nix) reaches it via hostPath + "/desktop.nix" rather than a ../hosts/${hostname}/... path that would resolve against the wrong repo. |
gisnixRoot |
This flake's own root (./.), as an absolute path |
A host's default.nix imports shared, non-bundle profiles with gisnixRoot + "/profiles/cosmic-desktop.nix" instead of ../../profiles/..., which only works when the host happens to live inside GISNIX's own tree. |
fleet |
The parsed hosts/fleet.nix |
Bundle-driven modules like fleet-hosts.nix (generates /etc/hosts for every known machine) take the registry as an argument instead of importing a hardcoded path — a downstream flake has its own fleet.nix, not GISNIX's. |
This is the fix that makes hostPath pointing outside GISNIX's own repo
actually work — see Building on GISNIX.
The bundle registry¶
Every bundle is a directory under software/ with a bundle.json
(name, path, description, implies, modules) beside the NixOS modules it
describes. profiles/bundles.nix reads a host's config.nix bundles
list, resolves implications transitively, and turns the result into module
imports. Nothing here is fleet-specific — the whole registry, and the
gisnix tooling that reads it (utils/lib/hostconfig.py, bundleinfo.py,
configure_tui.py), works the same whether the host lives in GISNIX
itself or one layer up, in a flake that pins GISNIX as an input.
Storage templates¶
templates/disko/*.nix are plain functions — { device, ... }: { disko.devices = ...; }
— not modules, so they can be called directly from a host's disks.nix:
{ gisnixRoot, ... }:
import (gisnixRoot + "/templates/disko/zfs-encrypted-single.nix") { device = "/dev/sda"; }
disks.nix itself has to be a module function (to receive gisnixRoot),
even though its body is just an import call — see
hosts/example/disks.nix for the exact shape.
The gisnix command manifest¶
Every operator-facing tool is a row in utils/commands.json plus a
utils/<name>.sh wrapper. mkCommandDrv in flake.nix turns one row into
three surfaces: a nix run .#<name> app, a gisnix <name> subcommand (via
gisnixDispatcher), and a dev-shell binary — one script, one dependency list,
no duplication. See the command reference for
every command that exists today, and
the GISNIX command pattern for
why the installer itself is wired this way rather than as a bespoke flake
app.