Expose the fips daemon as a managed NixOS service so flake consumers
can enable it with a single line instead of hand-rolling a systemd unit.
Flake outputs (system-independent, outside eachDefaultSystem):
- overlays.default — adds pkgs.fips
- nixosModules.default — packaging/nixos/ module providing services.fips.*
Module options (services.fips):
- enable (bool, default false) — main mesh daemon
- package (package, default pkgs.fips via overlay)
- configFile (path, default /share/fips/fips.yaml) — seed source
- openFirewall (bool, default true) — UDP 2121 + TCP 8443
- dns.enable (bool, default true) — route .fips to [::1]:5354 via
systemd-resolved (declarative,
no setup/teardown scripts)
- gateway.enable(bool, default false) — outbound LAN gateway service
Hybrid config pattern: fips.yaml + identity keys live in /var/lib/fips/
(writable, seeded on first run only); hosts/ACL files stay at /etc/fips/
because fips hardcodes those paths on Linux. Launched with --config so
fips never loads /etc/fips/fips.yaml by accident.
flake.nix: ship fips.yaml, hosts, and fips.nft via postInstall so the
module can reference them from /share/fips/ without the source tree.
Also fix deprecated stdenv.isLinux -> stdenv.hostPlatform.isLinux and
platforms.linux ++ darwin -> platforms.unix.
packaging/README.md: document the overlay + module and show a full
flake.nix consumer example.
NixOS Packaging
NixOS module and flake outputs for FIPS.
Quick Start (flake consumers)
Add fips as a flake input and enable the service:
# flake.nix
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
fips = {
url = "github:jmcorgan/fips";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, fips, ... }@inputs: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
specialArgs = { inherit inputs; };
modules = [
./configuration.nix
fips.nixosModules.default # ← import the module
{
services.fips.enable = true; # ← enable the daemon
}
];
};
};
}
Apply with:
sudo nixos-rebuild switch --flake .#myhost
Options
| Option | Type | Default | Description |
|---|---|---|---|
services.fips.enable |
bool | false |
Enable the FIPS mesh network daemon |
services.fips.package |
package | pkgs.fips |
The fips package (provided via overlay) |
services.fips.configFile |
path | shipped default | Default fips.yaml used to seed /var/lib/fips/fips.yaml on first run |
services.fips.openFirewall |
bool | true |
Open UDP 2121 + TCP 8443 |
services.fips.dns.enable |
bool | true |
Route .fips queries to the fips DNS responder via systemd-resolved |
services.fips.gateway.enable |
bool | false |
Enable the outbound LAN gateway (fips-gateway.service) |
What it does
- Installs the
fips,fipsctl,fipstop, andfips-gatewaybinaries - Creates a
fipsgroup (add your user for non-sudofipsctl/fipstop) - Seeds
/var/lib/fips/fips.yamland/etc/fips/hostsfrom the shipped defaults on first run only — operator edits are never clobbered - Runs
fipsas a systemd service (fips.service) asroot:fips(root is required for TUN interfaces and raw sockets) - Routes
.fipsDNS queries to the fips responder on[::1]:5354via systemd-resolved (enabled by default; disable withservices.fips.dns.enable = false) - Optionally runs the outbound LAN gateway (
fips-gateway.service) - Opens firewall ports for UDP (2121) and TCP (8443) transports
Config file layout (hybrid pattern)
FIPS uses a hybrid config pattern that balances declarative defaults with operator-editable runtime state:
| Path | Purpose | Writable | Seeded from |
|---|---|---|---|
/var/lib/fips/fips.yaml |
Main config | yes | services.fips.configFile (first run only) |
/var/lib/fips/fips.key |
Node identity (private) | yes | generated by fips on first start |
/var/lib/fips/fips.pub |
Node identity (public) | yes | generated by fips on first start |
/etc/fips/hosts |
Static hostname → npub map | yes | shipped hosts (first run only) |
/etc/fips/peers.allow |
Peer allowlist (ACL) | yes | operator-created |
/etc/fips/peers.deny |
Peer denylist (ACL) | yes | operator-created |
/etc/fips/fips.d/ |
nftables drop-in rules | yes | operator-created |
Why two locations?
/var/lib/fips/holds the main config and identity keys. This survives system rebuilds and reboots. fips derives key paths from the config file's parent directory, sofips.key/fips.publand here automatically./etc/fips/holds the hosts file and ACL files. fips hardcodes these paths on Linux (DEFAULT_HOSTS_PATH,DEFAULT_PEERS_ALLOW_PATH,DEFAULT_PEERS_DENY_PATH), so they cannot be relocated.
Why not environment.etc?
NixOS environment.etc creates symlinks into the read-only Nix store. That
makes files immutable at runtime and would clobber operator edits on every
rebuild. The module instead seeds real writable files via a preStart
script that only runs when the target file is absent.
Why --config /var/lib/fips/fips.yaml?
fips has a config search path (./fips.yaml, ~/.config/fips/fips.yaml,
/etc/fips/fips.yaml). Passing --config explicitly bypasses that search
path entirely, so fips loads only the user-managed file and never
accidentally picks up a stale /etc/fips/fips.yaml.
Usage after install
sudo journalctl -u fips -f # follow logs
fipsctl show status # check status (needs fips group membership)
fipstop # live monitoring
To use fipsctl/fipstop without sudo, add your user to the fips group:
users.users.myuser.extraGroups = [ "fips" ];
Log out and back in for the group change to take effect.
Editing config at runtime
sudo nano /var/lib/fips/fips.yaml # edit main config
sudo nano /etc/fips/hosts # add static hostname mappings
sudo nano /etc/fips/peers.allow # add peers to the allowlist
sudo systemctl restart fips # apply changes
Files are group-writable (0664, owned by root:fips), so fips group
members can edit without sudo.
Files
packaging/nixos/
├── default.nix # NixOS module (services.fips.*)
└── README.md # this file
The flake also exposes:
overlays.default— addspkgs.fipsnixosModules.default— the NixOS module