Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

← braid

NixOS configuration

Complete reference for the braid NixOS module options. Read this when setting up braid for the first time or tuning behavior after initial setup.

Minimal config

# flake.nix
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
    braid.url = "github:danneu/braid?ref=release";
  };

  outputs = { nixpkgs, braid, ... }: {
    nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        braid.nixosModules.default
        ./configuration.nix
      ];
    };
  };
}

?ref=release pins braid to its release channel: a moving branch the release fast-forwards to each tag. nix flake update braid is then the “upgrade to the newest release” button, and flake.lock still pins the exact rev. The snippet deliberately omits braid.inputs.nixpkgs.follows – see Binary cache and Tool overrides for why no-follows is the default.

# configuration.nix
braid = {
  enable = true;
};

nixosModules.default supplies braid.package automatically. Override it only to build the CLI yourself.

Binary cache

braid publishes a prebuilt x86_64-linux CLI to a public Cachix cache on every release. Add it so the NAS pulls the binary instead of recompiling Rust:

# configuration.nix
nix.settings = {
  extra-substituters = [ "https://braid.cachix.org" ];
  extra-trusted-public-keys = [ "braid.cachix.org-1:I/p7fx1z5n0+O80KzMuT7aXRdkVyHr/buZKaBu7HvJs=" ];
};

The cache only hits when braid resolves to the exact store path CI built – that is, with the recommended no-follows setup above. Setting braid.inputs.nixpkgs.follows rebuilds braid against your nixpkgs, producing a different path and a cache miss. See Toolchain pinning and ADR 029.

What you get for free

When braid.enable = true, the module sets up:

  • Monthly btrfs scrub – timer + service tied to pool lifecycle. Configurable via braid.autoScrub.
  • Resilient boot – a dead drive never blocks boot. LUKS open and btrfs mount are deferred to braid unlock, not wired into boot.initrd.
  • Pinned toolchain – btrfs-progs, cryptsetup, util-linux, NUT, smartmontools, and ethtool are pinned to NixOS stable versions. Override with braid.packages.* if needed.
  • Shell completions – bash, zsh, and fish completions registered automatically via clap_complete.
  • smartd integrationservices.smartd enabled by default with a braid-owned alert script. SMART failures trigger the braid alert service.
  • Storage group – a storage group is created; mount point is set to root:storage 2770 after unlock. See Sharing and permissions.
  • Disk health monitoring – polls btrfs device stats every 5 minutes, audible beep on errors. Configurable via braid.monitor.
  • Fan control (opt-in) – drive chassis fans from the hottest SATA drive temp. Handles hddfancontrol, SATA hotswap restart, crash recovery. Configurable via braid.fanControl.

Module options

Core

OptionTypeDefaultDescription
braid.enableboolfalseEnable the braid module
braid.packagepackage or nullnullThe braid CLI package; nixosModules.default defaults it to braid-cli-unwrapped
braid.mountPointpath/mnt/storageWhere to mount the btrfs pool
braid.poolAccessGroupstring or null"storage"Group for mount point access. null to disable
braid.lockSystemdStopDeadlineSecspositive int270Seconds to wait for the pool lock during braid-online.service ExecStop; must stay below the unit’s TimeoutStopSec

Tool overrides

OptionTypeDefaultDescription
braid.packages.cryptsetuppackagepkgs.cryptsetupcryptsetup package
braid.packages.btrfsProgspackagepkgs.btrfs-progsbtrfs-progs package
braid.packages.utilLinuxpackagepkgs.util-linuxutil-linux package
braid.packages.nutpackagepkgs.nutNUT package
braid.packages.smartmontoolspackagepkgs.smartmontoolssmartmontools package
braid.packages.ethtoolpackagepkgs.ethtoolethtool package

