Restart=on-failure with RestartSec=5 and systemd's default start limit (5 starts in 10 s) never trips: one start every 5 s or more stays under it, so a daemon whose start cannot succeed, such as one given an empty or invalid config, respawned every 5 s for as long as it was left, paying its full start-up cost each time. The fips and fips-gateway units in the tarball, .deb, AUR (which installs the .deb units) and NixOS module now set RestartSteps=5 and RestartMaxDelaySec=30. systemd grows the delay exponentially from RestartSec to the maximum over the steps, so successive automatic restarts wait 5 s, 7 s, 10 s, 15 s, 21 s and then 30 s each. The step count is systemd's restart counter, which a successful run does not clear: it is reset by a stop then start, a restart while the unit is running, or systemctl reset-failed, and not by a restart issued while the unit is waiting to restart. A unit that has backed off once therefore keeps the longer delay for later failures until one of those happens. The comments in the units say so. The units keep restarting without a limit: a start limit would leave a remote node down after a bad boot, and the mesh may be the only way to reach it. systemd before v254 ignores both keys with a warning and keeps the fixed 5 s delay. A packaging test checks that each tarball and .deb unit that restarts sets both keys, that the two copies of each unit agree on them, and that each NixOS service restarted on failure sets the same values as the unit of that name. Other supervisors are left as they are. fips-dns and fips-firewall are Type=oneshot units without Restart=. OpenWrt's procd init scripts, which stop respawning after more than 5 consecutive runs shorter than an hour, are changed separately to respawn without a limit. launchd throttles the macOS KeepAlive job by its default ThrottleInterval of 10 s. The Windows service is installed without recovery actions, so the service manager does not restart it, and the FreeBSD rc script runs daemon(8) without -r, so it does not restart either.
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 with persistent: true |
/var/lib/fips/fips.pub |
Node identity (public) | yes | written by fips on every 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, ~/.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