# 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 ./file-manager.nix ./power.nix ]; config = { # The safe half of distro branding: distroName flows into # /etc/os-release PRETTY_NAME, systemd-boot entry titles and the # ISO boot-menu label. distroId stays "nixos" on purpose — it feeds # DEFAULT_HOSTNAME and upstream isNixos checks (see roadmap). system.nixos.distroName = lib.mkDefault "Nomarchy"; # 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. users.motd = lib.mkDefault '' ${distroName} — a NixOS desktop, themed from one JSON. sys-update update inputs + rebuild the system home-update apply home/theme changes (no sudo) nomarchy-theme-sync apply switch the whole palette SUPER+? keybindings cheatsheet ''; # 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.config.allowUnfree = true; # ── 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; # ── 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. }; services.greetd = lib.mkIf cfg.greeter.enable { enable = lib.mkDefault true; settings = { default_session = { # start-hyprland is Hyprland 0.55's watchdog launcher; running # the bare binary makes every session print a warning. command = lib.mkDefault "${pkgs.tuigreet}/bin/tuigreet --time --remember --greeting 'Welcome to ${distroName}' --cmd start-hyprland"; user = "greeter"; }; # Boot straight into the session once; logout → normal greeter. initial_session = lib.mkIf (cfg.greeter.autoLogin != null) { command = "start-hyprland"; user = cfg.greeter.autoLogin; }; }; }; # ── 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; networking.networkmanager.enable = lib.mkDefault true; # No double-unlock on hibernate. hypridle locks hyprlock before every # sleep (right for suspend), but a hibernate resume is already gated by # the LUKS passphrase entered at boot — so the user would type a password # twice. Once we're back from an *encrypted* hibernate, drop the now- # redundant hyprlock. Ordered After the hibernate service, so it runs # post-resume; pulled in only by hibernate.target (suspend, and the # RAM-resume phase of suspend-then-hibernate, stay locked — they have no # passphrase gate). Gated on LUKS: an unencrypted hibernate keeps its lock. systemd.services.nomarchy-hibernate-unlock = lib.mkIf (builtins.attrNames config.boot.initrd.luks.devices != [ ]) { description = "Dismiss hyprlock after resuming from an encrypted hibernate"; after = [ "systemd-hibernate.service" ]; wantedBy = [ "hibernate.target" ]; serviceConfig = { Type = "oneshot"; # `-`: a missing hyprlock (idle disabled, or not locked) isn't a failure. ExecStart = "-${pkgs.procps}/bin/pkill -x hyprlock"; }; }; # 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"; }; }; # ── 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 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 # Friendly wrappers for the two rebuild paths (README §3). Run as # your user: `nix flake update` must NOT run as root (libgit2 # refuses the user-owned flake repo) — sudo happens inside, only # for the system switch. (pkgs.writeShellScriptBin "sys-update" '' set -e if [ "$(id -u)" -eq 0 ]; then echo "sys-update: run as your normal user (it sudos the rebuild itself)" >&2 exit 1 fi flake="''${NOMARCHY_PATH:-$HOME/.nomarchy}" echo "sys-update: updating flake inputs in $flake" nix flake update --flake "$flake" if command -v nixos-rebuild-snap >/dev/null 2>&1; then sudo nixos-rebuild-snap "$@" # BTRFS snapshot first else sudo nixos-rebuild switch --flake "$flake#default" "$@" fi '') (pkgs.writeShellScriptBin "home-update" '' set -e exec home-manager switch --flake "''${NOMARCHY_PATH:-$HOME/.nomarchy}" "$@" '') git vim wget curl jq brightnessctl playerctl pamixer wl-clipboard grim slurp ] ++ 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 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..." nixos-rebuild switch --flake /etc/nixos#default "$@" ''); # 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; }; gc = { automatic = lib.mkDefault true; dates = lib.mkDefault "weekly"; options = lib.mkDefault "--delete-older-than 14d"; }; }; }; }