From dfc436802ddbd4477011d675fc9a54c6e987e1a1 Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Thu, 4 Jun 2026 15:22:07 +0100 Subject: [PATCH 01/21] refactor(nixos): declare bootloader per-host, not in workstation.nix The bootloader is firmware-specific, not form-factor: UEFI hosts use systemd-boot, BIOS hosts use GRUB. Drop boot.loader.systemd-boot.enable from workstation.nix and declare it on the MBP instead, so the incoming BIOS-only T400 (GRUB) doesn't have to force it off. Co-Authored-By: Claude Opus 4.8 (1M context) --- system/machine/MBP-Asahi/configuration.nix | 4 +++- system/modules/workstation.nix | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/system/machine/MBP-Asahi/configuration.nix b/system/machine/MBP-Asahi/configuration.nix index 07bbb62..24dad33 100644 --- a/system/machine/MBP-Asahi/configuration.nix +++ b/system/machine/MBP-Asahi/configuration.nix @@ -7,7 +7,9 @@ ./hardware-configuration.nix ]; - # Asahi manages the EFI vars from macOS; do not touch them from NixOS. + # UEFI boot via systemd-boot. Asahi manages the EFI vars from macOS, so do not + # touch them from NixOS. + boot.loader.systemd-boot.enable = true; boot.loader.efi.canTouchEfiVariables = false; networking.hostName = "Emma-Asahi"; diff --git a/system/modules/workstation.nix b/system/modules/workstation.nix index dc7366f..96da265 100644 --- a/system/modules/workstation.nix +++ b/system/modules/workstation.nix @@ -1,10 +1,12 @@ # Form-factor-agnostic base for the physical graphical NixOS machines. Imported # by both ./laptop.nix and ./desktop.nix; those add only the bits that differ # between portable and desktop hosts (chiefly the networking backend). +# +# The bootloader is NOT set here -- it is firmware-specific, not form-factor: +# UEFI hosts (MBP, Mac Pro 3,1) use systemd-boot, the BIOS-only T400 uses GRUB. +# Each machine config declares its own. { ... }: { - boot.loader.systemd-boot.enable = true; - features.swayDesktop.enable = true; console.keyMap = "dvorak"; -- 2.52.0 From ebff5aeba6534d00a4244e938ce99a11325e2633 Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Thu, 4 Jun 2026 15:22:07 +0100 Subject: [PATCH 02/21] feat(nixos): replace X1 with ThinkPad T400; add Mac Pro 3,1 desktop - lyrathorpe-t400 replaces lyrathorpe-x1c: ThinkPad T400 (legacy BIOS -> GRUB, Intel microcode + redistributable firmware for iwlwifi, pipewire, sshd). - lyrathorpe-macpro31: new desktop host (portable = false) importing desktop.nix. Mac Pro 3,1 has 64-bit EFI -> systemd-boot; wired NetworkManager via desktop.nix; desktop status bar (temperature + net, no battery). Both ship hand-written placeholder hardware-configuration.nix (root/swap/ESP by label, GRUB device /dev/sda) to be regenerated with nixos-generate-config and committed at install time. All five host configs evaluate; nixfmt clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- flake.nix | 20 ++++++- system/machine/MacPro31/configuration.nix | 57 +++++++++++++++++++ .../MacPro31/hardware-configuration.nix | 52 +++++++++++++++++ system/machine/T400/configuration.nix | 43 ++++++++++++++ .../machine/T400/hardware-configuration.nix | 44 ++++++++++++++ system/machine/X1/configuration.nix | 33 ----------- system/machine/X1/hardware-configuration.nix | 42 -------------- 7 files changed, 214 insertions(+), 77 deletions(-) create mode 100644 system/machine/MacPro31/configuration.nix create mode 100644 system/machine/MacPro31/hardware-configuration.nix create mode 100644 system/machine/T400/configuration.nix create mode 100644 system/machine/T400/hardware-configuration.nix delete mode 100644 system/machine/X1/configuration.nix delete mode 100644 system/machine/X1/hardware-configuration.nix diff --git a/flake.nix b/flake.nix index fe4d937..c860a3e 100644 --- a/flake.nix +++ b/flake.nix @@ -192,12 +192,12 @@ ]; }; - lyrathorpe-x1c = { + lyrathorpe-t400 = { system = "x86_64-linux"; username = "lyrathorpe"; fullName = "Lyra Thorpe"; modules = [ - ./system/machine/X1/configuration.nix + ./system/machine/T400/configuration.nix ./system/modules/laptop.nix ./lyrathorpe/swaywm.nix ]; @@ -207,6 +207,22 @@ ]; }; + lyrathorpe-macpro31 = { + system = "x86_64-linux"; + username = "lyrathorpe"; + fullName = "Lyra Thorpe"; + portable = false; + modules = [ + ./system/machine/MacPro31/configuration.nix + ./system/modules/desktop.nix + ./lyrathorpe/swaywm.nix + ]; + homeModules = [ + ./lyrathorpe/home + ./lyrathorpe/home/desktop.nix + ]; + }; + emmathorpe-edaas = { system = "x86_64-linux"; username = "emmathorpe"; diff --git a/system/machine/MacPro31/configuration.nix b/system/machine/MacPro31/configuration.nix new file mode 100644 index 0000000..182a348 --- /dev/null +++ b/system/machine/MacPro31/configuration.nix @@ -0,0 +1,57 @@ +# Apple Mac Pro 3,1 (Early 2008, dual Xeon Harpertown, x86_64). Desktop host: +# shared graphical/wired options live in ../../modules/desktop.nix; only +# host-specific settings are here. +{ ... }: + +{ + imports = [ + ./hardware-configuration.nix + ]; + + # The Mac Pro 3,1 has 64-bit EFI (confirmed by the owner), so boot via + # systemd-boot like the MBP -- no GRUB/BIOS shim needed. + boot.loader.systemd-boot.enable = true; + # Apple's EFI does not reliably support efibootmgr NVRAM writes; leave the + # firmware vars untouched. + boot.loader.efi.canTouchEfiVariables = false; + # Apple-EFI quirk: if the Mac does not pick up the bootloader at the boot + # picker, install it to the fallback path \EFI\BOOT\BOOTX64.EFI and/or + # "bless" the ESP from macOS. Uncomment to write the removable fallback path: + # boot.loader.efi.efiInstallAsRemovable = true; + + networking.hostName = "MacPro31-NixOS"; + + # This host accepts SSH, so open 22 (the firewall itself is enabled in + # workstation.nix with a default-deny policy). + services.openssh.enable = true; + networking.firewall.allowedTCPPorts = [ 22 ]; + + services.pipewire = { + enable = true; + pulse.enable = true; + }; + + # No fingerprint hardware; empty service still lets swaylock authenticate via + # password. + security.pam.services.swaylock = { }; + + # Dual Harpertown Xeon microcode + redistributable firmware (e.g. GPU/NIC + # blobs). + hardware.cpu.intel.updateMicrocode = true; + hardware.enableRedistributableFirmware = true; + + # GPU note: the stock card varies between units -- ATI Radeon HD 2600 XT or + # NVIDIA GeForce 8800 GT. Sway needs a working KMS/modesetting driver; do NOT + # install a proprietary blob here. Depending on the installed card, rely on + # the open kernel driver: + # - ATI Radeon HD 2600 XT -> "radeon" (older) or "amdgpu" KMS + # - NVIDIA GeForce 8800 GT -> "nouveau" KMS + # These come up automatically via the in-tree drivers + KMS, and the graphics + # stack itself is enabled by swaywm.nix. If a card needs to be forced, add it + # here, e.g. `services.xserver.videoDrivers = [ "radeon" ];` (or "nouveau"), + # and/or `boot.initrd.kernelModules = [ "radeon" ];` in + # hardware-configuration.nix for early KMS. + + # See `man configuration.nix` / the stateVersion docs before changing. + system.stateVersion = "26.05"; +} diff --git a/system/machine/MacPro31/hardware-configuration.nix b/system/machine/MacPro31/hardware-configuration.nix new file mode 100644 index 0000000..713a78e --- /dev/null +++ b/system/machine/MacPro31/hardware-configuration.nix @@ -0,0 +1,52 @@ +# PLACEHOLDER -- hand-written, not machine-generated. Regenerate on the real +# Mac Pro 3,1 with `nixos-generate-config` and commit the result. The device +# labels below are guesses; replace them with the generated UUIDs (or label the +# partitions accordingly at install time). +{ + config, + lib, + modulesPath, + ... +}: + +{ + imports = [ + (modulesPath + "/installer/scan/not-detected.nix") + ]; + + boot.initrd.availableKernelModules = [ + "ahci" + "ata_piix" + "ehci_pci" + "uhci_hcd" + "firewire_ohci" + "usb_storage" + "sd_mod" + "sr_mod" + ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; + boot.extraModulePackages = [ ]; + + # EFI System Partition for systemd-boot. Label the ESP `ESP` at install, or + # replace with the generated UUID. + fileSystems."/boot" = { + device = "/dev/disk/by-label/ESP"; + fsType = "vfat"; + }; + + # Label your root partition `nixos` at install, or replace with the generated UUID. + fileSystems."/" = { + device = "/dev/disk/by-label/nixos"; + fsType = "ext4"; + }; + + swapDevices = [ + { device = "/dev/disk/by-label/swap"; } + ]; + + networking.useDHCP = lib.mkDefault true; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; +} diff --git a/system/machine/T400/configuration.nix b/system/machine/T400/configuration.nix new file mode 100644 index 0000000..b941349 --- /dev/null +++ b/system/machine/T400/configuration.nix @@ -0,0 +1,43 @@ +# ThinkPad T400 (NixOS). Shared laptop options live in ../../modules/laptop.nix; +# only host-specific settings are here. +{ ... }: + +{ + imports = [ + ./hardware-configuration.nix + ]; + + # The T400 is legacy BIOS only (no UEFI), so boot via GRUB in BIOS mode. + # `device` must point at the actual install disk -- adjust if it is not /dev/sda. + boot.loader.grub = { + enable = true; + device = "/dev/sda"; + }; + + networking.hostName = "T400-NixOS"; + + console.font = "Lat2-Terminus16"; + + services.pipewire = { + enable = true; + pulse.enable = true; + }; + + # This host accepts SSH, so open 22 (the firewall itself is enabled in + # laptop.nix with a default-deny policy). + services.openssh.enable = true; + networking.firewall.allowedTCPPorts = [ 22 ]; + + # The T400's fingerprint reader differs/may be absent; empty service still + # lets swaylock authenticate via password. + security.pam.services.swaylock = { }; + + # Intel Core 2 (Penryn) microcode + redistributable firmware for the Intel + # WiFi Link 5100/5300 iwlwifi blobs. The GMA 4500MHD works out of the box via + # i915/KMS, so no extra graphics config is needed. + hardware.cpu.intel.updateMicrocode = true; + hardware.enableRedistributableFirmware = true; + + # See `man configuration.nix` / the stateVersion docs before changing. + system.stateVersion = "26.05"; +} diff --git a/system/machine/T400/hardware-configuration.nix b/system/machine/T400/hardware-configuration.nix new file mode 100644 index 0000000..a68a562 --- /dev/null +++ b/system/machine/T400/hardware-configuration.nix @@ -0,0 +1,44 @@ +# PLACEHOLDER -- hand-written, not machine-generated. Regenerate on the real +# T400 with `nixos-generate-config` and commit the result. The device labels +# below are guesses; replace them with the generated UUIDs (or label the +# partitions accordingly at install time). +{ + config, + lib, + modulesPath, + ... +}: + +{ + imports = [ + (modulesPath + "/installer/scan/not-detected.nix") + ]; + + boot.initrd.availableKernelModules = [ + "ahci" + "ata_piix" + "ehci_pci" + "uhci_hcd" + "usb_storage" + "sd_mod" + "sr_mod" + ]; + boot.initrd.kernelModules = [ ]; + boot.kernelModules = [ "kvm-intel" ]; + boot.extraModulePackages = [ ]; + + # Label your root partition `nixos` at install, or replace with the generated UUID. + fileSystems."/" = { + device = "/dev/disk/by-label/nixos"; + fsType = "ext4"; + }; + + swapDevices = [ + { device = "/dev/disk/by-label/swap"; } + ]; + + networking.useDHCP = lib.mkDefault true; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; +} diff --git a/system/machine/X1/configuration.nix b/system/machine/X1/configuration.nix deleted file mode 100644 index bb31107..0000000 --- a/system/machine/X1/configuration.nix +++ /dev/null @@ -1,33 +0,0 @@ -# ThinkPad X1 (NixOS). Shared laptop options live in ../../modules/laptop.nix; -# only host-specific settings are here. -{ ... }: - -{ - imports = [ - ./hardware-configuration.nix - ]; - - boot.loader.efi.canTouchEfiVariables = true; - - networking.hostName = "X1-NixOS"; - networking.domain = "client.cbg.emmaisvery.gay"; - - console.font = "Lat2-Terminus16"; - - services.pipewire = { - enable = true; - pulse.enable = true; - }; - - # This host accepts SSH, so open 22 (the firewall itself is enabled in - # laptop.nix with a default-deny policy). - services.openssh.enable = true; - networking.firewall.allowedTCPPorts = [ 22 ]; - - # Fingerprint reader: allow swaylock to authenticate via fprintd. - services.fprintd.enable = true; - security.pam.services.swaylock.fprintAuth = true; - - # See `man configuration.nix` / the stateVersion docs before changing. - system.stateVersion = "24.11"; -} diff --git a/system/machine/X1/hardware-configuration.nix b/system/machine/X1/hardware-configuration.nix deleted file mode 100644 index 888cd4a..0000000 --- a/system/machine/X1/hardware-configuration.nix +++ /dev/null @@ -1,42 +0,0 @@ -# Do not modify this file! It was generated by ‘nixos-generate-config’ -# and may be overwritten by future invocations. Please make changes -# to /etc/nixos/configuration.nix instead. -{ config, lib, pkgs, modulesPath, ... }: - -{ - imports = - [ (modulesPath + "/installer/scan/not-detected.nix") - ]; - - boot.initrd.availableKernelModules = [ "xhci_pci" "nvme" "usb_storage" "sd_mod" ]; - boot.initrd.kernelModules = [ ]; - boot.kernelModules = [ "kvm-intel" ]; - boot.extraModulePackages = [ ]; - - fileSystems."/" = - { device = "/dev/disk/by-uuid/a7145534-b122-4899-a75a-3d2e78474d6b"; - fsType = "ext4"; - }; - - fileSystems."/boot" = - { device = "/dev/disk/by-uuid/1338-3D4F"; - fsType = "vfat"; - options = [ "fmask=0077" "dmask=0077" ]; - }; - - swapDevices = - [ { device = "/dev/disk/by-uuid/e553c8dc-9d5a-48ec-87bc-9c86ce5932a4"; } - ]; - - # Enables DHCP on each ethernet and wireless interface. In case of scripted networking - # (the default) this is the recommended approach. When using systemd-networkd it's - # still possible to use this option, but it's recommended to use it in conjunction - # with explicit per-interface declarations with `networking.interfaces..useDHCP`. - networking.useDHCP = lib.mkDefault true; - # networking.interfaces.enp0s31f6.useDHCP = lib.mkDefault true; - # networking.interfaces.wlp0s20f3.useDHCP = lib.mkDefault true; - # networking.interfaces.wwan0.useDHCP = lib.mkDefault true; - - nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; - hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; -} -- 2.52.0 From b3fa34f4315efde5beec59f7b770f364a7e2fe79 Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Thu, 4 Jun 2026 15:35:31 +0100 Subject: [PATCH 03/21] feat(t400): add coreboot GRUB/UEFI boot variants and discrete ATI GPU Split the T400 bootloader into self-contained, importable modules so the host can match whatever firmware is flashed (switch by changing one import): - boot-bios.nix stock BIOS / coreboot+SeaBIOS -> GRUB on the MBR (default) - boot-coreboot-grub.nix coreboot GRUB payload -> config-only GRUB (device=nodev) - boot-coreboot-uefi.nix coreboot Tianocore/UEFI payload -> systemd-boot; carries its own ESP (/boot vfat) so it travels with the mode Cover the optional discrete ATI Mobility Radeon HD 3470 (RV620): load the open `radeon` KMS driver in the initrd for early modesetting (firmware via enableRedistributableFirmware), with a note on the T400's switchable graphics. All three boot variants evaluate; nixfmt clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- system/machine/T400/boot-bios.nix | 11 ++++++++ system/machine/T400/boot-coreboot-grub.nix | 13 ++++++++++ system/machine/T400/boot-coreboot-uefi.nix | 17 ++++++++++++ system/machine/T400/configuration.nix | 30 ++++++++++++++-------- 4 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 system/machine/T400/boot-bios.nix create mode 100644 system/machine/T400/boot-coreboot-grub.nix create mode 100644 system/machine/T400/boot-coreboot-uefi.nix diff --git a/system/machine/T400/boot-bios.nix b/system/machine/T400/boot-bios.nix new file mode 100644 index 0000000..01fcc8c --- /dev/null +++ b/system/machine/T400/boot-bios.nix @@ -0,0 +1,11 @@ +# Boot via legacy BIOS -- the stock Lenovo BIOS, or coreboot with the SeaBIOS +# payload (both present a legacy BIOS interface). GRUB is installed to the MBR of +# the boot disk. This is the default. +{ ... }: +{ + boot.loader.grub = { + enable = true; + # Must point at the actual install disk -- adjust if it is not /dev/sda. + device = "/dev/sda"; + }; +} diff --git a/system/machine/T400/boot-coreboot-grub.nix b/system/machine/T400/boot-coreboot-grub.nix new file mode 100644 index 0000000..ba5a6ab --- /dev/null +++ b/system/machine/T400/boot-coreboot-grub.nix @@ -0,0 +1,13 @@ +# Boot via coreboot's GRUB payload (e.g. libreboot default). The GRUB in the +# flash chip reads the grub.cfg that NixOS generates on disk, so GRUB here is +# config-only -- it is NOT installed to any disk MBR (`device = "nodev"`). +# +# Your coreboot grub.cfg must locate and load the on-disk config, e.g. search +# for and `configfile` /boot/grub/grub.cfg (or chainload the disk's GRUB). +{ ... }: +{ + boot.loader.grub = { + enable = true; + device = "nodev"; + }; +} diff --git a/system/machine/T400/boot-coreboot-uefi.nix b/system/machine/T400/boot-coreboot-uefi.nix new file mode 100644 index 0000000..2c65608 --- /dev/null +++ b/system/machine/T400/boot-coreboot-uefi.nix @@ -0,0 +1,17 @@ +# Boot via coreboot's Tianocore/edk2 (UEFI) payload. This turns the T400 into a +# real UEFI machine, so use systemd-boot. Unlike Apple's firmware, coreboot's +# UEFI honours EFI variable writes, so canTouchEfiVariables is on. +# +# Requires an EFI System Partition. It is declared here so it travels with this +# boot mode; the generated hardware-configuration.nix should NOT also define +# /boot. Label the ESP `ESP` at install, or replace with the generated UUID. +{ ... }: +{ + boot.loader.systemd-boot.enable = true; + boot.loader.efi.canTouchEfiVariables = true; + + fileSystems."/boot" = { + device = "/dev/disk/by-label/ESP"; + fsType = "vfat"; + }; +} diff --git a/system/machine/T400/configuration.nix b/system/machine/T400/configuration.nix index b941349..2180ce9 100644 --- a/system/machine/T400/configuration.nix +++ b/system/machine/T400/configuration.nix @@ -5,15 +5,14 @@ { imports = [ ./hardware-configuration.nix + # Boot: import exactly ONE, matching the firmware currently flashed. + # Stock Lenovo BIOS and coreboot+SeaBIOS both use boot-bios.nix. + ./boot-bios.nix + # ./boot-coreboot-grub.nix # coreboot with the GRUB payload (config-only GRUB) + # ./boot-coreboot-uefi.nix # coreboot with the Tianocore/edk2 UEFI payload + # # (systemd-boot; carries its own ESP mount) ]; - # The T400 is legacy BIOS only (no UEFI), so boot via GRUB in BIOS mode. - # `device` must point at the actual install disk -- adjust if it is not /dev/sda. - boot.loader.grub = { - enable = true; - device = "/dev/sda"; - }; - networking.hostName = "T400-NixOS"; console.font = "Lat2-Terminus16"; @@ -32,12 +31,23 @@ # lets swaylock authenticate via password. security.pam.services.swaylock = { }; - # Intel Core 2 (Penryn) microcode + redistributable firmware for the Intel - # WiFi Link 5100/5300 iwlwifi blobs. The GMA 4500MHD works out of the box via - # i915/KMS, so no extra graphics config is needed. + # Intel Core 2 (Penryn) microcode + redistributable firmware. The latter also + # supplies the iwlwifi blobs (Intel WiFi Link 5100/5300) and the radeon + # firmware needed by the discrete GPU below. hardware.cpu.intel.updateMicrocode = true; hardware.enableRedistributableFirmware = true; + # This T400 has the optional discrete GPU fitted: an ATI Mobility Radeon HD + # 3470 (RV620), driven by the open `radeon` KMS driver. Load it in the initrd + # for early modesetting (clean Sway/Wayland start); firmware comes from + # enableRedistributableFirmware above. + # + # The T400 has switchable graphics (this discrete GPU + the Intel GMA + # 4500MHD). Select "Discrete" in the firmware's graphics setting so only the + # ATI is live; if you instead run "Integrated", the Intel i915 driver takes + # over with no extra config and `radeon` simply stays idle. + boot.initrd.kernelModules = [ "radeon" ]; + # See `man configuration.nix` / the stateVersion docs before changing. system.stateVersion = "26.05"; } -- 2.52.0 From b01fc132349ee068cfc25f28e07d57101064dc37 Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Thu, 4 Jun 2026 15:38:23 +0100 Subject: [PATCH 04/21] docs(t400,macpro31): add per-machine install-note READMEs Add system/machine/{T400,MacPro31}/README.md covering the placeholder hardware-configuration regeneration, partition labels, bootloader selection (T400 boot variants; Mac Pro EFI quirks), and GPU notes. Link each from its configuration.nix header, and refresh the top-level README host table (T400 replaces X1, Mac Pro 3,1 added) with links to both. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 9 ++-- system/machine/MacPro31/README.md | 51 +++++++++++++++++++++++ system/machine/MacPro31/configuration.nix | 3 +- system/machine/T400/README.md | 39 +++++++++++++++++ system/machine/T400/configuration.nix | 3 +- 5 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 system/machine/MacPro31/README.md create mode 100644 system/machine/T400/README.md diff --git a/README.md b/README.md index 33cc332..9f7b487 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,11 @@ Defined in the host table in [`flake.nix`](./flake.nix): | Configuration | System | Machine | | ------------------- | --------------- | ---------------------------------------- | -| `lyrathorpe-mbp` | `aarch64-linux` | MacBook Pro (Apple Silicon, Asahi) | -| `lyrathorpe-x1c` | `x86_64-linux` | ThinkPad X1 | -| `emmathorpe-edaas` | `x86_64-linux` | Work WSL box (NixOS-WSL) | -| `lyrathorpe-mac` | `aarch64-darwin`| macOS (nix-darwin) | +| `lyrathorpe-mbp` | `aarch64-linux` | MacBook Pro (Apple Silicon, Asahi) | +| `lyrathorpe-t400` | `x86_64-linux` | ThinkPad T400 — [install notes](./system/machine/T400/README.md) | +| `lyrathorpe-macpro31` | `x86_64-linux` | Mac Pro 3,1, desktop — [install notes](./system/machine/MacPro31/README.md) | +| `emmathorpe-edaas` | `x86_64-linux` | Work WSL box (NixOS-WSL) | +| `lyrathorpe-mac` | `aarch64-darwin` | macOS (nix-darwin) | Shared layers: `lyrathorpe/home` (home-manager: shell, git, editor), `system/modules/common-nixos.nix` (all NixOS hosts), and diff --git a/system/machine/MacPro31/README.md b/system/machine/MacPro31/README.md new file mode 100644 index 0000000..399a4d8 --- /dev/null +++ b/system/machine/MacPro31/README.md @@ -0,0 +1,51 @@ +# Mac Pro 3,1 (Early 2008) — install notes + +Flake host: `lyrathorpe-macpro31`. Desktop (`portable = false`, imports +`../../modules/desktop.nix`). Files: `configuration.nix`, +`hardware-configuration.nix`. + +## Hardware configuration + +`hardware-configuration.nix` here is a hand-written **placeholder**. On the real +machine, run `nixos-generate-config`, replace the file, and commit it. It assumes +by-label partitions — ESP `ESP` (vfat, mounted at `/boot`), root `nixos` (ext4), +and `swap` — so either label them at install time or swap in the generated UUIDs. + +## Bootloader + +The Mac Pro 3,1 has **64-bit EFI**, so it uses **systemd-boot** (no GRUB/CSM +shim). `canTouchEfiVariables = false` because Apple's firmware does not reliably +accept `efibootmgr` NVRAM writes. + +Apple-EFI quirk: if the firmware boot picker does not show NixOS after install, +either + +- uncomment `boot.loader.efi.efiInstallAsRemovable = true;` in + `configuration.nix` (installs the fallback `\EFI\BOOT\BOOTX64.EFI`), and/or +- "bless" the ESP from macOS. + +Partition the disk GPT with an ESP (vfat). + +## Graphics + +The stock card varies between units — **ATI Radeon HD 2600 XT** or **NVIDIA +GeForce 8800 GT**. No proprietary driver is hardcoded; Sway relies on in-tree KMS: + +- ATI Radeon HD 2600 XT → `radeon` (or `amdgpu`) KMS +- NVIDIA GeForce 8800 GT → `nouveau` KMS + +These come up automatically. If a card needs forcing, set +`services.xserver.videoDrivers` and/or add the module to +`boot.initrd.kernelModules` for early KMS (see the comment in +`configuration.nix`). + +## Networking + +Wired Ethernet via NetworkManager (from `desktop.nix`) — the Mac Pro has two +gigabit ports. + +## Apply + +```sh +sudo nixos-rebuild switch --flake .#lyrathorpe-macpro31 +``` diff --git a/system/machine/MacPro31/configuration.nix b/system/machine/MacPro31/configuration.nix index 182a348..1d41ad5 100644 --- a/system/machine/MacPro31/configuration.nix +++ b/system/machine/MacPro31/configuration.nix @@ -1,6 +1,7 @@ # Apple Mac Pro 3,1 (Early 2008, dual Xeon Harpertown, x86_64). Desktop host: # shared graphical/wired options live in ../../modules/desktop.nix; only -# host-specific settings are here. +# host-specific settings are here. Install notes (EFI booting, GPU, partitions): +# see ./README.md. { ... }: { diff --git a/system/machine/T400/README.md b/system/machine/T400/README.md new file mode 100644 index 0000000..4b8fe70 --- /dev/null +++ b/system/machine/T400/README.md @@ -0,0 +1,39 @@ +# ThinkPad T400 — install notes + +Flake host: `lyrathorpe-t400`. Files: `configuration.nix`, the `boot-*.nix` +variants, and `hardware-configuration.nix`. + +## Hardware configuration + +`hardware-configuration.nix` here is a hand-written **placeholder**. On the real +machine, run `nixos-generate-config`, replace the file, and commit it. It assumes +by-label partitions — root `nixos` (ext4) and `swap` — so either label them at +install time or swap in the generated UUIDs. + +## Bootloader — import the module matching the flashed firmware + +`configuration.nix` imports exactly one boot module. Default is `boot-bios.nix`; +switch by commenting it out and uncommenting the relevant alternative. + +| Firmware | Module | Notes | +| --- | --- | --- | +| Stock Lenovo BIOS, or coreboot + **SeaBIOS** payload | `boot-bios.nix` | GRUB on the MBR. Set `device` to the real install disk (`/dev/sda` by default). MBR/legacy layout. | +| coreboot + **GRUB** payload | `boot-coreboot-grub.nix` | GRUB is config-only (`device = "nodev"`); NixOS does **not** write to a disk. Your coreboot `grub.cfg` (in the flash chip) must `search` for and `configfile` the on-disk `/boot/grub/grub.cfg`, or chainload the disk's GRUB. | +| coreboot + **Tianocore/edk2 (UEFI)** payload | `boot-coreboot-uefi.nix` | systemd-boot. `canTouchEfiVariables = true` (coreboot honours NVRAM writes). The module **declares its own ESP** (`/boot` vfat, label `ESP`) — when you regenerate `hardware-configuration.nix`, do **not** let it also define `/boot`. Create + label an `ESP` vfat partition (GPT). | + +## Graphics + +This unit has the optional **discrete ATI Mobility Radeon HD 3470 (RV620)**. The +open `radeon` KMS driver is loaded in the initrd for early modesetting; firmware +comes from `enableRedistributableFirmware`. + +The T400 has switchable graphics (discrete ATI + Intel GMA 4500MHD). Select +**Discrete** in the firmware's graphics setting so only the ATI is live. If you +run **Integrated** instead, the Intel `i915` driver takes over with no config +change and `radeon` stays idle. + +## Apply + +```sh +sudo nixos-rebuild switch --flake .#lyrathorpe-t400 +``` diff --git a/system/machine/T400/configuration.nix b/system/machine/T400/configuration.nix index 2180ce9..2ed5d13 100644 --- a/system/machine/T400/configuration.nix +++ b/system/machine/T400/configuration.nix @@ -1,5 +1,6 @@ # ThinkPad T400 (NixOS). Shared laptop options live in ../../modules/laptop.nix; -# only host-specific settings are here. +# only host-specific settings are here. Install notes (boot variants, GPU, +# partitions): see ./README.md. { ... }: { -- 2.52.0 From 578e045a53cbe3973d7cb09d9a4dbd6390a46d27 Mon Sep 17 00:00:00 2001 From: Lyra Thorpe Date: Tue, 9 Jun 2026 17:53:57 +0100 Subject: [PATCH 05/21] feat(macpro): add hardware config --- .../MacPro31/hardware-configuration.nix | 61 +++++++------------ 1 file changed, 21 insertions(+), 40 deletions(-) diff --git a/system/machine/MacPro31/hardware-configuration.nix b/system/machine/MacPro31/hardware-configuration.nix index 713a78e..120d1b1 100644 --- a/system/machine/MacPro31/hardware-configuration.nix +++ b/system/machine/MacPro31/hardware-configuration.nix @@ -1,52 +1,33 @@ -# PLACEHOLDER -- hand-written, not machine-generated. Regenerate on the real -# Mac Pro 3,1 with `nixos-generate-config` and commit the result. The device -# labels below are guesses; replace them with the generated UUIDs (or label the -# partitions accordingly at install time). -{ - config, - lib, - modulesPath, - ... -}: +# Do not modify this file! It was generated by ‘nixos-generate-config’ +# and may be overwritten by future invocations. Please make changes +# to /etc/nixos/configuration.nix instead. +{ config, lib, pkgs, modulesPath, ... }: { - imports = [ - (modulesPath + "/installer/scan/not-detected.nix") - ]; + imports = + [ (modulesPath + "/installer/scan/not-detected.nix") + ]; - boot.initrd.availableKernelModules = [ - "ahci" - "ata_piix" - "ehci_pci" - "uhci_hcd" - "firewire_ohci" - "usb_storage" - "sd_mod" - "sr_mod" - ]; - boot.initrd.kernelModules = [ ]; + boot.initrd.availableKernelModules = [ "uhci_hcd" "ehci_pci" "ata_piix" "ahci" "firewire_ohci" "usb_storage" "usbhid" "sd_mod" ]; + boot.initrd.kernelModules = [ "dm-snapshot" ]; boot.kernelModules = [ "kvm-intel" ]; boot.extraModulePackages = [ ]; - # EFI System Partition for systemd-boot. Label the ESP `ESP` at install, or - # replace with the generated UUID. - fileSystems."/boot" = { - device = "/dev/disk/by-label/ESP"; - fsType = "vfat"; - }; + fileSystems."/" = + { device = "/dev/mapper/MacPro-Root"; + fsType = "ext4"; + }; - # Label your root partition `nixos` at install, or replace with the generated UUID. - fileSystems."/" = { - device = "/dev/disk/by-label/nixos"; - fsType = "ext4"; - }; - - swapDevices = [ - { device = "/dev/disk/by-label/swap"; } - ]; + fileSystems."/boot" = + { device = "/dev/disk/by-uuid/0E9C-C099"; + fsType = "vfat"; + options = [ "fmask=0022" "dmask=0022" ]; + }; + swapDevices = + [ { device = "/dev/disk/by-uuid/dec138b4-320f-4b69-acbc-3014a1032cbd"; } + ]; networking.useDHCP = lib.mkDefault true; - nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; } -- 2.52.0 From c61f94715f73565660f2223a08ffc8195e65075e Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Tue, 9 Jun 2026 18:10:08 +0100 Subject: [PATCH 06/21] feat(sway): add greetd + ReGreet Wayland greeter for Sway hosts Replace TTY/getty login with a graphical Wayland greeter on every host with features.swayDesktop enabled (MBP, T400, Mac Pro; not the WSL box). greetd launches ReGreet inside the cage kiosk compositor; the Sway session is listed automatically via services.displayManager.sessionPackages. Override regreet's mkDefault greetd command to export XKB_DEFAULT_LAYOUT=dvorak so the greeter password field matches the console (workstation.nix) and Sway session (home/sway.nix) layout. Co-Authored-By: Claude Opus 4.8 (1M context) --- lyrathorpe/swaywm.nix | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lyrathorpe/swaywm.nix b/lyrathorpe/swaywm.nix index eb14ab9..200c91c 100644 --- a/lyrathorpe/swaywm.nix +++ b/lyrathorpe/swaywm.nix @@ -45,6 +45,25 @@ in font-awesome ]; + # Wayland login screen (replaces console/getty login on every Sway host). + # greetd runs ReGreet inside the cage kiosk compositor; the Sway session is + # offered automatically because programs.sway registers itself via + # services.displayManager.sessionPackages. Hosts that turn off + # features.swayDesktop (e.g. EDaaS) keep plain TTY login. + programs.regreet.enable = true; + # cage reads the XKB_* environment at startup, so force the greeter onto the + # same Dvorak layout as the console (workstation.nix) and the Sway session + # (home/sway.nix) -- otherwise the password field would be QWERTY. This + # overrides the greetd command regreet sets with mkDefault. + services.greetd.settings.default_session.command = + let + greeter = pkgs.writeShellScript "regreet-cage" '' + export XKB_DEFAULT_LAYOUT=dvorak + exec ${pkgs.dbus}/bin/dbus-run-session ${lib.getExe pkgs.cage} -s -- ${lib.getExe config.programs.regreet.package} + ''; + in + "${greeter}"; + # Desktop portals: enables screen sharing (wlroots) and native file pickers # for Wayland apps such as Element and Firefox. xdg.portal = { -- 2.52.0 From 69ba65bde3512431c419d21ed76fcbbfd3b7f99a Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Tue, 9 Jun 2026 18:14:00 +0100 Subject: [PATCH 07/21] docs(sway): note the Wayland greeter login in READMEs Document the greetd/ReGreet greeter in the top-level README and the T400 and Mac Pro install notes, including that the user account needs a password set before the greeter can authenticate. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 10 ++++++++++ system/machine/MacPro31/README.md | 9 +++++++++ system/machine/T400/README.md | 9 +++++++++ 3 files changed, 28 insertions(+) diff --git a/README.md b/README.md index 9f7b487..0895d59 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,16 @@ sudo nixos-rebuild switch --flake .# darwin-rebuild switch --flake .#lyrathorpe-mac ``` +## Login / greeter + +Graphical (Sway) hosts log in through a Wayland greeter — `greetd` running +ReGreet inside the `cage` kiosk compositor — configured centrally in +[`lyrathorpe/swaywm.nix`](./lyrathorpe/swaywm.nix), gated on +`features.swayDesktop.enable`. The greeter is forced to Dvorak to match the +console and Sway session. Hosts with `features.swayDesktop.enable = false` (the +WSL work box) keep plain TTY login. The target account needs a password +(`passwd `) before it can log in. + ## MacBook (Asahi) firmware The MBP host references `system/modules/firmware/` for Apple peripheral diff --git a/system/machine/MacPro31/README.md b/system/machine/MacPro31/README.md index 399a4d8..e459b1e 100644 --- a/system/machine/MacPro31/README.md +++ b/system/machine/MacPro31/README.md @@ -44,6 +44,15 @@ These come up automatically. If a card needs forcing, set Wired Ethernet via NetworkManager (from `desktop.nix`) — the Mac Pro has two gigabit ports. +## Login + +Graphical login via a Wayland greeter — `greetd` running ReGreet inside the +`cage` kiosk compositor — configured centrally in `lyrathorpe/swaywm.nix` for +every Sway host (gated on `features.swayDesktop.enable`). The greeter is forced +to the Dvorak layout to match the console and Sway session. Set the user +password (`passwd lyrathorpe`) after install, or the greeter cannot +authenticate. Requires working KMS (radeon/nouveau — see Graphics). + ## Apply ```sh diff --git a/system/machine/T400/README.md b/system/machine/T400/README.md index 4b8fe70..6f2d1d7 100644 --- a/system/machine/T400/README.md +++ b/system/machine/T400/README.md @@ -32,6 +32,15 @@ The T400 has switchable graphics (discrete ATI + Intel GMA 4500MHD). Select run **Integrated** instead, the Intel `i915` driver takes over with no config change and `radeon` stays idle. +## Login + +Graphical login via a Wayland greeter — `greetd` running ReGreet inside the +`cage` kiosk compositor — configured centrally in `lyrathorpe/swaywm.nix` for +every Sway host (gated on `features.swayDesktop.enable`). The greeter is forced +to the Dvorak layout to match the console and Sway session. Set the user +password (`passwd lyrathorpe`) after install, or the greeter cannot +authenticate. Requires working radeon/i915 KMS (see Graphics). + ## Apply ```sh -- 2.52.0 From eb1704764fc98a817212553de27b8f0c20b1c718 Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Tue, 9 Jun 2026 18:31:47 +0100 Subject: [PATCH 08/21] fix(sway): use the us(dvorak) variant, not a "dvorak" layout Dvorak is a variant of the "us" XKB layout, not a layout of its own: there is no symbols/dvorak file, so "dvorak" fails to compile. In the greetd/cage greeter the keymap comes solely from XKB_DEFAULT_*, so the failure left the greeter with no keymap and therefore no keyboard input at all (mouse unaffected). Split it into XKB_DEFAULT_LAYOUT=us + XKB_DEFAULT_VARIANT=dvorak. The same mistake in the Sway session (home/sway.nix) was masked: the default us keymap compiled and the failing override was silently dropped, so the session ran QWERTY despite the dvorak setting. Use xkb_variant there too so it is actually Dvorak. console.keyMap = "dvorak" (workstation.nix) is unaffected -- that is a kbd console map, a separate subsystem where "dvorak" is valid. Co-Authored-By: Claude Opus 4.8 (1M context) --- lyrathorpe/home/sway.nix | 8 +++++++- lyrathorpe/swaywm.nix | 12 ++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lyrathorpe/home/sway.nix b/lyrathorpe/home/sway.nix index 0d0d709..7add6c9 100644 --- a/lyrathorpe/home/sway.nix +++ b/lyrathorpe/home/sway.nix @@ -26,7 +26,13 @@ # Launcher: sway-launcher-desktop running inside a floating foot window. menu = "${pkgs.foot}/bin/foot --app-id=launcher ${pkgs.sway-launcher-desktop}/bin/sway-launcher-desktop"; - input."type:keyboard".xkb_layout = "dvorak"; + # Dvorak is a variant of the "us" layout, not a standalone layout -- + # `xkb_layout = "dvorak"` fails to compile (no symbols/dvorak) and wlroots + # silently falls back to QWERTY. Use the variant. + input."type:keyboard" = { + xkb_layout = "us"; + xkb_variant = "dvorak"; + }; window.commands = [ { diff --git a/lyrathorpe/swaywm.nix b/lyrathorpe/swaywm.nix index 200c91c..b90192c 100644 --- a/lyrathorpe/swaywm.nix +++ b/lyrathorpe/swaywm.nix @@ -52,13 +52,17 @@ in # features.swayDesktop (e.g. EDaaS) keep plain TTY login. programs.regreet.enable = true; # cage reads the XKB_* environment at startup, so force the greeter onto the - # same Dvorak layout as the console (workstation.nix) and the Sway session - # (home/sway.nix) -- otherwise the password field would be QWERTY. This - # overrides the greetd command regreet sets with mkDefault. + # same Dvorak layout as the Sway session (home/sway.nix) -- otherwise the + # password field would be QWERTY. Dvorak is the "us" layout's variant, NOT a + # layout of its own: "dvorak" alone has no symbols/ file, so the keymap + # fails to compile and the greeter ends up with no keyboard at all (the + # greeter has no fallback, unlike a running Sway session). This overrides the + # greetd command regreet sets with mkDefault. services.greetd.settings.default_session.command = let greeter = pkgs.writeShellScript "regreet-cage" '' - export XKB_DEFAULT_LAYOUT=dvorak + export XKB_DEFAULT_LAYOUT=us + export XKB_DEFAULT_VARIANT=dvorak exec ${pkgs.dbus}/bin/dbus-run-session ${lib.getExe pkgs.cage} -s -- ${lib.getExe config.programs.regreet.package} ''; in -- 2.52.0 From cf384c60509eeff22375b7c54a747ec58402dd63 Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Tue, 9 Jun 2026 18:43:07 +0100 Subject: [PATCH 09/21] fix(sway): bind the launcher to Mod+Space Only `menu` was set, which the module's default keybindings run on Mod+d; Mod+Space defaulted to `focus mode_toggle`, so sway-launcher-desktop was never reachable from Mod+Space. Add an explicit Mod+Space -> exec ${menu} binding at normal priority (via mkMerge) so it overrides the default. Mod+d still launches it as well. Co-Authored-By: Claude Opus 4.8 (1M context) --- lyrathorpe/home/sway.nix | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/lyrathorpe/home/sway.nix b/lyrathorpe/home/sway.nix index 7add6c9..fa7e9bb 100644 --- a/lyrathorpe/home/sway.nix +++ b/lyrathorpe/home/sway.nix @@ -55,20 +55,27 @@ } ]; - keybindings = lib.mkOptionDefault ( - { - "${modifier}+l" = "exec ${pkgs.swaylock}/bin/swaylock -f"; - "Print" = "exec ${pkgs.grim}/bin/grim ~/screenshot-$(date +%F-%H%M%S).png"; - "XF86AudioRaiseVolume" = "exec ${pkgs.wireplumber}/bin/wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+"; - "XF86AudioLowerVolume" = "exec ${pkgs.wireplumber}/bin/wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"; - "XF86AudioMute" = "exec ${pkgs.wireplumber}/bin/wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"; - } - # Screen backlight: laptops only (no internal backlight on a desktop). - // lib.optionalAttrs portable { - "XF86MonBrightnessUp" = "exec ${pkgs.brightnessctl}/bin/brightnessctl set 5%+"; - "XF86MonBrightnessDown" = "exec ${pkgs.brightnessctl}/bin/brightnessctl set 5%-"; - } - ); + keybindings = lib.mkMerge [ + # Bind the launcher to Mod+Space. This deliberately replaces the default + # Mod+Space (focus mode_toggle); a bare attrset (normal priority) wins + # over the module default, whereas the mkOptionDefault block below would + # only tie with it. Mod+d still runs the launcher too (module default). + { "${modifier}+space" = "exec ${menu}"; } + (lib.mkOptionDefault ( + { + "${modifier}+l" = "exec ${pkgs.swaylock}/bin/swaylock -f"; + "Print" = "exec ${pkgs.grim}/bin/grim ~/screenshot-$(date +%F-%H%M%S).png"; + "XF86AudioRaiseVolume" = "exec ${pkgs.wireplumber}/bin/wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+"; + "XF86AudioLowerVolume" = "exec ${pkgs.wireplumber}/bin/wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"; + "XF86AudioMute" = "exec ${pkgs.wireplumber}/bin/wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"; + } + # Screen backlight: laptops only (no internal backlight on a desktop). + // lib.optionalAttrs portable { + "XF86MonBrightnessUp" = "exec ${pkgs.brightnessctl}/bin/brightnessctl set 5%+"; + "XF86MonBrightnessDown" = "exec ${pkgs.brightnessctl}/bin/brightnessctl set 5%-"; + } + )) + ]; }; }; -- 2.52.0 From 91e3ccb85b2642c9e6dbd6654f5cc34b0300c65f Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Tue, 9 Jun 2026 19:00:36 +0100 Subject: [PATCH 10/21] feat(sway): unify the desktop on Catppuccin Mocha Replace the mismatched theming (gruvbox i3status-rust, unthemed foot, default Sway borders) with a single Catppuccin Mocha palette so the desktop matches the Vim colorscheme. A `ctp` let-binding holds the raw hex once; consumers add "#" as needed. Themed: foot (16-colour + selection/cursor), i3status-rust ("plain" base + overrides, idle blocks on mantle, loud bg only for warning/critical), Sway window borders and the bar/workspace buttons, swaylock (full ring/inside/text set) and dunst (base/text bg, blue/peach frames). Lives in the shared home/sway.nix, so every Sway host is themed consistently. Vim already uses catppuccin_mocha, so the editor is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- lyrathorpe/home/sway.nix | 188 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 183 insertions(+), 5 deletions(-) diff --git a/lyrathorpe/home/sway.nix b/lyrathorpe/home/sway.nix index fa7e9bb..2a86e58 100644 --- a/lyrathorpe/home/sway.nix +++ b/lyrathorpe/home/sway.nix @@ -14,6 +14,33 @@ portable ? true, ... }: +let + # Catppuccin Mocha. Raw 6-digit hex (no leading "#"); prefix "#" where a + # consumer needs it -- Sway/i3status/dunst want "#", foot/swaylock do not. + ctp = { + base = "1e1e2e"; + mantle = "181825"; + crust = "11111b"; + surface0 = "313244"; + surface1 = "45475a"; + surface2 = "585b70"; + overlay0 = "6c7086"; + subtext0 = "a6adc8"; + subtext1 = "bac2de"; + text = "cdd6f4"; + rosewater = "f5e0dc"; + red = "f38ba8"; + maroon = "eba0ac"; + peach = "fab387"; + yellow = "f9e2af"; + green = "a6e3a1"; + teal = "94e2d5"; + sapphire = "74c7ec"; + blue = "89b4fa"; + mauve = "cba6f7"; + pink = "f5c2e7"; + }; +in { wayland.windowManager.sway = { enable = true; @@ -34,6 +61,38 @@ xkb_variant = "dvorak"; }; + # Window borders -- Catppuccin Mocha (blue accent on the focused window). + colors = { + focused = { + border = "#${ctp.blue}"; + background = "#${ctp.base}"; + text = "#${ctp.text}"; + indicator = "#${ctp.blue}"; + childBorder = "#${ctp.blue}"; + }; + focusedInactive = { + border = "#${ctp.surface0}"; + background = "#${ctp.base}"; + text = "#${ctp.subtext0}"; + indicator = "#${ctp.surface0}"; + childBorder = "#${ctp.surface0}"; + }; + unfocused = { + border = "#${ctp.surface0}"; + background = "#${ctp.base}"; + text = "#${ctp.subtext0}"; + indicator = "#${ctp.surface0}"; + childBorder = "#${ctp.surface0}"; + }; + urgent = { + border = "#${ctp.red}"; + background = "#${ctp.base}"; + text = "#${ctp.text}"; + indicator = "#${ctp.red}"; + childBorder = "#${ctp.red}"; + }; + }; + window.commands = [ { criteria.app_id = "launcher"; @@ -52,6 +111,33 @@ ]; size = 11.0; }; + # Bar background + workspace buttons -- Catppuccin Mocha. The mantle + # background matches i3status-rust's idle_bg below for a uniform strip. + colors = { + background = "#${ctp.mantle}"; + statusline = "#${ctp.text}"; + separator = "#${ctp.surface0}"; + focusedWorkspace = { + border = "#${ctp.blue}"; + background = "#${ctp.blue}"; + text = "#${ctp.base}"; + }; + activeWorkspace = { + border = "#${ctp.surface0}"; + background = "#${ctp.surface0}"; + text = "#${ctp.text}"; + }; + inactiveWorkspace = { + border = "#${ctp.mantle}"; + background = "#${ctp.mantle}"; + text = "#${ctp.subtext0}"; + }; + urgentWorkspace = { + border = "#${ctp.red}"; + background = "#${ctp.red}"; + text = "#${ctp.base}"; + }; + }; } ]; @@ -79,13 +165,70 @@ }; }; - programs.swaylock = { + # Terminal: Catppuccin Mocha. foot reads ~/.config/foot/foot.ini; the Sway + # `terminal` above still launches the same binary, now themed. + programs.foot = { enable = true; settings = { - color = "1e1e2e"; + colors = { + background = ctp.base; + foreground = ctp.text; + regular0 = ctp.surface1; + regular1 = ctp.red; + regular2 = ctp.green; + regular3 = ctp.yellow; + regular4 = ctp.blue; + regular5 = ctp.pink; + regular6 = ctp.teal; + regular7 = ctp.subtext1; + bright0 = ctp.surface2; + bright1 = ctp.red; + bright2 = ctp.green; + bright3 = ctp.yellow; + bright4 = ctp.blue; + bright5 = ctp.pink; + bright6 = ctp.teal; + bright7 = ctp.subtext0; + "selection-foreground" = ctp.base; + "selection-background" = ctp.rosewater; + }; + cursor.color = "${ctp.base} ${ctp.rosewater}"; + }; + }; + + programs.swaylock = { + enable = true; + # Catppuccin Mocha (swaylock colours are hex without "#"). + settings = { + color = ctp.base; indicator-radius = 100; indicator-thickness = 7; show-failed-attempts = true; + font = "Noto Sans"; + + inside-color = ctp.base; + inside-clear-color = ctp.base; + inside-ver-color = ctp.base; + inside-wrong-color = ctp.base; + + ring-color = ctp.surface1; + ring-clear-color = ctp.yellow; + ring-ver-color = ctp.blue; + ring-wrong-color = ctp.red; + + key-hl-color = ctp.blue; + bs-hl-color = ctp.red; + + line-color = ctp.base; + line-clear-color = ctp.base; + line-ver-color = ctp.base; + line-wrong-color = ctp.base; + separator-color = "00000000"; + + text-color = ctp.text; + text-clear-color = ctp.text; + text-ver-color = ctp.text; + text-wrong-color = ctp.text; }; }; @@ -111,16 +254,30 @@ services.dunst = { enable = true; + # Catppuccin Mocha notifications (dunst colours need a leading "#"). settings = { global = { font = "Noto Sans 11"; - frame_color = "#89b4fa"; + frame_color = "#${ctp.blue}"; + frame_width = 2; separator_color = "frame"; offset = "10x10"; corner_radius = 5; }; + urgency_low = { + background = "#${ctp.base}"; + foreground = "#${ctp.text}"; + frame_color = "#${ctp.surface1}"; + }; + urgency_normal = { + background = "#${ctp.base}"; + foreground = "#${ctp.text}"; + frame_color = "#${ctp.blue}"; + }; urgency_critical = { - frame_color = "#fab387"; + background = "#${ctp.base}"; + foreground = "#${ctp.text}"; + frame_color = "#${ctp.peach}"; timeout = 0; }; }; @@ -129,8 +286,29 @@ programs.i3status-rust = { enable = true; bars.default = { - theme = "gruvbox-dark"; + # Catppuccin Mocha: a flat "plain" base recoloured via overrides. Idle + # blocks sit on mantle (matching the Sway bar background) with light text; + # only warning/critical states get a loud tinted background. The `theme` + # bar option is shallow-merged away by `settings.theme`, so set the base + # theme and its overrides together here. icons = "awesome6"; + settings.theme = { + theme = "plain"; + overrides = { + idle_bg = "#${ctp.mantle}"; + idle_fg = "#${ctp.text}"; + info_bg = "#${ctp.mantle}"; + info_fg = "#${ctp.blue}"; + good_bg = "#${ctp.mantle}"; + good_fg = "#${ctp.green}"; + warning_bg = "#${ctp.peach}"; + warning_fg = "#${ctp.base}"; + critical_bg = "#${ctp.red}"; + critical_fg = "#${ctp.base}"; + separator_bg = "#${ctp.mantle}"; + separator_fg = "#${ctp.surface1}"; + }; + }; blocks = [ { block = "disk_space"; -- 2.52.0 From ab48a14ec01118cf74c1ba83d3dfb0030be3f74b Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Tue, 9 Jun 2026 19:41:17 +0100 Subject: [PATCH 11/21] feat(sway): theme the ReGreet greeter to match (Catppuccin Mocha) Factor the Catppuccin Mocha palette into lyrathorpe/catppuccin-mocha.nix so the desktop (home/sway.nix) and the system greeter (swaywm.nix) share one source of truth, then theme ReGreet from it: GTK CSS (libadwaita named colours + plain node selectors for window/entry/button/combobox) plus Noto Sans to match the bar and notifications. Verified the rendered /etc/greetd/regreet.css and regreet.toml (font_name = "Noto Sans 16"), and that foot still resolves its colours through the shared import. Co-Authored-By: Claude Opus 4.8 (1M context) --- lyrathorpe/catppuccin-mocha.nix | 26 ++++++++++++++ lyrathorpe/home/sway.nix | 29 +++------------ lyrathorpe/swaywm.nix | 62 +++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 25 deletions(-) create mode 100644 lyrathorpe/catppuccin-mocha.nix diff --git a/lyrathorpe/catppuccin-mocha.nix b/lyrathorpe/catppuccin-mocha.nix new file mode 100644 index 0000000..d621b95 --- /dev/null +++ b/lyrathorpe/catppuccin-mocha.nix @@ -0,0 +1,26 @@ +# Catppuccin Mocha palette. Raw 6-digit hex (no leading "#"); consumers add a +# "#" where their format needs it. Shared by the Sway desktop theming +# (home/sway.nix) and the ReGreet greeter (swaywm.nix) so the two stay in sync. +{ + base = "1e1e2e"; + mantle = "181825"; + crust = "11111b"; + surface0 = "313244"; + surface1 = "45475a"; + surface2 = "585b70"; + overlay0 = "6c7086"; + subtext0 = "a6adc8"; + subtext1 = "bac2de"; + text = "cdd6f4"; + rosewater = "f5e0dc"; + red = "f38ba8"; + maroon = "eba0ac"; + peach = "fab387"; + yellow = "f9e2af"; + green = "a6e3a1"; + teal = "94e2d5"; + sapphire = "74c7ec"; + blue = "89b4fa"; + mauve = "cba6f7"; + pink = "f5c2e7"; +} diff --git a/lyrathorpe/home/sway.nix b/lyrathorpe/home/sway.nix index 2a86e58..9ba4acd 100644 --- a/lyrathorpe/home/sway.nix +++ b/lyrathorpe/home/sway.nix @@ -15,31 +15,10 @@ ... }: let - # Catppuccin Mocha. Raw 6-digit hex (no leading "#"); prefix "#" where a - # consumer needs it -- Sway/i3status/dunst want "#", foot/swaylock do not. - ctp = { - base = "1e1e2e"; - mantle = "181825"; - crust = "11111b"; - surface0 = "313244"; - surface1 = "45475a"; - surface2 = "585b70"; - overlay0 = "6c7086"; - subtext0 = "a6adc8"; - subtext1 = "bac2de"; - text = "cdd6f4"; - rosewater = "f5e0dc"; - red = "f38ba8"; - maroon = "eba0ac"; - peach = "fab387"; - yellow = "f9e2af"; - green = "a6e3a1"; - teal = "94e2d5"; - sapphire = "74c7ec"; - blue = "89b4fa"; - mauve = "cba6f7"; - pink = "f5c2e7"; - }; + # Catppuccin Mocha (shared with the ReGreet greeter). Raw hex; prefix "#" + # where a consumer needs it -- Sway/i3status/dunst want "#", foot/swaylock do + # not. + ctp = import ../catppuccin-mocha.nix; in { wayland.windowManager.sway = { diff --git a/lyrathorpe/swaywm.nix b/lyrathorpe/swaywm.nix index b90192c..84e9bf4 100644 --- a/lyrathorpe/swaywm.nix +++ b/lyrathorpe/swaywm.nix @@ -7,6 +7,8 @@ let cfg = config.features.swayDesktop; + # Catppuccin Mocha (shared with the Sway desktop, see lyrathorpe/home/sway.nix). + ctp = import ./catppuccin-mocha.nix; in { options = { @@ -51,6 +53,66 @@ in # services.displayManager.sessionPackages. Hosts that turn off # features.swayDesktop (e.g. EDaaS) keep plain TTY login. programs.regreet.enable = true; + # Theme the greeter to match the Sway desktop (Catppuccin Mocha). ReGreet is + # GTK; recolour via CSS (covering both libadwaita named colours and plain + # GTK node selectors) and use the same Noto Sans as the bar/notifications. + programs.regreet.font = { + name = "Noto Sans"; + package = pkgs.noto-fonts; + size = 16; + }; + programs.regreet.extraCss = '' + @define-color window_bg_color #${ctp.base}; + @define-color window_fg_color #${ctp.text}; + @define-color view_bg_color #${ctp.mantle}; + @define-color view_fg_color #${ctp.text}; + @define-color card_bg_color #${ctp.surface0}; + @define-color card_fg_color #${ctp.text}; + @define-color accent_bg_color #${ctp.blue}; + @define-color accent_fg_color #${ctp.base}; + @define-color accent_color #${ctp.blue}; + @define-color destructive_bg_color #${ctp.red}; + @define-color destructive_fg_color #${ctp.base}; + @define-color error_color #${ctp.red}; + @define-color warning_color #${ctp.peach}; + @define-color success_color #${ctp.green}; + + window { + background-color: #${ctp.base}; + color: #${ctp.text}; + } + + label { + color: #${ctp.text}; + } + + entry { + background-color: #${ctp.surface0}; + color: #${ctp.text}; + border: 1px solid #${ctp.surface1}; + } + + entry:focus-within { + border-color: #${ctp.blue}; + } + + button, + combobox button { + background-color: #${ctp.surface0}; + color: #${ctp.text}; + border: 1px solid #${ctp.surface1}; + } + + button:hover { + background-color: #${ctp.surface1}; + } + + button:active, + button:checked { + background-color: #${ctp.blue}; + color: #${ctp.base}; + } + ''; # cage reads the XKB_* environment at startup, so force the greeter onto the # same Dvorak layout as the Sway session (home/sway.nix) -- otherwise the # password field would be QWERTY. Dvorak is the "us" layout's variant, NOT a -- 2.52.0 From 00d314411da5fe33bad30cc7df32188cc8a87477 Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Tue, 9 Jun 2026 19:41:17 +0100 Subject: [PATCH 12/21] refactor(sway): drop the dead tty1 Sway autostart greetd now owns tty1 and launches the Sway session, so the zsh initContent that exec'd sway on tty1 login can never fire. Remove it (and the now-unused lib arg), and refresh the module header (login is via the greeter; host list MBP/T400/Mac Pro, no longer "X1"). Co-Authored-By: Claude Opus 4.8 (1M context) --- lyrathorpe/home/desktop.nix | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/lyrathorpe/home/desktop.nix b/lyrathorpe/home/desktop.nix index f77ee2c..a2329a3 100644 --- a/lyrathorpe/home/desktop.nix +++ b/lyrathorpe/home/desktop.nix @@ -1,7 +1,8 @@ -# Graphical desktop layer: GUI apps, Wayland session env, cursor theme, and the -# tty1 Sway autostart. Imported only on hosts that run Sway (MBP, X1); never -# pulled onto the headless WSL host. -{ pkgs, lib, ... }: +# Graphical desktop layer: GUI apps, Wayland session env, and cursor theme. +# Imported only on hosts that run Sway (MBP, T400, Mac Pro); never pulled onto +# the headless WSL host. Login (and the Sway session launch) is handled by the +# greetd/ReGreet greeter -- see ../swaywm.nix -- so there is no tty1 autostart. +{ pkgs, ... }: { imports = [ ./sway.nix @@ -29,11 +30,4 @@ name = "Adwaita"; size = 24; }; - - # Start Sway automatically on the first virtual terminal. - programs.zsh.initContent = lib.mkOrder 1500 '' - if [ -z "$DISPLAY" ] && [ "$(tty)" = "/dev/tty1" ]; then - exec sway - fi - ''; } -- 2.52.0 From 4858de0a073edbd7d9996305f28f72db1c2949e1 Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Tue, 9 Jun 2026 19:49:13 +0100 Subject: [PATCH 13/21] fix(sway): force a dark base for the ReGreet greeter theme ReGreet is plain GTK4 (no libadwaita in its closure), so it defaulted to light Adwaita and my libadwaita-named colour overrides (window_bg_color, accent_bg_color, ...) were inert -- a light theme with stray accents. Force the dark Adwaita variant via GTK_THEME=Adwaita:dark in the greeter wrapper, and override the GTK4 legacy colour names (theme_bg_color, theme_fg_color, theme_selected_bg_color, borders, ...) that plain GTK4 actually references. The libadwaita names stay as harmless forward-compat. Co-Authored-By: Claude Opus 4.8 (1M context) --- lyrathorpe/swaywm.nix | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/lyrathorpe/swaywm.nix b/lyrathorpe/swaywm.nix index 84e9bf4..3d9fcf2 100644 --- a/lyrathorpe/swaywm.nix +++ b/lyrathorpe/swaywm.nix @@ -62,6 +62,21 @@ in size = 16; }; programs.regreet.extraCss = '' + /* GTK4 Adwaita legacy names (what plain GTK4 actually references). */ + @define-color theme_bg_color #${ctp.base}; + @define-color theme_fg_color #${ctp.text}; + @define-color theme_base_color #${ctp.mantle}; + @define-color theme_text_color #${ctp.text}; + @define-color theme_selected_bg_color #${ctp.blue}; + @define-color theme_selected_fg_color #${ctp.base}; + @define-color insensitive_bg_color #${ctp.mantle}; + @define-color insensitive_fg_color #${ctp.overlay0}; + @define-color borders #${ctp.surface1}; + @define-color warning_color #${ctp.peach}; + @define-color error_color #${ctp.red}; + @define-color success_color #${ctp.green}; + + /* libadwaita names (inert on plain GTK4, kept for forward-compat). */ @define-color window_bg_color #${ctp.base}; @define-color window_fg_color #${ctp.text}; @define-color view_bg_color #${ctp.mantle}; @@ -73,9 +88,6 @@ in @define-color accent_color #${ctp.blue}; @define-color destructive_bg_color #${ctp.red}; @define-color destructive_fg_color #${ctp.base}; - @define-color error_color #${ctp.red}; - @define-color warning_color #${ctp.peach}; - @define-color success_color #${ctp.green}; window { background-color: #${ctp.base}; @@ -125,6 +137,9 @@ in greeter = pkgs.writeShellScript "regreet-cage" '' export XKB_DEFAULT_LAYOUT=us export XKB_DEFAULT_VARIANT=dvorak + # ReGreet is plain GTK4 (no libadwaita); force the dark Adwaita variant + # so the extraCss accents sit on a dark base instead of light Adwaita. + export GTK_THEME=Adwaita:dark exec ${pkgs.dbus}/bin/dbus-run-session ${lib.getExe pkgs.cage} -s -- ${lib.getExe config.programs.regreet.package} ''; in -- 2.52.0 From cee083fbfa479f5610a15fba0b9174bef33bb3db Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Tue, 9 Jun 2026 20:03:59 +0100 Subject: [PATCH 14/21] fix(zsh): bind history-substring-search for CSI and SS3 arrows historySubstringSearch bound only the default CSI sequences (^[[A/^[[B), so Up/Down did nothing at the prompt in foot and iTerm2, which send the SS3 application-mode sequences (^[OA/^[OB). Bind both forms via searchUpKey/searchDownKey; this covers foot, iTerm2 and the Linux TTY (CSI). Co-Authored-By: Claude Opus 4.8 (1M context) --- lyrathorpe/home/shell.nix | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lyrathorpe/home/shell.nix b/lyrathorpe/home/shell.nix index 2eb0679..0403dc9 100644 --- a/lyrathorpe/home/shell.nix +++ b/lyrathorpe/home/shell.nix @@ -6,7 +6,21 @@ enableCompletion = true; enableVteIntegration = true; autosuggestion.enable = true; - historySubstringSearch.enable = true; + # Bind Up/Down for history-substring-search in BOTH cursor-key modes: CSI + # (^[[A/^[[B -- normal mode, and what the Linux TTY sends) and SS3 + # (^[OA/^[OB -- application mode, used by foot, tmux and iTerm2). Binding + # only the default CSI form leaves it dead at the prompt in foot/iTerm2. + historySubstringSearch = { + enable = true; + searchUpKey = [ + "^[[A" + "^[OA" + ]; + searchDownKey = [ + "^[[B" + "^[OB" + ]; + }; history.append = true; oh-my-zsh = { enable = true; -- 2.52.0 From 77e14968eec32d31327c2ff68bebabdfcfb8d7a8 Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Tue, 9 Jun 2026 20:04:52 +0100 Subject: [PATCH 15/21] fix(sway): restore clobbered keybindings; update foot for 1.27 Two desktop-config fixes found together: 1. Keybindings: the previous Mod+Space launcher bind wrapped the set in `lib.mkMerge [ { ... } (mkOptionDefault ...) ]`. A normal-priority definition there wins over (and discards) the home-manager module's default keybindings, which are at mkOptionDefault priority -- so every default bind (terminal Mod+Return, movement, workspaces, kill, ...) and even the custom swaylock/volume binds silently vanished; only Mod+Space survived. That is why Super+Enter opened nothing. Restore the single mkOptionDefault wrapper (so it merges with the module defaults) and override just Mod+Space via lib.mkForce to beat the module's default focus-mode_toggle without a same-priority conflict. Generated config goes from 1 bindsym back to 57. 2. foot 1.27: the bare [colors] section is deprecated in favour of [colors-dark], and `[cursor] color` is rejected ("not a valid option: color"). Move the palette to colors-dark and set the cursor colour via its `cursor` key (" "). `foot --check-config` now passes. Co-Authored-By: Claude Opus 4.8 (1M context) --- lyrathorpe/home/sway.nix | 53 +++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/lyrathorpe/home/sway.nix b/lyrathorpe/home/sway.nix index 9ba4acd..27e6772 100644 --- a/lyrathorpe/home/sway.nix +++ b/lyrathorpe/home/sway.nix @@ -120,27 +120,29 @@ in } ]; - keybindings = lib.mkMerge [ - # Bind the launcher to Mod+Space. This deliberately replaces the default - # Mod+Space (focus mode_toggle); a bare attrset (normal priority) wins - # over the module default, whereas the mkOptionDefault block below would - # only tie with it. Mod+d still runs the launcher too (module default). - { "${modifier}+space" = "exec ${menu}"; } - (lib.mkOptionDefault ( - { - "${modifier}+l" = "exec ${pkgs.swaylock}/bin/swaylock -f"; - "Print" = "exec ${pkgs.grim}/bin/grim ~/screenshot-$(date +%F-%H%M%S).png"; - "XF86AudioRaiseVolume" = "exec ${pkgs.wireplumber}/bin/wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+"; - "XF86AudioLowerVolume" = "exec ${pkgs.wireplumber}/bin/wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"; - "XF86AudioMute" = "exec ${pkgs.wireplumber}/bin/wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"; - } - # Screen backlight: laptops only (no internal backlight on a desktop). - // lib.optionalAttrs portable { - "XF86MonBrightnessUp" = "exec ${pkgs.brightnessctl}/bin/brightnessctl set 5%+"; - "XF86MonBrightnessDown" = "exec ${pkgs.brightnessctl}/bin/brightnessctl set 5%-"; - } - )) - ]; + # NB: this whole set is wrapped in mkOptionDefault so it MERGES with the + # home-manager module's default keybindings (same priority) rather than + # replacing them. Do not wrap it in mkMerge with a normal-priority attr -- + # that makes the normal-priority def win and silently drops every default + # bind (terminal, movement, workspaces, ...). + keybindings = lib.mkOptionDefault ( + { + # Launcher on Mod+Space. mkForce overrides the module's own default + # Mod+Space (focus mode_toggle); a plain value would conflict with it + # at equal priority. Mod+d also still runs the launcher (module default). + "${modifier}+space" = lib.mkForce "exec ${menu}"; + "${modifier}+l" = "exec ${pkgs.swaylock}/bin/swaylock -f"; + "Print" = "exec ${pkgs.grim}/bin/grim ~/screenshot-$(date +%F-%H%M%S).png"; + "XF86AudioRaiseVolume" = "exec ${pkgs.wireplumber}/bin/wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+"; + "XF86AudioLowerVolume" = "exec ${pkgs.wireplumber}/bin/wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"; + "XF86AudioMute" = "exec ${pkgs.wireplumber}/bin/wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"; + } + # Screen backlight: laptops only (no internal backlight on a desktop). + // lib.optionalAttrs portable { + "XF86MonBrightnessUp" = "exec ${pkgs.brightnessctl}/bin/brightnessctl set 5%+"; + "XF86MonBrightnessDown" = "exec ${pkgs.brightnessctl}/bin/brightnessctl set 5%-"; + } + ); }; }; @@ -148,8 +150,13 @@ in # `terminal` above still launches the same binary, now themed. programs.foot = { enable = true; + # foot 1.27: the bare [colors] section is deprecated in favour of + # [colors-dark] (the default theme), and the cursor colour moved out of + # [cursor] (where `color` is now rejected) into a `cursor` key here, written + # " " (man foot.ini(5): "ff0000 00ff00" => green cursor, red + # text). Only colors-dark is needed; we never set initial-color-theme=light. settings = { - colors = { + "colors-dark" = { background = ctp.base; foreground = ctp.text; regular0 = ctp.surface1; @@ -170,8 +177,8 @@ in bright7 = ctp.subtext0; "selection-foreground" = ctp.base; "selection-background" = ctp.rosewater; + cursor = "${ctp.base} ${ctp.rosewater}"; }; - cursor.color = "${ctp.base} ${ctp.rosewater}"; }; }; -- 2.52.0 From 2cbcc3d7f17040e5ca4d629df51fa2f7ad3897ed Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Tue, 9 Jun 2026 20:30:32 +0100 Subject: [PATCH 16/21] feat(sway): add the nemo file manager (themed, Mod+e) Install nemo and bind it to Mod+e (mkForce, overriding the module default `layout toggle split`). Add a home-manager `gtk` block so GTK apps match the desktop: the Catppuccin Mocha GTK theme (catppuccin-gtk, mocha/blue), with Adwaita icons + cursor as before. Under Sway GTK reads ~/.config/gtk-*/settings.ini directly (no XSettings daemon), so this themes nemo without extra env. Co-Authored-By: Claude Opus 4.8 (1M context) --- lyrathorpe/home/desktop.nix | 20 ++++++++++++++++++++ lyrathorpe/home/sway.nix | 2 ++ 2 files changed, 22 insertions(+) diff --git a/lyrathorpe/home/desktop.nix b/lyrathorpe/home/desktop.nix index a2329a3..147fef5 100644 --- a/lyrathorpe/home/desktop.nix +++ b/lyrathorpe/home/desktop.nix @@ -11,6 +11,7 @@ home.packages = [ pkgs.element-desktop pkgs.legcord + pkgs.nemo # file manager (launched via Mod+e, see ./sway.nix) #pkgs.plex-desktop #pkgs.plexamp ]; @@ -20,6 +21,25 @@ XDG_CURRENT_DESKTOP = "sway"; }; + # Theme GTK apps (nemo, etc.) to match the Catppuccin Mocha desktop. Under + # Sway there is no XSettings daemon, so GTK reads these from the generated + # ~/.config/gtk-{3,4}.0/settings.ini directly. The Mocha theme is dark by + # design, so no separate prefer-dark hint is needed. + gtk = { + enable = true; + theme = { + name = "catppuccin-mocha-blue-standard"; + package = pkgs.catppuccin-gtk.override { + accents = [ "blue" ]; + variant = "mocha"; + }; + }; + iconTheme = { + name = "Adwaita"; + package = pkgs.adwaita-icon-theme; + }; + }; + home.pointerCursor = { gtk.enable = true; x11 = { diff --git a/lyrathorpe/home/sway.nix b/lyrathorpe/home/sway.nix index 27e6772..324640e 100644 --- a/lyrathorpe/home/sway.nix +++ b/lyrathorpe/home/sway.nix @@ -131,6 +131,8 @@ in # Mod+Space (focus mode_toggle); a plain value would conflict with it # at equal priority. Mod+d also still runs the launcher (module default). "${modifier}+space" = lib.mkForce "exec ${menu}"; + # File manager. mkForce overrides the module default (layout toggle split). + "${modifier}+e" = lib.mkForce "exec ${pkgs.nemo}/bin/nemo"; "${modifier}+l" = "exec ${pkgs.swaylock}/bin/swaylock -f"; "Print" = "exec ${pkgs.grim}/bin/grim ~/screenshot-$(date +%F-%H%M%S).png"; "XF86AudioRaiseVolume" = "exec ${pkgs.wireplumber}/bin/wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+"; -- 2.52.0 From b04391809ee1ff63bb2fe1f94062ba56d45ec83e Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Tue, 9 Jun 2026 20:49:36 +0100 Subject: [PATCH 17/21] feat(sway): port keybindings, modes and tweaks from dotfiles Adapted from emmaisadev/dotfiles (sway/config.d) into the Nix config: - Screenshots to swappy: Print = drag a region, Shift+Print = focused window (a writeShellScript reusing the grimshot.sh tree/jq logic). Replaces the old plain full-screen grim->file. - Workspace cycle Mod+z / Mod+x (prev/next). - Media keys (playerctl) and mic mute (wpctl source). - Re-home `focus mode_toggle` onto Mod+Alt+space (Mod+Space is the launcher now). - Clipboard history: services.clipman stores copies; Mod+c picks one through a Catppuccin-themed fuzzel (programs.fuzzel). - Binding modes: a layout submenu (Mod+y -> s/w/e, which also restores split-toggle that Mod+e gave up to nemo) and a power menu (Mod+Shift+x -> lock/exit/sleep/reboot/shutdown). Mod+l still locks immediately. - Touchpad tap + natural scroll (laptops; inert on desktop). - Solid Catppuccin base as the wallpaper (output * bg, no image). - foot: term=xterm-256color and scrollback 100000 (colours unchanged). - swaywm.nix: add slurp/swappy/jq/playerctl to the session packages. Skipped from the dotfiles: named workspaces + app auto-assign, the foot --server/footclient setup, and pactl/MX-Master/lxqt device-specific bits. All in the shared files, so every Sway host gets it. Co-Authored-By: Claude Opus 4.8 (1M context) --- lyrathorpe/home/sway.nix | 119 ++++++++++++++++++++++++++++++++++++++- lyrathorpe/swaywm.nix | 4 ++ 2 files changed, 122 insertions(+), 1 deletion(-) diff --git a/lyrathorpe/home/sway.nix b/lyrathorpe/home/sway.nix index 324640e..118a65f 100644 --- a/lyrathorpe/home/sway.nix +++ b/lyrathorpe/home/sway.nix @@ -19,6 +19,19 @@ let # where a consumer needs it -- Sway/i3status/dunst want "#", foot/swaylock do # not. ctp = import ../catppuccin-mocha.nix; + + # Focused-window screenshot -> swappy editor (the dotfiles' grimshot.sh logic). + # Full store paths so it needs nothing on PATH. + screenshotWindow = pkgs.writeShellScript "screenshot-window" '' + ${pkgs.grim}/bin/grim -g "$(${pkgs.sway}/bin/swaymsg -t get_tree \ + | ${pkgs.jq}/bin/jq -r '.. | select(.focused?) | .rect | "\(.x),\(.y) \(.width)x\(.height)"')" \ + - | ${pkgs.swappy}/bin/swappy -f - + ''; + + # Binding-mode names. The string is both the `modes` attr key and what the + # bar's mode indicator shows, so the keys are spelled out in the label. + layoutMode = "layout: [s]tacking [w]tabbed [e]split"; + systemMode = "system: [l]ock [e]xit [s]leep [r]eboot [Shift+s]shutdown"; in { wayland.windowManager.sway = { @@ -39,6 +52,15 @@ in xkb_layout = "us"; xkb_variant = "dvorak"; }; + # Touchpads (laptops): tap-to-click and natural scrolling. Inert on the + # desktop hosts, which have no touchpad. + input."type:touchpad" = { + tap = "enabled"; + natural_scroll = "enabled"; + }; + + # Solid Catppuccin Mocha base as the wallpaper (no image dependency). + output."*".bg = "#${ctp.base} solid_color"; # Window borders -- Catppuccin Mocha (blue accent on the focused window). colors = { @@ -79,6 +101,33 @@ in } ]; + # Binding modes (submenus). Entered from keybindings below; each action + # returns to the default mode. mkOptionDefault-merged with the module's + # built-in "resize" mode. + modes = { + # Layout submenu (Mod+y). Mirrors Sway's default s/w/e layout keys -- + # notably it restores split-toggle, which moved off Mod+e when that + # became the nemo launcher. + ${layoutMode} = { + "s" = "layout stacking, mode default"; + "w" = "layout tabbed, mode default"; + "e" = "layout toggle split, mode default"; + "Return" = "mode default"; + "Escape" = "mode default"; + }; + # Power menu (Mod+Shift+x). Lock reuses the themed swaylock; the rest go + # through systemd/logind (allowed for the active local session). + ${systemMode} = { + "l" = "exec ${pkgs.swaylock}/bin/swaylock -f, mode default"; + "e" = "exec ${pkgs.sway}/bin/swaymsg exit, mode default"; + "s" = "exec systemctl suspend, mode default"; + "r" = "exec systemctl reboot, mode default"; + "Shift+s" = "exec systemctl poweroff, mode default"; + "Return" = "mode default"; + "Escape" = "mode default"; + }; + }; + bars = [ { position = "top"; @@ -134,10 +183,39 @@ in # File manager. mkForce overrides the module default (layout toggle split). "${modifier}+e" = lib.mkForce "exec ${pkgs.nemo}/bin/nemo"; "${modifier}+l" = "exec ${pkgs.swaylock}/bin/swaylock -f"; - "Print" = "exec ${pkgs.grim}/bin/grim ~/screenshot-$(date +%F-%H%M%S).png"; + + # Cycle workspaces. + "${modifier}+z" = "workspace prev"; + "${modifier}+x" = "workspace next"; + + # focus mode_toggle (tiling <-> floating focus) -- re-homed off + # Mod+Space, which is now the launcher. + "${modifier}+Mod1+space" = "focus mode_toggle"; + + # Enter the binding-mode submenus defined above. + "${modifier}+y" = "mode \"${layoutMode}\""; + "${modifier}+Shift+x" = "mode \"${systemMode}\""; + + # Clipboard history: pick a past entry through fuzzel (clipman stores + # it -- see services.clipman below). + "${modifier}+c" = + "exec ${pkgs.clipman}/bin/clipman pick -t CUSTOM --tool-args=\"${pkgs.fuzzel}/bin/fuzzel --dmenu\""; + + # Screenshots -> swappy editor: Print = drag a region, Shift+Print = + # the focused window. + "Print" = + "exec ${pkgs.grim}/bin/grim -g \"$(${pkgs.slurp}/bin/slurp)\" - | ${pkgs.swappy}/bin/swappy -f -"; + "Shift+Print" = "exec ${screenshotWindow}"; + "XF86AudioRaiseVolume" = "exec ${pkgs.wireplumber}/bin/wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+"; "XF86AudioLowerVolume" = "exec ${pkgs.wireplumber}/bin/wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"; "XF86AudioMute" = "exec ${pkgs.wireplumber}/bin/wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"; + "XF86AudioMicMute" = "exec ${pkgs.wireplumber}/bin/wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"; + + # Media keys (MPRIS via playerctl). + "XF86AudioPlay" = "exec ${pkgs.playerctl}/bin/playerctl play-pause"; + "XF86AudioNext" = "exec ${pkgs.playerctl}/bin/playerctl next"; + "XF86AudioPrev" = "exec ${pkgs.playerctl}/bin/playerctl previous"; } # Screen backlight: laptops only (no internal backlight on a desktop). // lib.optionalAttrs portable { @@ -158,6 +236,13 @@ in # " " (man foot.ini(5): "ff0000 00ff00" => green cursor, red # text). Only colors-dark is needed; we never set initial-color-theme=light. settings = { + main = { + # Advertise as xterm-256color so remote hosts without foot's terminfo + # still behave (tmux re-adds foot's RGB/sync/etc. features -- see + # shell.nix). The [main] section is the unheadered top of foot.ini. + term = "xterm-256color"; + }; + scrollback.lines = 100000; "colors-dark" = { background = ctp.base; foreground = ctp.text; @@ -184,6 +269,38 @@ in }; }; + # Clipboard history: a user service runs `wl-paste --watch clipman store`, + # bound to the Wayland session, so copies persist and Mod+c (above) can pick + # an old entry through fuzzel. + services.clipman.enable = true; + + # fuzzel: the dmenu picker used by clipman, themed Catppuccin Mocha to match + # (fuzzel colours are RRGGBBAA -- 8 hex digits). + programs.fuzzel = { + enable = true; + settings = { + main = { + font = "Noto Sans:size=12"; + prompt = "\"clipboard \""; + }; + border = { + width = 2; + radius = 8; + }; + colors = { + background = "${ctp.base}f0"; + text = "${ctp.text}ff"; + prompt = "${ctp.subtext0}ff"; + input = "${ctp.text}ff"; + match = "${ctp.blue}ff"; + selection = "${ctp.surface1}ff"; + selection-text = "${ctp.text}ff"; + selection-match = "${ctp.blue}ff"; + border = "${ctp.blue}ff"; + }; + }; + }; + programs.swaylock = { enable = true; # Catppuccin Mocha (swaylock colours are hex without "#"). diff --git a/lyrathorpe/swaywm.nix b/lyrathorpe/swaywm.nix index 3d9fcf2..9c5551b 100644 --- a/lyrathorpe/swaywm.nix +++ b/lyrathorpe/swaywm.nix @@ -37,6 +37,10 @@ in brightnessctl foot grim + slurp # region selection for screenshots + swappy # screenshot annotation/save + jq # used by the focused-window screenshot bind + playerctl # MPRIS media keys sway-launcher-desktop pavucontrol ]; -- 2.52.0 From e7e11c17b00134466745a217ea3195ea6ddc51a4 Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Tue, 9 Jun 2026 20:49:36 +0100 Subject: [PATCH 18/21] feat(tmux): apply dotfiles tmux config From emmaisadev/dotfiles (tmux.conf): run a non-login shell (default-command), drop the stock %/" split keys (the s/v vim splits already come from reverseSplit), declare foot's terminal-features (RGB/sync/clipboard/title/ccolour/cstyle), and raise the scrollback to 500000. Alt-arrow pane nav and mouse were already present. Also drop a stale comment referencing the removed tty1 autostart. Co-Authored-By: Claude Opus 4.8 (1M context) --- lyrathorpe/home/shell.nix | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/lyrathorpe/home/shell.nix b/lyrathorpe/home/shell.nix index 0403dc9..f9ce679 100644 --- a/lyrathorpe/home/shell.nix +++ b/lyrathorpe/home/shell.nix @@ -31,9 +31,7 @@ theme = "robbyrussell"; }; syntaxHighlighting.enable = true; - # Prefix the prompt with the hostname over SSH. The graphical autostart - # (exec sway on tty1) lives in ./desktop.nix so it never runs on headless - # hosts. + # Prefix the prompt with the hostname over SSH. initContent = lib.mkOrder 1500 '' if [ "$SSH_CLIENT" ] || [ "$SSH_TTY" ]; then export PS1="%M $PS1" @@ -50,14 +48,33 @@ terminal = "tmux-direct"; newSession = true; keyMode = "vi"; - historyLimit = 50000; + historyLimit = 500000; mouse = true; + # `reverseSplit = true` already binds s -> vertical and v -> horizontal + # split (the dotfiles' vim-style splits). extraConfig = '' + # Run a non-login shell in new panes/windows. + set -g default-command "''${SHELL}" + + # Drop the stock split keys in favour of the s/v binds above. + unbind % + unbind '"' + # Alt+Arrow pane navigation bind -n M-Left select-pane -L bind -n M-Right select-pane -R bind -n M-Up select-pane -U bind -n M-Down select-pane -D + + # Tell tmux which capabilities the foot terminal supports, so truecolor, + # synchronised output, the system clipboard (OSC 52), window titles and + # cursor styling all pass through. + set -as terminal-features ",foot*:RGB" + set -as terminal-features ",foot*:sync" + set -as terminal-features ",foot*:clipboard" + set -as terminal-features ",foot*:title" + set -as terminal-features ",foot*:ccolour" + set -as terminal-features ",foot*:cstyle" ''; }; } -- 2.52.0 From e78e52e18db5465a16c7ceb1a97c92bcaff482ed Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Tue, 9 Jun 2026 20:57:15 +0100 Subject: [PATCH 19/21] docs: add a keybindings reference covering Sway/tmux/foot/zsh Document every configured shortcut in lyrathorpe/home/KEYBINDINGS.md, compiled from the rendered configs (so it includes the home-manager Sway module defaults alongside the custom binds and modes), and link it from the top-level README. Notes the Dvorak keysym caveat and the laptop-only brightness keys. Co-Authored-By: Claude Opus 4.8 (1M context) --- README.md | 5 + lyrathorpe/home/KEYBINDINGS.md | 172 +++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 lyrathorpe/home/KEYBINDINGS.md diff --git a/README.md b/README.md index 0895d59..ba0e045 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,11 @@ sudo nixos-rebuild switch --flake .# darwin-rebuild switch --flake .#lyrathorpe-mac ``` +## Keybindings + +All Sway / tmux / foot / zsh keyboard shortcuts are documented in +[`lyrathorpe/home/KEYBINDINGS.md`](./lyrathorpe/home/KEYBINDINGS.md). + ## Login / greeter Graphical (Sway) hosts log in through a Wayland greeter — `greetd` running diff --git a/lyrathorpe/home/KEYBINDINGS.md b/lyrathorpe/home/KEYBINDINGS.md new file mode 100644 index 0000000..dc9a4cf --- /dev/null +++ b/lyrathorpe/home/KEYBINDINGS.md @@ -0,0 +1,172 @@ +# Keybindings reference + +Every keyboard shortcut configured across this desktop, and where it is defined. +Everything here is managed declaratively through Nix — edit the listed file and +rebuild, never the generated dotfiles. + +| Area | Defined in | +| --- | --- | +| Sway (compositor) | [`sway.nix`](./sway.nix) `config.keybindings` + `config.modes`, plus the home-manager Sway module's built-in defaults | +| tmux | [`shell.nix`](./shell.nix) `programs.tmux` | +| zsh line editor | [`shell.nix`](./shell.nix) `programs.zsh.historySubstringSearch` | +| foot (terminal) | foot package defaults — only colours are themed (in `sway.nix`) | + +**Conventions** + +- **Super** is the `Mod4` / logo (Windows/Command) key; **Alt** is `Mod1`. +- Letter keys are **keysyms** (the character produced), not physical positions. + The keyboard is **Dvorak** (`us`/`dvorak`), so e.g. "Super+s" is whatever key + types `s` in Dvorak. +- Shortcuts apply to every Sway host (MBP, T400, Mac Pro); brightness keys are + laptop-only, as noted. + +--- + +## Sway + +### Applications & session + +| Shortcut | Action | +| --- | --- | +| `Super`+`Return` | Open a terminal (foot) | +| `Super`+`Space` | App launcher (sway-launcher-desktop in a floating foot) | +| `Super`+`d` | App launcher (same as above; module default) | +| `Super`+`e` | File manager (nemo) | +| `Super`+`c` | Clipboard history picker (clipman → fuzzel) | +| `Super`+`l` | Lock screen (swaylock) | +| `Super`+`Shift`+`q` | Close the focused window | +| `Super`+`Shift`+`c` | Reload the Sway config | +| `Super`+`Shift`+`e` | Exit Sway (asks for confirmation) | + +### Focus + +| Shortcut | Action | +| --- | --- | +| `Super`+`←`/`↓`/`↑`/`→` | Move focus by direction | +| `Super`+`h`/`j`/`k` | Move focus left / down / up (vim-style) | +| `Super`+`a` | Focus the parent container | +| `Super`+`Alt`+`Space` | Toggle focus between tiling and floating | + +> Note: vim focus-right would be `Super`+`l`, but that is bound to **lock** here; +> use `Super`+`→`. + +### Moving windows + +| Shortcut | Action | +| --- | --- | +| `Super`+`Shift`+`←`/`↓`/`↑`/`→` | Move the window by direction | +| `Super`+`Shift`+`h`/`j`/`k`/`l` | Move the window left / down / up / right | +| `Super`+`Shift`+`Space` | Toggle the window floating | + +Mouse (with `Super` held): left-drag moves a window, right-drag resizes it. + +### Layout + +| Shortcut | Action | +| --- | --- | +| `Super`+`b` | Split horizontally | +| `Super`+`v` | Split vertically | +| `Super`+`s` | Stacking layout | +| `Super`+`w` | Tabbed layout | +| `Super`+`f` | Toggle fullscreen | +| `Super`+`y` | **Layout submenu**: `s` stacking · `w` tabbed · `e` toggle split · `Return`/`Esc` exit | + +> The layout submenu's `e` (toggle split) is the home for that action since +> `Super`+`e` now opens the file manager. + +### Workspaces + +| Shortcut | Action | +| --- | --- | +| `Super`+`1`…`0` | Switch to workspace 1…10 | +| `Super`+`Shift`+`1`…`0` | Move the window to workspace 1…10 | +| `Super`+`z` | Previous workspace | +| `Super`+`x` | Next workspace | + +### Scratchpad + +| Shortcut | Action | +| --- | --- | +| `Super`+`Shift`+`-` | Move the window to the scratchpad | +| `Super`+`-` | Show / cycle the scratchpad | + +### Modes (submenus) + +| Shortcut | Action | +| --- | --- | +| `Super`+`r` | **Resize mode**: arrow keys resize; `Return`/`Esc` exit | +| `Super`+`y` | **Layout mode** (see Layout above) | +| `Super`+`Shift`+`x` | **Power menu**: `l` lock · `e` log out · `s` sleep · `r` reboot · `Shift`+`s` shutdown · `Return`/`Esc` exit | + +### Screenshots + +| Shortcut | Action | +| --- | --- | +| `Print` | Select a region → swappy (annotate/save) | +| `Shift`+`Print` | Focused window → swappy | + +### Audio & media + +| Shortcut | Action | +| --- | --- | +| `XF86AudioRaiseVolume` / `XF86AudioLowerVolume` | Volume ±5% (wpctl) | +| `XF86AudioMute` | Toggle output mute | +| `XF86AudioMicMute` | Toggle microphone mute | +| `XF86AudioPlay` | Play/pause (playerctl) | +| `XF86AudioNext` / `XF86AudioPrev` | Next / previous track | + +### Brightness — laptops only + +| Shortcut | Action | +| --- | --- | +| `XF86MonBrightnessUp` / `XF86MonBrightnessDown` | Backlight ±5% (brightnessctl) | + +Present only on portable hosts (T400, MBP); desktops have no internal backlight. + +--- + +## tmux + +Prefix is **`Ctrl`+`b`** (default). Copy mode uses **vi** keys. + +| Shortcut | Action | +| --- | --- | +| `Ctrl`+`b` then `v` | Split into left/right panes | +| `Ctrl`+`b` then `s` | Split into top/bottom panes | +| `Alt`+`←`/`→`/`↑`/`↓` | Switch pane by direction (no prefix needed) | +| `Ctrl`+`b` then `[` | Enter copy mode (then vi motions; `Space`/`Enter` to select/copy) | +| `Ctrl`+`b` then `z` | Zoom / unzoom the focused pane | +| `Ctrl`+`b` then `c` | New window | +| `Ctrl`+`b` then `n` / `p` | Next / previous window | +| `Ctrl`+`b` then `d` | Detach | +| Mouse | Enabled — click to focus, drag borders, scroll, select | + +> The stock split keys `%` and `"` are unbound; use `v` / `s` above. `Ctrl`+`b` +> then `s` is therefore a split, not the session tree. + +--- + +## foot (terminal) + +Only colours are themed; these are foot's default key bindings. + +| Shortcut | Action | +| --- | --- | +| `Ctrl`+`Shift`+`c` / `Ctrl`+`Shift`+`v` | Copy / paste (clipboard) | +| `Shift`+`Insert` | Paste primary selection | +| `Ctrl`+`Shift`+`r` | Search scrollback | +| `Ctrl`+`+` / `Ctrl`+`-` / `Ctrl`+`0` | Font larger / smaller / reset | +| `Ctrl`+`Shift`+`u` | URL mode (jump to/open links) | +| `Ctrl`+`Shift`+`n` | Spawn a new terminal | +| `Shift`+`PageUp` / `Shift`+`PageDown` | Scroll back / forward | + +--- + +## zsh + +| Shortcut | Action | +| --- | --- | +| `↑` / `↓` | History **substring** search — type a fragment first, then the arrows cycle matching past commands | + +Bound for both CSI and SS3 cursor sequences, so it works in foot, iTerm2 and +the Linux TTY alike. -- 2.52.0 From 416fbcae529e595495e5d6fb391bebf22814c2a6 Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Tue, 9 Jun 2026 20:59:50 +0100 Subject: [PATCH 20/21] feat(gtk): theme GTK4 apps to match (import catppuccin gtk-4.0 CSS) Set gtk.gtk4.theme = config.gtk.theme so any GTK4 app added later is themed too. GTK4 ignores gtk-theme-name, but home-manager renders this as an `@import` of the theme's gtk-4.0/gtk.css into ~/.config/gtk-4.0/gtk.css -- which libadwaita honours, since that file overrides the named colours it uses (window_bg_color/accent_bg_color/view_bg_color, verified present in catppuccin-gtk's GTK4 stylesheet). Setting it explicitly (to the same value as the legacy default) also silences the stateVersion<26.05 default-change warning. nemo (GTK3) is unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) --- lyrathorpe/home/desktop.nix | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lyrathorpe/home/desktop.nix b/lyrathorpe/home/desktop.nix index 147fef5..0bfddf4 100644 --- a/lyrathorpe/home/desktop.nix +++ b/lyrathorpe/home/desktop.nix @@ -2,7 +2,7 @@ # Imported only on hosts that run Sway (MBP, T400, Mac Pro); never pulled onto # the headless WSL host. Login (and the Sway session launch) is handled by the # greetd/ReGreet greeter -- see ../swaywm.nix -- so there is no tty1 autostart. -{ pkgs, ... }: +{ pkgs, config, ... }: { imports = [ ./sway.nix @@ -27,6 +27,13 @@ # design, so no separate prefer-dark hint is needed. gtk = { enable = true; + # Theme GTK4 apps too (for any added later). GTK4 ignores gtk-theme-name, + # but home-manager turns this into an `@import` of the theme's + # gtk-4.0/gtk.css into ~/.config/gtk-4.0/gtk.css -- which even libadwaita + # honours, since that file overrides the named colours it uses + # (window_bg_color, accent_bg_color, ...). Set explicitly (same value as the + # legacy default) so it also silences the stateVersion<26.05 warning. + gtk4.theme = config.gtk.theme; theme = { name = "catppuccin-mocha-blue-standard"; package = pkgs.catppuccin-gtk.override { -- 2.52.0 From 745188c3cff67d76c29394cc5cde7d9f62671fad Mon Sep 17 00:00:00 2001 From: Emma Thorpe Date: Tue, 9 Jun 2026 21:14:00 +0100 Subject: [PATCH 21/21] feat(firefox): theme with the Catppuccin Mocha add-on Firefox draws its own chrome and ignores the GTK theme, so theme it at the browser level. Add the rycee firefox-addons flake input and, in the home-manager desktop layer, manage the Firefox profile (package = null -- the system programs.firefox in user.nix still provides the binary): - install the Catppuccin Mocha theme add-on (catppuccin-mocha-mauve; only the mauve accent is packaged upstream, so it differs slightly from the blue accent used elsewhere), - autoDisableScopes = 0 so it applies on first launch, - ui.systemUsesDarkTheme + prefers-color-scheme override for dark chrome and page content. Verified the XPI fetches, user.js renders the prefs, finalPackage is null (no duplicate Firefox), all Sway hosts eval, and EDaaS is unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) --- flake.lock | 23 +++++++++++++++++++++++ flake.nix | 5 +++++ lyrathorpe/home/desktop.nix | 35 ++++++++++++++++++++++++++++++++++- 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/flake.lock b/flake.lock index a3ed534..1696d7f 100644 --- a/flake.lock +++ b/flake.lock @@ -17,6 +17,28 @@ "type": "github" } }, + "firefox-addons": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "dir": "pkgs/firefox-addons", + "lastModified": 1780977789, + "narHash": "sha256-UFJfQlvInbsVaTK5XC2lafdqWlwiNP5LuQFYfDKq6Dc=", + "owner": "rycee", + "repo": "nur-expressions", + "rev": "0b627f105ea3baa2fa10308a6a67a8f8cbbb3e2a", + "type": "gitlab" + }, + "original": { + "dir": "pkgs/firefox-addons", + "owner": "rycee", + "repo": "nur-expressions", + "type": "gitlab" + } + }, "flake-compat": { "locked": { "lastModified": 1761640442, @@ -204,6 +226,7 @@ }, "root": { "inputs": { + "firefox-addons": "firefox-addons", "flake-parts": "flake-parts", "home-manager": "home-manager", "nix-darwin": "nix-darwin", diff --git a/flake.nix b/flake.nix index c860a3e..07ee130 100644 --- a/flake.nix +++ b/flake.nix @@ -23,6 +23,11 @@ # Provides mkFlake: the systems/perSystem scaffolding used below. flake-parts.url = "github:hercules-ci/flake-parts"; flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs"; + # Declarative Firefox add-ons (e.g. the Catppuccin theme); see lyrathorpe/user.nix. + firefox-addons = { + url = "gitlab:rycee/nur-expressions?dir=pkgs/firefox-addons"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = diff --git a/lyrathorpe/home/desktop.nix b/lyrathorpe/home/desktop.nix index 0bfddf4..da6f757 100644 --- a/lyrathorpe/home/desktop.nix +++ b/lyrathorpe/home/desktop.nix @@ -2,7 +2,13 @@ # Imported only on hosts that run Sway (MBP, T400, Mac Pro); never pulled onto # the headless WSL host. Login (and the Sway session launch) is handled by the # greetd/ReGreet greeter -- see ../swaywm.nix -- so there is no tty1 autostart. -{ pkgs, config, ... }: +{ + pkgs, + config, + inputs, + username, + ... +}: { imports = [ ./sway.nix @@ -57,4 +63,31 @@ name = "Adwaita"; size = 24; }; + + # Firefox is themed at the browser level (it does not follow the GTK theme). + # The system installs the binary (programs.firefox in ../user.nix); here + # home-manager owns only the profile, hence package = null. Apply the + # Catppuccin Mocha theme add-on (only the mauve accent is packaged upstream; + # the rest of the desktop uses blue) and make content + UI dark. + programs.firefox = { + enable = true; + package = null; + profiles.${username} = { + id = 0; + isDefault = true; + extensions = { + force = true; + packages = [ + inputs.firefox-addons.packages.${pkgs.system}.catppuccin-mocha-mauve + ]; + }; + settings = { + # Enable bundled add-ons automatically so the theme applies on first run. + "extensions.autoDisableScopes" = 0; + # Dark chrome + dark page content. + "ui.systemUsesDarkTheme" = 1; + "layout.css.prefers-color-scheme.content-override" = 0; + }; + }; + }; } -- 2.52.0