No description
  • Nix 79%
  • Shell 19.2%
  • Go Template 1.1%
  • JavaScript 0.7%
Find a file
2026-08-23 21:22:49 +12:00
.githooks nix formating checks and helpers, more checks by default e.g. sops 2026-08-05 21:25:38 +12:00
docs Beszel filesystem options and an auto add option 2026-08-23 21:22:49 +12:00
hosts-base Generic VM (Windows, etc) + tools , generic ISO and SD installers 2026-08-04 23:15:33 +12:00
lib backup report fixes, updated docs for homepage as well 2026-08-20 19:03:37 +12:00
modules Beszel filesystem options and an auto add option 2026-08-23 21:22:49 +12:00
pkgs refactor: optimize external prober by consolidating dual-request fetch into one and add token-gated API trigger 2026-08-12 21:56:26 +12:00
scripts pii-check hostname/fqdn checks with opt-in domain groups, narrowed ip slices, pii-allow reference 2026-08-22 15:47:24 +12:00
tests/checks caches: attic push transport, per-host push scoping, tilde key fix 2026-08-22 18:00:49 +12:00
.editorconfig nix formating checks and helpers, more checks by default e.g. sops 2026-08-05 21:25:38 +12:00
.gitignore nix formating checks and helpers, more checks by default e.g. sops 2026-08-05 21:25:38 +12:00
.pii-allow pii-check hostname/fqdn checks with opt-in domain groups, narrowed ip slices, pii-allow reference 2026-08-22 15:47:24 +12:00
flake.lock refactor: consolidate shell configuration into a modular structure and add interactive fzf history deletion for zsh, added prompt fallback for shells without fonts/modern terminals 2026-08-10 21:29:17 +12:00
flake.nix include generic cache & push module with tests 2026-08-22 12:32:40 +12:00
README.md pii-check hostname/fqdn checks with opt-in domain groups, narrowed ip slices, pii-allow reference 2026-08-22 15:47:24 +12:00

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.