Override these only if you need a specific version for compatibility testing. The recommended setup omits braid.inputs.nixpkgs.follows (the Minimal config example above), so nixosModules.default sources these defaults from braid’s own pinned nixpkgs (nixos-26.05) – the same versions the release binary cache is built against, so braid is a cache hit. Adding braid.inputs.nixpkgs.follows = "nixpkgs" is an advanced opt-out: it dedups your closure but rebuilds braid against your nixpkgs (a cache miss) and sources these tools from your nixpkgs instead. If you take it, keep your nixpkgs on the same NixOS stable release braid targets so the parsed tool output stays compatible – see Toolchain pinning.

Auto-scrub

OptionTypeDefaultDescription
braid.autoScrub.enablebooltrueEnable periodic btrfs scrub
braid.autoScrub.intervalstring"monthly"systemd calendar expression

The scrub timer is lifecycle-aware: it starts when the pool comes online and stops when the pool goes offline. Persistent = true ensures a missed scrub runs on next unlock (e.g. the pool was locked over a monthly boundary).

braid’s scrub conflicts with the NixOS built-in services.btrfs.autoScrub. If both are enabled, evaluation fails with a clear error. Disable one or the other.

Monitoring

OptionTypeDefaultDescription
braid.monitor.enablebooltrueEnable disk health monitoring
braid.monitor.intervalstring"5min"Polling interval (systemd time span)
braid.monitor.beepbooltrueAudible PC speaker beep on alert
braid.monitor.alertCommandstring or nullnullCustom command to run on alert

When beep = true, the module unblacklists the pcspkr kernel module, creates a beep group, and sets up a udev rule for PC speaker access. The beep loops with exponential backoff (5s, 10s, 20s, 40s, …) capped at once every 15 minutes, until acknowledged with braid ack.

alertCommand runs in addition to the beep (not instead of). Use it for push notifications, email, etc.:

braid.monitor.alertCommand = "curl -s -d 'Disk error on NAS' https://ntfy.sh/my-nas-alerts";

See Monitoring and alerts for the full workflow.

Auto-unlock

OptionTypeDefaultDescription
braid.autoUnlock.enableboolfalseEnable USB keyfile auto-unlock
braid.autoUnlock.keyDevicestring""Block device path (/dev/disk/by-id/...)
braid.autoUnlock.timeoutSecpositive int5Seconds to wait for USB device
braid.autoUnlock.allowDegradedboolfalseMount with missing devices

keyDevice must use a /dev/disk/by-id/ path – /dev/sdX names shift when devices are added or removed.

The auto-unlock service mounts the USB read-only, reads braid.key, unlocks the pool, and unmounts the USB immediately. The keyfile is never left accessible. If the USB is absent at boot, the service exits cleanly without blocking boot.

See Auto-unlock for the enrollment and setup workflow.

Auto-suspend

OptionTypeDefaultDescription
braid.autoSuspend.enableboolfalseSuspend NAS when idle
braid.autoSuspend.wolInterfacestring or nullnullNetwork interface for Wake-on-LAN (required)
braid.autoSuspend.idleTimepositive int900Seconds idle before suspend

Requires a wired ethernet interface – WiFi interfaces are rejected at evaluation time (WoL needs ethtool, which does not work for WiFi).

Activity checks that block suspend:

  • braid idle – scrub or any btrfs kernel exclusive operation (balance, device add/remove/replace, resize, swap activate)
  • Active SSH sessions
  • Active local sessions (TTY/X11/Wayland)
  • SMB connections (auto-detected if services.samba is enabled)
  • NFS connections (auto-detected if services.nfs.server is enabled)

The scrub timer is registered as a wakeup source so the NAS wakes for scheduled scrubs.

See Power management for the full workflow.

Fan control

OptionTypeDefaultDescription
braid.fanControl.enableboolfalseDrive chassis fans from HDD temps
braid.fanControl.pwm.platformDevicestring(required)Platform device name under /sys/devices/platform/
braid.fanControl.pwm.numberint(required)PWM channel number (1-based)
braid.fanControl.pwm.minStartint(required)Minimum PWM to start fan from standstill
braid.fanControl.pwm.maxStopint(required)PWM below which a spinning fan stalls
braid.fanControl.minTempint30Temperature (C) at which fan runs at minimum speed
braid.fanControl.maxTempint40Temperature (C) at which fan runs at full speed
braid.fanControl.minFanSpeedPercentint20Minimum fan speed % (0 = fan may stop)
braid.fanControl.intervalstring"30s"Temperature polling interval

