# Nomarchy — reusable system layer (NixOS 26.05). # # This module is the distro: import it from any host (see # nixosModules.nomarchy in flake.nix) and layer your machine specifics # (bootloader, hostname, users, hardware) on top. Host concerns are # deliberately NOT set here. Everything user-facing (Hyprland config, # Waybar, Ghostty, theming) lives in modules/home. { config, lib, pkgs, ... }: let cfg = config.nomarchy.system; # Reused by the greeter banner and MOTD so the brand name lives in one # place (system.nixos.distroName, set below). distroName = config.system.nixos.distroName; # The Nomarchy glyph font: a single-glyph face (the logo at U+F000) used # by the Waybar menu button in the summer themes. Pinned via # `font-family: Nomarchy` in their CSS, since Nerd Fonts also occupy # U+F000 (a glass icon) and would otherwise win. nomarchyLogoFont = pkgs.runCommandLocal "nomarchy-logo-font" { } '' install -Dm444 ${./branding/Nomarchy.ttf} $out/share/fonts/truetype/Nomarchy.ttf ''; in { imports = [ ./options.nix ./plymouth.nix ./greeter.nix ./file-manager.nix ./power.nix ./services.nix ./hardware.nix ./timezone.nix ./oom.nix ]; config = { # Distro branding. distroName flows into /etc/os-release PRETTY_NAME, # systemd-boot entry titles and the ISO boot-menu label; distroId is the # machine-readable ID (DEFAULT_HOSTNAME, lsb DISTRIB_ID, CPE name). # distroId = "nomarchy" makes os-release honest — ID=nomarchy with # ID_LIKE=nixos, the standard derivative-distro lineage marker (cf. # Ubuntu→debian) — and is safe: switch-to-configuration builds its # "is this NixOS?" guard from the *configured* distroId (and /etc/NIXOS # still exists as the fallback), so rebuilds keep working; the nixos-* # CLI tools are package names, untouched. The one side effect is that # isNixos goes false and blanks the upstream nixos.org URLs, so we # restore them pointing at the project instead. system.nixos.distroName = lib.mkDefault "Nomarchy"; system.nixos.distroId = lib.mkDefault "nomarchy"; system.nixos.extraOSReleaseArgs = lib.mkDefault { HOME_URL = "https://git.bemagri.xyz/bernardo/Nomarchy"; DOCUMENTATION_URL = "https://git.bemagri.xyz/bernardo/Nomarchy"; SUPPORT_URL = "https://git.bemagri.xyz/bernardo/Nomarchy"; BUG_REPORT_URL = "https://git.bemagri.xyz/bernardo/Nomarchy/issues"; }; # MOTD on TTY/SSH login (the desktop auto-logs into Hyprland, so this # is mostly seen over SSH or on a bare console). Branded, and doubles # as a cheat sheet for the distro's own helpers. Fingerprint line only # when fprintd is enabled (no permanent nag on machines without a # reader); doctor is always on PATH via systemPackages. users.motd = lib.mkDefault ('' ${distroName} — a NixOS desktop, themed from one JSON. nomarchy-pull update flake inputs (nomarchy, nixpkgs, …) nomarchy-rebuild rebuild the system (current lock) nomarchy-home rebuild the desktop / Home Manager nomarchy-theme-sync apply switch the whole palette nomarchy-doctor read-only health check SUPER+? keybindings cheatsheet SUPER+M → System › Firmware check LVFS firmware updates (fwupd) '' + lib.optionalString config.nomarchy.hardware.fingerprint.enable '' SUPER+M → System › Fingerprint enroll a finger (fprintd) ''); # Unfree allowed distro-wide: pragmatic-desktop territory (vendor # GPU/wifi drivers, firmware, fonts, …). The custom nixpkgs-config # type can't carry a nested mkDefault; disagree with # `nixpkgs.config = lib.mkForce { allowUnfree = false; }`. # `NIXPKGS_ALLOW_UNFREE` covers CLI `nix-shell` / `nix shell` / # `nix run` (those ignore the system `nixpkgs.config`); pure flake # eval still needs `--impure` when the env var is the only gate. nixpkgs.config.allowUnfree = true; environment.variables.NIXPKGS_ALLOW_UNFREE = "1"; # ── One keyboard layout everywhere, incl. the LUKS prompt ──────── # services.xserver.xkb.layout is the single source of truth for the # layout (the installer writes it; downstream sets it in system.nix). # These bridge that one knob through to the encrypted-disk prompt — # without them, setting only xkb.layout moves the graphical session # but leaves the console keymap unset, so the initrd LUKS passphrase # prompt falls back to US: # useXkbConfig the virtual-console keymap follows xkb (TTYs) # earlySetup bakes that keymap into the initrd # systemd initrd loads it before cryptsetup asks for the passphrase # (the legacy scripted initrd cannot; also what # Plymouth needs — defaulted here so it no longer # hinges on the splash being enabled). # All mkDefault, so an explicit console.keyMap or a scripted initrd # still wins. console.useXkbConfig = lib.mkDefault true; console.earlySetup = lib.mkDefault true; boot.initrd.systemd.enable = lib.mkDefault true; # Nomarchy roots are BTRFS, not ZFS, so adopt the 26.11 default early and # silence the eval warning the old `true` default emits. mkDefault, so a # genuine ZFS-root downstream can still force it back on. boot.zfs.forceImportRoot = lib.mkDefault false; # Magic SysRq Keys: safety net for Wayland lockups. Alt+SysRq+REISUB allows # safe reboot without data loss when the compositor hangs. boot.kernel.sysctl."kernel.sysrq" = lib.mkDefault 1; # ── Wayland session: Hyprland ──────────────────────────────────── # Installs the binary, registers the session, wires up # xdg-desktop-portal-hyprland. Configuration is Home Manager's job. programs.hyprland.enable = lib.mkDefault true; xdg.portal = { enable = lib.mkDefault true; extraPortals = [ pkgs.xdg-desktop-portal-gtk ]; # file pickers, etc. }; # The greetd/tuigreet login screen lives in ./greeter.nix — themed # from the state JSON (console.colors + --theme) at system rebuild. # ── Audio: Pipewire ────────────────────────────────────────────── security.rtkit.enable = lib.mkDefault cfg.audio.enable; services.pulseaudio.enable = lib.mkDefault false; services.pipewire = lib.mkIf cfg.audio.enable { enable = lib.mkDefault true; alsa.enable = true; alsa.support32Bit = true; pulse.enable = true; wireplumber.enable = true; }; # ── Desktop services ───────────────────────────────────────────── security.polkit.enable = lib.mkDefault true; services.gnome.gnome-keyring.enable = lib.mkDefault true; services.dbus.enable = lib.mkDefault true; services.upower.enable = lib.mkDefault true; # Hardware security key (FIDO2/U2F/GPG) support. Without pcscd, tokens like # YubiKeys often silently fail in WebAuthn (browsers), SSH, and GPG. services.pcscd.enable = lib.mkDefault true; # Firmware updates via LVFS. Ships the daemon + its metadata-refresh # timer only — it never flashes anything on its own; applying an update # is an explicit `fwupdmgr update`. On by default as desktop security # hygiene (BIOS/UEFI/Thunderbolt/SSD fixes land here); a clean no-op # where LVFS sees no devices (VMs/headless). Disable natively with # `services.fwupd.enable = false`. services.fwupd.enable = lib.mkDefault true; # Drive health monitoring (SMART). services.smartd = { enable = lib.mkDefault true; notifications.x11.enable = lib.mkDefault true; notifications.wall.enable = lib.mkDefault true; }; # Core security: enable AppArmor to confine desktop apps and services. security.apparmor.enable = true; security.apparmor.killUnconfinedConfinables = false; # Core stability: reboot automatically after 10s on a kernel panic # (prevents the system from hanging indefinitely on a black screen). boot.kernelParams = [ "panic=10" "oops=panic" ]; # Explicitly enable systembus-notify to resolve a mkDefault conflict # between earlyoom (true) and smartd (false). services.systembus-notify.enable = true; # Enable I2C and DDC/CI by default for external monitor brightness control. nomarchy.hardware.i2c.ddcci = lib.mkDefault true; networking.networkmanager.enable = lib.mkDefault true; # OpenVPN support for the VPN menu's import/connect flow (nomarchy-vpn). # WireGuard needs no plugin (NetworkManager imports wg .conf natively); # this adds the openvpn type so `nmcli connection import type openvpn` works. # mkDefault so a downstream can drop it to slim the closure. networking.networkmanager.plugins = lib.mkDefault [ pkgs.networkmanager-openvpn ]; # No double-unlock on hibernate. Locking the session before sleep is # right for suspend (resumes from RAM, no other gate), but an encrypted # hibernate already resumes through the LUKS passphrase — a hyprlock on # top is a second password. And we can't just drop the lock after the # resume: a Wayland session-lock whose client dies without releasing # trips hyprlock's "go to a tty" crash failsafe (the compositor keeps the # screen locked for safety). So don't lock before an encrypted hibernate # in the first place. hypridle can't tell suspend from hibernate, hence a # system unit hooked to the sleep targets: it locks on the RAM-resume # sleeps always, and on hibernate only when the disk is unencrypted (no # LUKS gate to rely on). Replaces hypridle's old before_sleep_cmd. systemd.services.nomarchy-lock-before-sleep = let encrypted = builtins.attrNames config.boot.initrd.luks.devices != [ ]; # Sleeps whose normal resume is from RAM — always lock these. ramTargets = [ "suspend.target" "hybrid-sleep.target" "suspend-then-hibernate.target" ]; ramServices = [ "systemd-suspend.service" "systemd-hybrid-sleep.service" "systemd-suspend-then-hibernate.service" ]; in { description = "Lock the session before sleep (skipped for an encrypted hibernate)"; before = ramServices ++ lib.optional (!encrypted) "systemd-hibernate.service"; wantedBy = ramTargets ++ lib.optional (!encrypted) "hibernate.target"; serviceConfig = { Type = "oneshot"; # Plural: a system unit has no session of its own to lock. ExecStart = "${config.systemd.package}/bin/loginctl lock-sessions"; }; }; # zsh as the default login shell (the desktop's shell experience — # starship/bat/eza/zoxide — is configured home-side in shell.nix). # programs.zsh.enable wires /etc/zshrc, completion and /etc/shells; # defaultUserShell applies to normal users that don't set their own. programs.zsh.enable = lib.mkDefault true; # mkOverride 500: beats nixpkgs' bash.nix `mkDefault bashInteractive` # (equal mkDefault priority would conflict) while staying below a # plain downstream assignment. A per-user `users.users..shell` # overrides this regardless. users.defaultUserShell = lib.mkOverride 500 pkgs.zsh; hardware.bluetooth.enable = lib.mkDefault cfg.bluetooth.enable; services.blueman.enable = lib.mkDefault cfg.bluetooth.enable; # ── Foreign binaries: nix-ld ───────────────────────────────────── # An ld.so shim so dynamically-linked binaries not built for NixOS # (downloaded tools, language servers, pip/npm-installed ELFs, the # npx-fetched claude-code) run without manual patchelf. On by default — # a pragmatic-desktop expectation. programs.nix-ld.enable = lib.mkDefault true; # ── Firmware ───────────────────────────────────────────────────── # Blobs for in-kernel drivers: wifi (iwlwifi/ath/rtw/brcm), SOF # audio, Bluetooth. Drivers ship with the kernel, but without these # a real machine can boot with no wifi — QEMU never catches it. # nixos-generate-config also keys CPU microcode off this flag. hardware.enableRedistributableFirmware = lib.mkDefault true; # ── BTRFS timeline snapshots (ported from the previous iteration) ─ # Guarded on the actual filesystem so enabling it on an ext4 machine # is a clean no-op rather than a failing timer. services.snapper.configs = lib.mkIf (cfg.snapper.enable && (config.fileSystems."/".fsType or "") == "btrfs") ({ root = { SUBVOLUME = "/"; TIMELINE_CREATE = true; TIMELINE_CLEANUP = true; TIMELINE_LIMIT_HOURLY = "5"; TIMELINE_LIMIT_DAILY = "7"; TIMELINE_LIMIT_WEEKLY = "0"; TIMELINE_LIMIT_MONTHLY = "0"; TIMELINE_LIMIT_YEARLY = "0"; }; } # /home is user data — snapshot it too when it's its own BTRFS subvolume # (the installer's @home). Keep the recent hourlies for oops-recovery # plus dailies, and a month of weeklies; "at least daily" by default. // lib.optionalAttrs ((config.fileSystems."/home".fsType or "") == "btrfs") { home = { SUBVOLUME = "/home"; TIMELINE_CREATE = true; TIMELINE_CLEANUP = true; TIMELINE_LIMIT_HOURLY = "5"; TIMELINE_LIMIT_DAILY = "7"; TIMELINE_LIMIT_WEEKLY = "4"; TIMELINE_LIMIT_MONTHLY = "0"; TIMELINE_LIMIT_YEARLY = "0"; }; }); # snapper needs a `.snapshots` subvolume inside each tracked subvolume. # The installer made one for / (@snapshots → /.snapshots) but not /home, # so create a nested one there if missing (idempotent via the condition; # excluded from /home snapshots since BTRFS snapshots are non-recursive). # Runs at boot so it also fixes already-installed machines — no disko # change needed. systemd.services.nomarchy-home-snapshots-subvol = lib.mkIf (cfg.snapper.enable && (config.fileSystems."/home".fsType or "") == "btrfs") { description = "Create /home/.snapshots for snapper if missing"; wantedBy = [ "multi-user.target" ]; before = [ "snapper-timeline.service" "snapper-cleanup.service" ]; unitConfig.ConditionPathExists = "!/home/.snapshots"; serviceConfig = { Type = "oneshot"; ExecStart = "${pkgs.btrfs-progs}/bin/btrfs subvolume create /home/.snapshots"; }; }; # Background filesystem health checks. Scans all BTRFS filesystems to detect # and (if RAID/dup) correct bitrot before it propagates. services.btrfs.autoScrub = lib.mkIf ((config.fileSystems."/".fsType or "") == "btrfs") { enable = lib.mkDefault true; interval = lib.mkDefault "monthly"; }; # ── Fonts ──────────────────────────────────────────────────────── # The ten most popular Nerd Fonts ship by default, so any of them # can be named in the theme state's fonts.mono and actually resolve # (nomarchy-theme-sync warns when a configured font is missing). fonts = { packages = with pkgs; [ nerd-fonts.jetbrains-mono nerd-fonts.fira-code nerd-fonts.caskaydia-cove nerd-fonts.hack nerd-fonts.sauce-code-pro nerd-fonts.meslo-lg nerd-fonts.roboto-mono nerd-fonts.ubuntu-mono # iosevka would be next by popularity, but its package is 1.1 GB # (every weight × variant) — too heavy for the ISO and closures. nerd-fonts.mononoki nerd-fonts.inconsolata # GeistMono (57 MB) — the Boreal theme's mono face; modern and # geometric, unlike the ten defaults. Cheap enough for the ISO # (Iosevka above was rejected at 1.1 GB; this is a compact family). nerd-fonts.geist-mono inter noto-fonts noto-fonts-color-emoji # The legacy-ported waybar identities (summer-day/night) use # FontAwesome codepoints in their layouts and list it in css. font-awesome # The Nomarchy logo glyph (U+F000) for the summer menu button. nomarchyLogoFont ]; fontconfig.defaultFonts = { monospace = lib.mkDefault [ "JetBrainsMono Nerd Font" ]; sansSerif = lib.mkDefault [ "Inter" ]; emoji = lib.mkDefault [ "Noto Color Emoji" ]; }; }; # ── Essential packages ─────────────────────────────────────────── environment.systemPackages = with pkgs; [ nomarchy-theme-sync # provided by overlays.default nomarchy-doctor # read-only health check (System › Doctor) nomarchy-control-center # TUI control center nomarchy-detect-hw # post-install hardware re-probe (HARDWARE.md §8) # Day-to-day lifecycle (README §3). Always run as your user — # `nix flake update` / git must not run as root (libgit2 refuses a # user-owned flake); sudo is only for the system switch. # # nomarchy-pull git pull (if checkout) + flake.lock update # nomarchy-rebuild system switch against the *current* lock # nomarchy-home Home Manager switch (desktop layer) # # Full distro upgrade: nomarchy-pull && nomarchy-rebuild && nomarchy-home # Config-only system: nomarchy-rebuild # Theme/desktop only: nomarchy-home # # Legacy names (sys-update / sys-rebuild / home-update) stay as # thin wrappers so old docs and muscle memory keep working. (pkgs.writeShellScriptBin "nomarchy-pull" '' set -euo pipefail if [ "$(id -u)" -eq 0 ]; then echo "nomarchy-pull: run as your normal user" >&2 exit 1 fi flake="''${NOMARCHY_PATH:-$HOME/.nomarchy}" if [ ! -d "$flake" ]; then echo "nomarchy-pull: flake path not found: $flake" >&2 echo " Set NOMARCHY_PATH or clone/symlink your flake to ~/.nomarchy." >&2 exit 1 fi # 1) Your checkout (system.nix / home.nix / local edits). if [ -d "$flake/.git" ]; then echo "nomarchy-pull: git pull --ff-only in $flake" git -C "$flake" pull --ff-only else echo "nomarchy-pull: $flake is not a git checkout — skipping git pull" fi # 2) Flake inputs (nixpkgs, nomarchy, home-manager, …). echo "nomarchy-pull: nix flake update in $flake" nix flake update --flake "$flake" echo "nomarchy-pull: done — next: nomarchy-rebuild then nomarchy-home" '') (pkgs.writeShellScriptBin "nomarchy-rebuild" '' set -euo pipefail if [ "$(id -u)" -eq 0 ]; then echo "nomarchy-rebuild: run as your normal user (it sudos the rebuild itself)" >&2 exit 1 fi flake="''${NOMARCHY_PATH:-$HOME/.nomarchy}" if [ ! -d "$flake" ]; then echo "nomarchy-rebuild: flake path not found: $flake" >&2 exit 1 fi before=$(readlink -f /run/current-system) log=$(mktemp) trap 'rm -f "$log"' EXIT set +e # Snapshot-first when snapper is on; pass flake path through sudo # (nixos-rebuild-snap used to hardcode /etc/nixos). if command -v nixos-rebuild-snap >/dev/null 2>&1; then sudo env NOMARCHY_PATH="$flake" nixos-rebuild-snap "$@" 2>&1 | tee "$log" else sudo nixos-rebuild switch --flake "$flake#default" "$@" 2>&1 | tee "$log" fi rc=''${PIPESTATUS[0]} set -e if [ "$rc" -ne 0 ]; then echo echo "nomarchy-rebuild: FAILED (exit $rc). Last lines:" tail -n 40 "$log" || true echo echo "Diagnose: nomarchy-doctor" echo "Recovery: docs/RECOVERY.md (boot menu generations / snapper)" exit "$rc" fi after=$(readlink -f /run/current-system) if [ "$before" = "$after" ]; then echo "nomarchy-rebuild: no changes — the system is identical." else echo "nomarchy-rebuild: what changed:" ${pkgs.nvd}/bin/nvd diff "$before" "$after" || true fi '') (pkgs.writeShellScriptBin "nomarchy-home" '' set -euo pipefail if [ "$(id -u)" -eq 0 ]; then echo "nomarchy-home: run as your normal user" >&2 exit 1 fi flake="''${NOMARCHY_PATH:-$HOME/.nomarchy}" if [ ! -d "$flake" ]; then echo "nomarchy-home: flake path not found: $flake" >&2 exit 1 fi log=$(mktemp) trap 'rm -f "$log"' EXIT set +e home-manager switch --flake "$flake" "$@" 2>&1 | tee "$log" rc=''${PIPESTATUS[0]} set -e if [ "$rc" -ne 0 ]; then echo echo "nomarchy-home: FAILED (exit $rc). Last lines:" tail -n 40 "$log" || true echo echo "Diagnose: nomarchy-doctor" echo "Recovery: home-manager generations (or docs/RECOVERY.md)" exit "$rc" fi echo "nomarchy-home: desktop applied." '') # Legacy aliases — same behaviour, old names. (pkgs.writeShellScriptBin "sys-update" '' # Was: flake update + system rebuild. Now the two explicit steps. nomarchy-pull && nomarchy-rebuild "$@" '') (pkgs.writeShellScriptBin "sys-rebuild" '' exec nomarchy-rebuild "$@" '') (pkgs.writeShellScriptBin "home-update" '' exec nomarchy-home "$@" '') git vim wget curl jq brightnessctl playerctl pamixer wl-clipboard grim slurp hyprpicker ] ++ lib.optional (cfg.snapper.enable && (config.fileSystems."/".fsType or "") == "btrfs") # Snapshot, then rebuild — rollback material for system changes # (theme changes don't need it; HM generations already roll back). (pkgs.writeShellScriptBin "nixos-rebuild-snap" '' if [ "$(id -u)" -ne 0 ]; then echo "This script must be run as root (use sudo)" >&2 exit 1 fi # Prefer the path nomarchy-rebuild passes through sudo; fall back # for hand invocations. flake="''${NOMARCHY_PATH:-/etc/nixos}" echo "Creating pre-rebuild snapshot..." ${pkgs.snapper}/bin/snapper -c root create \ -d "Pre-rebuild $(date +'%Y-%m-%d %H:%M:%S')" \ --cleanup-algorithm number echo "Rebuilding $flake#default ..." nixos-rebuild switch --flake "$flake#default" "$@" '') # The desktop snapshot manager (browse / diff / restore / rollback over # snapper, elevating via polkit) — the primary `nomarchy-menu snapshot` # target. The "2.2 segfault" is unprivileged-only (libbtrfsutil # unprivileged subvolume iteration, btrfs-progs 6.17.1, fixed upstream # after); the pkexec launcher runs it as root, where it works — # VM-proven, guarded by checks.snapshot-gui. ++ lib.optional cfg.snapper.enable pkgs.btrfs-assistant # Keyboard-driven snapper browser/restore — the menu's fallback when the # GUI is absent, and handy over SSH. Runs as root (snapper is root-only # here; the menu opens it in a terminal via sudo, one password prompt), # fzf to pick, with browse/diff (read-only) and typed-`yes` confirmation # before any write. ++ lib.optional cfg.snapper.enable (pkgs.writeShellApplication { name = "nomarchy-snapshots"; runtimeInputs = with pkgs; [ snapper fzf gawk gnugrep less coreutils systemd ]; text = '' if [ "$(id -u)" -ne 0 ]; then echo "nomarchy-snapshots must run as root (snapper needs it) — use sudo." >&2 exit 1 fi mapfile -t configs < <(snapper list-configs | awk 'NR>2 {print $1}' | grep .) if [ "''${#configs[@]}" -eq 0 ]; then echo "No snapper configs found." >&2; exit 1 elif [ "''${#configs[@]}" -eq 1 ]; then config="''${configs[0]}" else config=$(printf '%s\n' "''${configs[@]}" | fzf --prompt="snapper config> ") || exit 0 fi while :; do snap=$(snapper -c "$config" list \ | fzf --header-lines=2 --prompt="[$config] pick a snapshot (Esc quits)> ") || exit 0 num=$(awk '{print $1}' <<<"$snap") case "$num" in ""|*[!0-9]*) continue ;; esac action=$(printf '%s\n' \ "Browse changes since #$num (read-only)" \ "Restore changed files to #$num (undochange)" \ "Roll the system back to #$num (reboot)" \ "↩ Back to the snapshot list" \ | fzf --prompt="snapshot #$num> ") || exit 0 case "$action" in Browse*) snapper -c "$config" status "$num..0" | less -R || true ;; Restore*) read -rp "Revert files in '$config' to snapshot #$num? Type yes to confirm: " ans || continue if [ "$ans" = yes ]; then snapper -c "$config" undochange "$num..0" echo "Files restored. Press enter."; read -r _ || true fi ;; Roll*) if [ "$config" != root ]; then echo "Rollback applies to the 'root' config only; use Restore for '$config'. Press enter." read -r _ || true else read -rp "Roll the SYSTEM back to #$num and REBOOT now? Type yes to confirm: " ans || continue if [ "$ans" = yes ]; then snapper -c root rollback "$num" echo "Rolled back — rebooting…"; systemctl reboot fi fi ;; *) continue ;; esac done ''; }); # Don't let boot entries fill the ESP over the years. boot.loader.systemd-boot.configurationLimit = lib.mkDefault 10; # Backlight udev rule (chgrp video + g+w on /sys/class/backlight) so # the desktop's swayosd brightness control works for video-group # users without root. Harmless baseline even if the OSD is disabled. services.udev.packages = [ pkgs.swayosd ]; # ── Nix itself ─────────────────────────────────────────────────── nix = { settings = { experimental-features = [ "nix-command" "flakes" ]; auto-optimise-store = lib.mkDefault true; # The downstream flake (~/.nomarchy) is meant to be a live working # tree: nomarchy-theme-sync rewrites theme-state.json on every # switch (and you needn't commit each tweak), so the "Git tree is # dirty" warning fires on every rebuild and is pure noise here. warn-dirty = lib.mkDefault false; }; gc = { automatic = lib.mkDefault true; dates = lib.mkDefault "weekly"; options = lib.mkDefault "--delete-older-than 14d"; }; }; }; }