- Nix 79%
- Shell 19.2%
- Go Template 1.1%
- JavaScript 0.7%
| .githooks | ||
| docs | ||
| hosts-base | ||
| lib | ||
| modules | ||
| pkgs | ||
| scripts | ||
| tests/checks | ||
| .editorconfig | ||
| .gitignore | ||
| .pii-allow | ||
| flake.lock | ||
| flake.nix | ||
| README.md | ||
nixos-common
A shared NixOS library. Every host in the fleet — servers and workstations alike — pulls its building blocks from here, so a service is configured once and reused everywhere rather than copy-pasted between machines.
The library holds no configuration of its own. It offers mechanism; the repo that consumes it supplies the policy. That means you will not find a hostname, an IP address, a sops path, a brand name, or an organisation name anywhere in this repo — those all arrive as option values from the consumer. A pre-commit scan enforces it.
If you have just cloned this repo
scripts/install-hooks.sh # sets core.hooksPath = .githooks
.githooks/pre-commit is tracked and so arrives with the clone, but git
will not run it until core.hooksPath is set — and that is local config,
never cloned. Until you run this, commits to your clone go ungated and
the PII / secret scan silently does not happen. Verify with
git config --get core.hooksPath; it must print .githooks.
Consuming the library rather than working on it? You want the pre-commit gate in your own repo instead.
What's in here
Host-wide modules cover the things every machine needs: a baseline
of Nix daemon, garbage-collection and journald settings
(commonBase), the shared option surface
(commonOptions), declarative admin
accounts (commonAdmins), the unattended
deploy SSH user (commonDeployUser),
networking (commonNetworking), a
notification framework (commonNotify),
shell defaults (commonShell), scheduled
flake.lock refresh
(commonFlakeUpdater) and upstream
release watching (commonUpdateCheck).
Server modules are opt-in, one per service — reverse proxying,
backups, monitoring and dashboards, identity, databases, container
engines, and the individual applications that sit on top of them. Each
one has its own doc under docs/modules/server/.
Virtualisation modules turn a machine into a VM host: libvirt/QEMU-KVM with
UEFI and a software TPM (commonLibvirtHost),
an optional fullscreen guest console on its own monitor
(localConsole), USB storage handed to a guest
(commonVmUsbPassthrough), PCI
passthrough (commonVfio) and the guest network
bridge, with or without a VLAN split
(commonGuestBridge).
Nix helpers in lib/ are pure functions rather than
modules: a generic host builder (mkHost) plus
generators for Caddy vhosts, Gatus endpoints, Authentik blueprints,
database dumps and backup reports. See docs/lib/.
Host bases in hosts-base/ switch on a coherent set of these
modules for a common kind of machine — a VM host, a Windows build agent, a
workstation running guests, an install medium. A host imports one and overrides
what differs; every value a base chooses is mkDefault. They are starting points,
not deployable configurations. See docs/hosts-base.md.
Installer images come from commonInstaller —
sshd, mDNS and the install toolkit — built into a bootable ISO or aarch64 SD image
by mkInstallerImage.
Booting a host as a VM is mkVm plus
commonVmLayer: a host configuration is defined once
and the guest is a layer over it — sops, disko and per-interface networking stood
down, virtio and port forwards added — rather than a second configuration that
drifts. Useful for trying a change before it reaches hardware, and for the
integration tests that boot the real host module list.
Windows-VM tooling in scripts/win-vm/ builds a guest
image from a Windows ISO by unattended install and generates its libvirt domain
XML — exposed as runnable packages, so nix run <flake>#win-vm-build needs no
checkout. See docs/scripts/win-vm.md.
Shell helpers in scripts/ handle the parts that
cannot be declarative — bootstrapping a new host, deploying, sops key
management, backup pruning, and the hygiene scans that run on commit.
See docs/scripts/.
The pre-commit gate is a shim over scripts/pre-commit-checks.sh: a
consumer copies .githooks/pre-commit once, and every check added here
afterwards reaches it without re-copying anything. See
docs/scripts/pre-commit.md.
One Nix formatter for the whole fleet: the formatter output here is the
pinned nixfmt, and a consumer opts in with
formatter.x86_64-linux = inputs.common.formatter.x86_64-linux;. That single
line is also the switch — check-format.sh
skips any repo without it, so the pre-commit wiring is safe to land
everywhere first. Setup, and the alignment caveat, in
docs/nix-formatting.md.
Using the library from another flake
Reference the repo by its git URL in the consumer's flake.nix:
inputs.common.url = "git+ssh://git@forge.magnificent.nz/alister/nixos-common.git";
Modules are then picked à la carte, or in bulk via the server /
desktop bundles:
imports = [
inputs.common.nixosModules.commonBase
inputs.common.nixosModules.commonAdmins
inputs.common.nixosModules.server # bundle: caddy, backup, tailscale, …
];
Nothing turns on by itself. Each module gates on its own enable
option and takes its values from the consumer, so importing one is
safe and inert until you configure it. The bundles exist mainly so a
desktop host never sees the server option namespaces at all.
One pattern is worth knowing before you start: values a consumer needs
while computing its imports must be passed through specialArgs,
not _module.args, or module evaluation hits infinite recursion. The
reasoning is in docs/conventions.md.
Enabling the pre-commit gate in your repo
The PII / secret / brand-name scan that keeps identifying data out of
source is worth having in the consuming repo too, and it is not
nix-specific — git grep plus bash, so it works in any git repo. Run
the setup from the root of the repo you are adding it to, not from a
nixos-common checkout.
Two files get committed to your repo: scripts/common-boot.sh and
.githooks/pre-commit. The scan itself is not committed — the shim
fetches it into a gitignored scripts/.common/ cache on each commit, so
nixos-common stays the single source of truth and there is nothing to
re-sync. Full copy-in recipe, including the vendored variant for
machines that cannot see a nixos-common checkout, is in
docs/scripts/check-pii.md.
Then, on every fresh clone of your repo — yours and other people's:
git config core.hooksPath .githooks
core.hooksPath is local git config and is not cloned. The committed
.githooks/pre-commit arrives with the clone, but git ignores it until
that config is set, so an un-set-up clone commits with no gate at all
and the scan silently does not run. Git refuses by design to let a repo
configure its own hook execution — a clone must not be able to run code
on you — so this cannot be automated away, only made hard to forget.
Put the line in your repo's own README, and verify with
git config --get core.hooksPath.
scripts/install-hooks.sh is not needed here — it lives in
nixos-common, and the git config line above is the only part of what
it does that matters to a consumer.
Developing against a consumer
Point the input at a local checkout instead of the git URL:
inputs.common.url = "path:/path/to/nixos-common";
Or leave the consumer untouched and override for a single build:
nixos-rebuild build --flake . \
--override-input common path:/path/to/nixos-common \
--no-write-lock-file
To check the library still evaluates on its own:
nix flake check
The checks live in tests/checks/, one file each —
see docs/tests.md for what they cover and how to add
one. nix flake check only sees files git knows about, so git add a
new one before running it.
Note that a build against a local path picks up uncommitted work, so a
change that builds locally can still fail for consumers until it is
committed and their flake.lock is updated.
Finding your way around
docs/layout.md— the file tree, annotated, and where new files belong.docs/conventions.md— house rules: file size budget, docs-per-file, the server/desktop split, mechanism-not-policy.docs/— one doc per module, helper and script, explaining what it does, how to wire it up, and its caveats. Source files carry only a short header pointing at their doc, so the doc is the place to read first.docs/nix-getting-started.md— a short primer if the Nix language itself is new to you.