pwm.platformDevice and pwm.number are found via pwmconfig. pwm.minStart and pwm.maxStop are measured with hddfancontrol pwm-test -p <pwm-path>. All four are hardware-specific.

Monitors all visible SATA devices (not only braid pool members). Requires a board-specific Super I/O driver in boot.kernelModules – see Fan control for the hardware discovery workflow.

UPS

OptionTypeDefaultDescription
braid.ups.enableboolfalseEnable UPS support via NUT (single-host standalone)
braid.ups.namestring"ups"UPS identifier for upsd/upsc
braid.ups.driverstring"usbhid-ups"NUT driver; the USB default covers most home-NAS UPSes
braid.ups.portstring"auto"Driver port; auto finds the first matching USB UPS

When enabled, NUT triggers an orderly poweroff on low battery (unwinding braid-online.service -> braid lock -> unmount) and pool-mutating commands (add/remove/remove-missing/replace) refuse to start unless the UPS reports verified utility power (OL). Only name is written to /etc/braid/config.json, so braid ups status and the TUI know which UPS to query; driver and port configure the NUT driver only. Non-USB drivers (apcsmart, snmp-ups) are an escape hatch and not first-class.

See UPS for the setup workflow and live status.

Full config example

Every option with its default (or a representative value for required/optional fields):

braid = {
  enable = true;
  # package -- defaults to nixosModules.default's pinned braid-cli-unwrapped;
  # set only to build the CLI yourself.
  mountPoint = "/mnt/storage";   # default
  poolAccessGroup = "storage";   # default; null to disable
  lockSystemdStopDeadlineSecs = 270;  # default; must stay below braid-online TimeoutStopSec

  # Tool version overrides -- the recommended setup omits nixpkgs `follows`, so
  # defaults come from braid's pinned nixos-26.05 (cache hit); `follows` is an
  # opt-out that tracks your nixpkgs but rebuilds braid (cache miss). See "Tool overrides".
  # packages.cryptsetup = pkgs.cryptsetup;
  # packages.btrfsProgs = pkgs.btrfs-progs;
  # packages.utilLinux = pkgs.util-linux;
  # packages.nut = pkgs.nut;
  # packages.smartmontools = pkgs.smartmontools;
  # packages.ethtool = pkgs.ethtool;

  autoScrub = {
    enable = true;       # default
    interval = "monthly"; # default; any systemd calendar expression
  };

  monitor = {
    enable = true;       # default
    interval = "5min";   # default
    beep = true;         # default
    alertCommand = null; # default; e.g. "curl -s -d 'alert' https://ntfy.sh/my-nas"
  };

  autoUnlock = {
    enable = false;  # default
    keyDevice = "/dev/disk/by-id/usb-Kingston_DataTraveler_XXXX-0:0";
    timeoutSec = 5;  # default
    allowDegraded = false; # default
  };

  autoSuspend = {
    enable = false;   # default
    wolInterface = "eno1";
    idleTime = 900;   # default (15 minutes)
  };

  fanControl = {
    enable = false;    # default; opt-in
    pwm = {
      platformDevice = "f71882fg.656";  # from pwmconfig (required)
      number = 2;                        # from pwmconfig (required)
      minStart = 65;     # from hddfancontrol pwm-test (required)
      maxStop = 60;      # from hddfancontrol pwm-test (required)
    };
    minTemp = 30;      # default
    maxTemp = 40;      # default
    minFanSpeedPercent = 20;  # default
    interval = "30s";  # default
  };

  ups = {
    enable = false;        # default; opt-in
    name = "ups";          # default
    driver = "usbhid-ups"; # default
    port = "auto";         # default
  };
};