From be7ecfc0ea65d30eb3594a9cff2adddf04b726bc Mon Sep 17 00:00:00 2001 From: randogoth Date: Thu, 23 Jul 2026 16:15:20 +0200 Subject: [PATCH 01/10] desktop: replace Thunar with Nautilus, add File Roller Thunar didn't integrate cleanly with the GNOME session. Drop the XFCE file-manager module (thunar/xfconf/exo/tumbler) and restore Nautilus as the native file manager: un-exclude nautilus, point the dash favorite at org.gnome.Nautilus.desktop, remove the Thunar desktop override, and keep gvfs (moved into gnome.nix) for mounting, trash, and network shares. Add file-roller for archive-manager integration in Nautilus. Co-Authored-By: Claude Opus 4.8 --- modules/desktop/desktop-entries.nix | 25 ------------------------- modules/desktop/file-manager.nix | 27 --------------------------- modules/desktop/gnome.nix | 8 ++++++-- modules/profile/nanuqsaurus.nix | 1 - 4 files changed, 6 insertions(+), 55 deletions(-) delete mode 100644 modules/desktop/file-manager.nix diff --git a/modules/desktop/desktop-entries.nix b/modules/desktop/desktop-entries.nix index 7da1689..04e540b 100644 --- a/modules/desktop/desktop-entries.nix +++ b/modules/desktop/desktop-entries.nix @@ -1,30 +1,6 @@ { lib, pkgs, ... }: let - thunarDesktopOverride = pkgs.makeDesktopItem { - name = "thunar"; - desktopName = "Files"; - genericName = "File Manager"; - tryExec = "thunar"; - exec = "thunar %U"; - icon = "org.xfce.thunar"; - categories = [ - "Utility" - "FileManager" - ]; - mimeTypes = [ "inode/directory" ]; - keywords = [ - "folder" - "manager" - "explorer" - "files" - ]; - startupNotify = true; - startupWMClass = "Thunar"; - terminal = false; - comment = "Browse files"; - }; - ptyxisDesktopOverride = pkgs.makeDesktopItem { name = "org.gnome.Ptyxis"; desktopName = "Terminal"; @@ -41,7 +17,6 @@ let in { environment.systemPackages = lib.mkAfter [ - (lib.hiPrio thunarDesktopOverride) (lib.hiPrio ptyxisDesktopOverride) ]; } diff --git a/modules/desktop/file-manager.nix b/modules/desktop/file-manager.nix deleted file mode 100644 index 9764e7d..0000000 --- a/modules/desktop/file-manager.nix +++ /dev/null @@ -1,27 +0,0 @@ -{ pkgs, ... }: - -{ - environment.etc."xdg/xfce4/xfconf/xfce-perchannel-xml/exo.xml".text = '' - - - - - - - ''; - - services.gvfs.enable = true; - services.tumbler.enable = true; - - programs.thunar = { - enable = true; - plugins = [ - pkgs.xfce.thunar-archive-plugin - pkgs.xfce.thunar-media-tags-plugin - pkgs.xfce.thunar-vcs-plugin - pkgs.xfce.thunar-volman - ]; - }; - - programs.xfconf.enable = true; -} diff --git a/modules/desktop/gnome.nix b/modules/desktop/gnome.nix index 929d076..bd9f8ab 100644 --- a/modules/desktop/gnome.nix +++ b/modules/desktop/gnome.nix @@ -30,6 +30,9 @@ in services.displayManager.gdm.enable = true; services.desktopManager.gnome.enable = true; + # Virtual filesystem backend for Nautilus: mounting, trash, network shares. + services.gvfs.enable = true; + services.gnome.core-developer-tools.enable = false; services.gnome.games.enable = false; @@ -41,7 +44,7 @@ in "org/gnome/shell" = { disable-user-extensions = false; favorite-apps = [ - "thunar.desktop" + "org.gnome.Nautilus.desktop" "org.gnome.Ptyxis.desktop" "com.ranfdev.DistroShelf.desktop" "io.github.kolunmi.Bazaar.desktop" @@ -94,6 +97,8 @@ in pkgs.gnome-extension-manager pkgs.mission-center pkgs.papirus-icon-theme + pkgs.file-roller + pkgs.unrar pkgs.gnomeExtensions.all-in-one-clipboard pkgs.gnomeExtensions.alphabetical-app-grid pkgs.gnomeExtensions.app-hider @@ -129,7 +134,6 @@ in pkgs.gnome-system-monitor pkgs.gnome-weather pkgs.loupe - pkgs.nautilus pkgs.papers # gnome-connections pkgs.showtime diff --git a/modules/profile/nanuqsaurus.nix b/modules/profile/nanuqsaurus.nix index 0904657..9c50ad5 100644 --- a/modules/profile/nanuqsaurus.nix +++ b/modules/profile/nanuqsaurus.nix @@ -16,7 +16,6 @@ ../system/printer.nix ../system/impermanence.nix ../desktop/desktop-entries.nix - ../desktop/file-manager.nix ../desktop/gnome.nix ../users/admin.nix ]; From 86b5c2422ea2327a5091a91f178a9eadb945261c Mon Sep 17 00:00:00 2001 From: randogoth Date: Fri, 28 Aug 2026 11:23:26 +0300 Subject: [PATCH 02/10] just: parameterize update recipe (system/home/both) `just update system` re-locks + rebuilds the NixOS system flake, `just update home` re-locks + switches the Home Manager flake, and `just update` with no arg does both (system first). Previously update only touched the system flake, leaving the Home Manager side stale. Co-Authored-By: Claude Opus 4.8 --- modules/system/justfile.nix | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/modules/system/justfile.nix b/modules/system/justfile.nix index 29036e9..506aad7 100644 --- a/modules/system/justfile.nix +++ b/modules/system/justfile.nix @@ -26,9 +26,29 @@ let nix-collect-garbage -d [no-cd] - update: - sudo nix flake update --flake "''${NIXOS_FLAKE:-/etc/nixos}" - just rebuild + update target="": + #!/usr/bin/env bash + set -euo pipefail + + update_system() { + sudo nix flake update --flake "''${NIXOS_FLAKE:-/etc/nixos}" + just rebuild + } + + update_home() { + nix flake update --flake "''${XDG_CONFIG_HOME:-$HOME/.config}/home-manager" + just switch + } + + case "{{target}}" in + system) update_system ;; + home) update_home ;; + "") update_system; update_home ;; + *) + echo "usage: just update [system|home] (empty = both)" >&2 + exit 1 + ;; + esac [no-cd] init-home: From 2f9293f6982ddc861ba4f12bfa39e4a4c5b5b3dc Mon Sep 17 00:00:00 2001 From: randogoth Date: Fri, 28 Aug 2026 12:04:46 +0300 Subject: [PATCH 03/10] flake: track nixos-26.05 (nixos-25.11 is EOL) nixos-25.11 reached end-of-life (frozen upstream at b6018f8), so `nix flake update` no longer pulls system package updates. Move the default nixpkgs channel to the current stable release, nixos-26.05. Co-Authored-By: Claude Opus 4.8 --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index ff9c8e2..d7c7586 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,7 @@ description = "NixOS installation ISO with Lix"; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05"; disko = { url = "github:nix-community/disko"; inputs.nixpkgs.follows = "nixpkgs"; From 72941092d2c240e9a2be6fa2398e42d7d478db80 Mon Sep 17 00:00:00 2001 From: randogoth Date: Fri, 28 Aug 2026 12:11:17 +0300 Subject: [PATCH 04/10] 26.05: source Lix from nixpkgs, fix disko btrfs label MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit nixos-26.05 removed lix_2_93, which the lix-module release-2.93 branch hard-pins via lixFromNixpkgs (no 2.94 module release exists yet). Drop the lix-module input and set nix.package = pkgs.lix, which is Lix 2.94.2 on 26.05 — so we stay on Lix, just sourced from nixpkgs. Also: newer disko dropped the btrfs `label` option; pass -L nixos via extraArgs instead so the impermanence rollback still finds the "nixos" label. Co-Authored-By: Claude Opus 4.8 --- flake.lock | 95 ++------------------------------- flake.nix | 2 - modules/profile/nanuqsaurus.nix | 7 ++- modules/system/disko-btrfs.nix | 5 +- 4 files changed, 12 insertions(+), 97 deletions(-) diff --git a/flake.lock b/flake.lock index 21a6dd0..80a9f4b 100644 --- a/flake.lock +++ b/flake.lock @@ -125,39 +125,6 @@ "type": "github" } }, - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flakey-profile": { - "locked": { - "lastModified": 1712898590, - "narHash": "sha256-FhGIEU93VHAChKEXx905TSiPZKga69bWl1VB37FK//I=", - "owner": "lf-", - "repo": "flakey-profile", - "rev": "243c903fd8eadc0f63d205665a92d4df91d42d9d", - "type": "github" - }, - "original": { - "owner": "lf-", - "repo": "flakey-profile", - "type": "github" - } - }, "flox": { "inputs": { "crane": "crane", @@ -288,44 +255,6 @@ "type": "github" } }, - "lix": { - "flake": false, - "locked": { - "lastModified": 1768477252, - "narHash": "sha256-RgET5pILoiG+3GnDpgfUEA4y8ISfZe6SB+geE+8U76Y=", - "rev": "fbe811e94e9c4efc9b9c283fe2fbb3aeff50fca7", - "type": "tarball", - "url": "https://git.lix.systems/api/v1/repos/lix-project/lix/archive/fbe811e94e9c4efc9b9c283fe2fbb3aeff50fca7.tar.gz?rev=fbe811e94e9c4efc9b9c283fe2fbb3aeff50fca7" - }, - "original": { - "type": "tarball", - "url": "https://git.lix.systems/lix-project/lix/archive/release-2.93.tar.gz" - } - }, - "lix-module": { - "inputs": { - "flake-utils": "flake-utils", - "flakey-profile": "flakey-profile", - "lix": "lix", - "nixpkgs": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1756125859, - "narHash": "sha256-6a+PWILmqHCs9B5eIBLg6HSZ8jYweZpgOWO8FlyVwYI=", - "ref": "release-2.93", - "rev": "d3292125035b04df00d01549a26e948631fabe1e", - "revCount": 156, - "type": "git", - "url": "https://git.lix.systems/lix-project/nixos-module" - }, - "original": { - "ref": "release-2.93", - "type": "git", - "url": "https://git.lix.systems/lix-project/nixos-module" - } - }, "nix-flatpak": { "locked": { "lastModified": 1767983141, @@ -380,16 +309,16 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1769741972, - "narHash": "sha256-RxSg1EioTWNpoLaykiT1UQKTo/K0PPdLqCyQgNjNqWs=", + "lastModified": 1787753485, + "narHash": "sha256-BZWCi9ZRJiARTuKTbbtvFTj7t1TK4G3UEckT3HyNfRg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "63590ac958a8af30ebd52c7a0309d8c52a94dd77", + "rev": "062346a6d85bc4b49dfaa61c986e9c5be21217d1", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-25.11", + "ref": "nixos-26.05", "repo": "nixpkgs", "type": "github" } @@ -467,7 +396,6 @@ "flox": "flox", "impermanence": "impermanence", "lanzaboote": "lanzaboote", - "lix-module": "lix-module", "nix-flatpak": "nix-flatpak", "nix-index-database": "nix-index-database", "nixpkgs": "nixpkgs_2", @@ -511,21 +439,6 @@ "repo": "rust-overlay", "type": "github" } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } } }, "root": "root", diff --git a/flake.nix b/flake.nix index d7c7586..66a10f5 100644 --- a/flake.nix +++ b/flake.nix @@ -20,8 +20,6 @@ url = "github:nix-community/NUR"; inputs.nixpkgs.follows = "nixpkgs"; }; - lix-module.url = "git+https://git.lix.systems/lix-project/nixos-module?ref=release-2.93"; - lix-module.inputs.nixpkgs.follows = "nixpkgs"; lanzaboote.url = "github:nix-community/lanzaboote"; lanzaboote.inputs.nixpkgs.follows = "nixpkgs"; flox.url = "github:flox/flox"; diff --git a/modules/profile/nanuqsaurus.nix b/modules/profile/nanuqsaurus.nix index 9c50ad5..724d416 100644 --- a/modules/profile/nanuqsaurus.nix +++ b/modules/profile/nanuqsaurus.nix @@ -1,8 +1,11 @@ { inputs }: -{ ... }: +{ pkgs, ... }: { + # Use Lix from nixpkgs (2.94.x on nixos-26.05). The lix-module's + # lixFromNixpkgs still pins lix_2_93, which nixos-26.05 removed. + nix.package = pkgs.lix; + imports = [ - inputs.lix-module.nixosModules.lixFromNixpkgs inputs.nix-index-database.nixosModules.nix-index inputs.nix-flatpak.nixosModules.nix-flatpak inputs.impermanence.nixosModules.impermanence diff --git a/modules/system/disko-btrfs.nix b/modules/system/disko-btrfs.nix index 08e0a57..450f26a 100644 --- a/modules/system/disko-btrfs.nix +++ b/modules/system/disko-btrfs.nix @@ -26,8 +26,9 @@ size = "100%"; content = { type = "btrfs"; - extraArgs = [ "-f" ]; - label = "nixos"; + # Filesystem label "nixos" is required by the impermanence rollback + # logic. Newer disko dropped the `label` option; pass it to mkfs.btrfs. + extraArgs = [ "-f" "-L" "nixos" ]; subvolumes = { root = { mountpoint = "/"; From 17cce973fc20a9134bce70c5538fdbb430ab53d3 Mon Sep 17 00:00:00 2001 From: randogoth Date: Fri, 28 Aug 2026 12:14:53 +0300 Subject: [PATCH 05/10] impermanence: port btrfs rollback to systemd-stage-1 initrd nixos-26.05 asserts against boot.initrd.postDeviceCommands under the systemd stage-1 initrd. Move the root-subvolume rollback into an initrd systemd oneshot (ordered after the labelled device, before sysroot.mount). Same rollback logic; awk swapped for cut since coreutils' cut is in the systemd initrd but awk is not. Co-Authored-By: Claude Opus 4.8 --- modules/system/impermanence.nix | 60 ++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/modules/system/impermanence.nix b/modules/system/impermanence.nix index 28bcb89..ad10a1d 100644 --- a/modules/system/impermanence.nix +++ b/modules/system/impermanence.nix @@ -10,36 +10,50 @@ config = lib.mkIf config.nanuqsaurus.impermanence.enable { boot.initrd.supportedFilesystems = [ "btrfs" ]; - boot.initrd.postDeviceCommands = lib.mkAfter '' - mkdir -p /mnt - mount -t btrfs -o subvol=/ /dev/disk/by-label/nixos /mnt + # Reset the /root subvolume to a pristine snapshot on every boot. + # systemd stage-1 initrd does not support boot.initrd.postDeviceCommands, so + # this runs as an initrd systemd service, ordered after the labelled device + # appears and before the root filesystem is mounted (sysroot.mount). + # Note: `cut` (coreutils) is used instead of `awk`, which is not in the + # systemd initrd. + boot.initrd.systemd.services.rollback = { + description = "Rollback btrfs root subvolume to a pristine state"; + wantedBy = [ "initrd.target" ]; + after = [ "dev-disk-by\\x2dlabel-nixos.device" ]; + before = [ "sysroot.mount" ]; + unitConfig.DefaultDependencies = "no"; + serviceConfig.Type = "oneshot"; + script = '' + mkdir -p /mnt + mount -t btrfs -o subvol=/ /dev/disk/by-label/nixos /mnt - if [ ! -d /mnt/root-blank ]; then - btrfs subvolume snapshot -r /mnt/root /mnt/root-blank - fi - - if [ -d /mnt/root ]; then - if [ -d /mnt/root-previous ]; then - btrfs subvolume delete /mnt/root-previous + if [ ! -d /mnt/root-blank ]; then + btrfs subvolume snapshot -r /mnt/root /mnt/root-blank fi - btrfs subvolume snapshot /mnt/root /mnt/root-previous - while true; do - subvols="$(btrfs subvolume list -o /mnt/root | awk '{print $9}')" - if [ -z "$subvols" ]; then - break + if [ -d /mnt/root ]; then + if [ -d /mnt/root-previous ]; then + btrfs subvolume delete /mnt/root-previous fi - for subvol in $subvols; do - btrfs subvolume delete "/mnt/$subvol" + btrfs subvolume snapshot /mnt/root /mnt/root-previous + + while true; do + subvols="$(btrfs subvolume list -o /mnt/root | cut -f9 -d' ')" + if [ -z "$subvols" ]; then + break + fi + for subvol in $subvols; do + btrfs subvolume delete "/mnt/$subvol" + done done - done - btrfs subvolume delete /mnt/root - fi + btrfs subvolume delete /mnt/root + fi - btrfs subvolume snapshot /mnt/root-blank /mnt/root - umount /mnt - ''; + btrfs subvolume snapshot /mnt/root-blank /mnt/root + umount /mnt + ''; + }; fileSystems."/persist".neededForBoot = true; From d1321cf7ca38988f79a4e5a0c71dbd5fde438c4a Mon Sep 17 00:00:00 2001 From: randogoth Date: Fri, 28 Aug 2026 12:38:06 +0300 Subject: [PATCH 06/10] docs: nixos-26.05 in adopt examples, document impermanence Bump the wrapper-flake nixpkgs channel in the adopt/laptop examples to nixos-26.05, and add the "Impermanence (optional)" section covering the /persist subvolume, the required "nixos" fs label, and the default persistence set. Co-Authored-By: Claude Opus 4.8 --- docs/adopt.md | 29 ++++++- laptop.md | 208 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 236 insertions(+), 1 deletion(-) create mode 100644 laptop.md diff --git a/docs/adopt.md b/docs/adopt.md index 3598c3e..83a56e1 100644 --- a/docs/adopt.md +++ b/docs/adopt.md @@ -22,7 +22,7 @@ On the target machine: description = "Local wrapper for the nanuqsaurus profile"; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05"; nanuqsaurus = { url = "git+https://codeberg.org/randogoth/nanuqsaurus.git"; inputs.nixpkgs.follows = "nixpkgs"; @@ -53,6 +53,8 @@ On the target machine: { # Optional: change the default admin username ("admin") nanuqsaurus.admin.username = "admin"; + # Optional: enable impermanence (requires a /persist Btrfs subvolume and mounts in hardware-configuration.nix) + # nanuqsaurus.impermanence.enable = true; networking.hostName = "nanuqsaurus"; system.stateVersion = "25.11"; } @@ -75,3 +77,28 @@ sudo nixos-rebuild switch --flake /etc/nixos#nanuqsaurus - `sudo nixos-rebuild switch --flake /etc/nixos#nanuqsaurus` - Keep all your machine-specific changes in the wrapper flake (additional modules, packages, services, etc.). + +## Impermanence (optional) + +If you enable `nanuqsaurus.impermanence.enable = true;` you must: + +- Add a `/persist` mount (Btrfs subvolume) in `hardware-configuration.nix`. +- Migrate state you care about into `/persist` before the first reboot. + +Notes: + +- The rollback logic expects the root Btrfs volume label to be `nixos`. +- Create the subvolume with `btrfs subvolume create /mnt/persist` and mount it as `/persist`. + +The default persistence set includes: + +- `/etc/nixos` +- `/etc/ssh` +- `/etc/cups` +- `/var/lib/cups` +- `/var/lib/libvirt` +- `/var/lib/secureboot` +- `/var/lib/tailscale` +- `/var/lib/systemd` +- `/var/lib/nixos` +- `/etc/machine-id` diff --git a/laptop.md b/laptop.md new file mode 100644 index 0000000..c59c2b3 --- /dev/null +++ b/laptop.md @@ -0,0 +1,208 @@ +# Install Nanuqsaurus On An Empty Partition (No Full-Disk Wipe) + +This repo’s `nanuqsaurus-btrfs` “fresh install” path uses `disko` to create a brand new GPT layout (EFI + Btrfs) and therefore **wipes the whole disk**. If you want to install onto an **existing empty partition** while keeping other partitions intact, use the “wrapper flake” approach and a manual mount + `nixos-install`. + +These steps are meant to be run from a **NixOS installer ISO** on the target laptop. + +## 0) Preconditions + +- Boot the installer in **UEFI mode** (this profile enables `systemd-boot` + EFI). +- You have working network connectivity (WiFi instructions below). +- You have: + - an existing **empty root partition** you will format (example: `/dev/nvme0n1p6`) + - an existing **EFI System Partition** (vfat) you will mount at `/boot` (example: `/dev/nvme0n1p1`) + +Confirm UEFI: + +```bash +test -d /sys/firmware/efi/efivars && echo "UEFI booted" || echo "NOT UEFI booted" +``` + +## 0.5) Connect To WiFi (Installer) + +Most NixOS installer ISOs run NetworkManager. The easiest flow is `nmtui`. + +1) Bring up the text UI and connect: + +```bash +sudo -i +nmtui +``` + +In `nmtui`: + +- Select `Activate a connection` +- Pick your WiFi SSID +- Enter the password +- Exit + +2) Verify you have connectivity: + +```bash +ping -c 1 1.1.1.1 +ping -c 1 example.com +``` + +If `nmtui` is unavailable, try: + +```bash +nmcli dev status +nmcli dev wifi list +nmcli dev wifi connect "YOUR_SSID" password "YOUR_PASSWORD" +``` + +## 1) Identify Your Partitions + +```bash +lsblk -f +``` + +Pick stable paths if available (`/dev/disk/by-id/...`). In the commands below, set: + +- `ROOT_PART` = the empty partition to become `/` +- `EFI_PART` = the EFI System Partition (vfat) to become `/boot` + +Example: + +```bash +ROOT_PART=/dev/nvme0n1p6 +EFI_PART=/dev/nvme0n1p1 +``` + +## 2) Format The Root Partition + +Warning: this destroys data on `ROOT_PART`. + +Recommended (Btrfs with subvolumes similar to this repo’s intended layout): + +```bash +mkfs.btrfs -f -L nixos "$ROOT_PART" + +mount "$ROOT_PART" /mnt +btrfs subvolume create /mnt/root +btrfs subvolume create /mnt/home +btrfs subvolume create /mnt/nix +umount /mnt + +mount -o subvol=root,compress=zstd "$ROOT_PART" /mnt +mkdir -p /mnt/home /mnt/nix +mount -o subvol=home,compress=zstd "$ROOT_PART" /mnt/home +mount -o subvol=nix,compress=zstd "$ROOT_PART" /mnt/nix +``` + +Alternative (ext4): + +```bash +mkfs.ext4 -F -L nixos "$ROOT_PART" +mount "$ROOT_PART" /mnt +mkdir -p /mnt/nix +``` + +## 3) Mount EFI At `/boot` + +```bash +mkdir -p /mnt/boot +mount "$EFI_PART" /mnt/boot +``` + +## 4) Generate Hardware Config + +Important: do this **after** mounting `/boot`, so the EFI mount gets captured. + +```bash +nixos-generate-config --root /mnt +``` + +This creates: + +- `/mnt/etc/nixos/hardware-configuration.nix` +- `/mnt/etc/nixos/configuration.nix` (we will not use this directly) + +## 5) Create A Wrapper Flake In `/etc/nixos` + +This repo’s `nanuqsaurus` profile defines an `admin` user and also tends to lock `root` by default. To avoid surprises, set your own password hashes in the wrapper flake using `lib.mkForce`. + +If you have a Lenovo ThinkPad X13 (Intel), you can also import the `nixos-hardware` module for it in this wrapper flake. + +1) Generate SHA-512 password hashes: + +```bash +nix --experimental-features "nix-command flakes" \ + shell nixpkgs#mkpasswd -c mkpasswd -- -m sha-512 +``` + +Run it twice if you want to set both `admin` and `root`. + +2) Create `/mnt/etc/nixos/flake.nix`: + +```bash +nano /mnt/etc/nixos/flake.nix +``` + +Paste and edit: + +```nix +{ + description = "Local wrapper for the nanuqsaurus profile"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05"; + nanuqsaurus = { + url = "git+https://codeberg.org/randogoth/nanuqsaurus.git"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + # Optional hardware profiles (recommended on laptops) + nixos-hardware = { + url = "github:NixOS/nixos-hardware"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = inputs@{ nixpkgs, nanuqsaurus, nixos-hardware, ... }: { + nixosConfigurations.nanuqsaurus = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + specialArgs = { inherit inputs; }; + modules = [ + ./hardware-configuration.nix + # Optional: ThinkPad X13 (Intel) profile (path: lenovo/thinkpad/x13/intel) + # Remove or replace if you have different hardware. + nixos-hardware.nixosModules.lenovo-thinkpad-x13-intel + nanuqsaurus.nixosModules.nanuqsaurus + ({ lib, ... }: { + networking.hostName = "nanuqsaurus"; + system.stateVersion = "25.11"; + + nanuqsaurus.admin.username = "admin"; + users.users.admin.hashedPassword = lib.mkForce "PUT_ADMIN_SHA512_HASH_HERE"; + + # Recommended so you can recover easily: + users.users.root.hashedPassword = lib.mkForce "PUT_ROOT_SHA512_HASH_HERE"; + + # Optional: require sudo password (this repo defaults to passwordless sudo) + security.sudo.wheelNeedsPassword = lib.mkForce true; + }) + ]; + }; + }; +} +``` + +## 6) Install NixOS + +```bash +NIX_CONFIG="experimental-features = nix-command flakes" \ + nixos-install --root /mnt --flake /mnt/etc/nixos#nanuqsaurus +``` + +## 7) Reboot + +```bash +reboot +``` + +If your firmware shows multiple boot options, select the systemd-boot entry for the disk containing the EFI partition. + +## Notes + +- Secure Boot: this repo’s installer ISO expects Secure Boot off at boot; after installation you can enable Secure Boot using Lanzaboote (see `docs/secure-boot.md`). +- If you actually want to wipe an entire disk and let this repo handle partitioning, use `disko-install` with `#nanuqsaurus-btrfs` (see `README.md` and `docs/install-help.txt`). From 228fe626b52b809592ad9ca5da5de4b16e4f3090 Mon Sep 17 00:00:00 2001 From: randogoth Date: Fri, 28 Aug 2026 12:48:25 +0300 Subject: [PATCH 07/10] gitignore: ignore .claude/ (local assistant config) Co-Authored-By: Claude Opus 4.8 --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 6c112c0..ead1537 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,6 @@ result .DS_Store hosts/*-hardware.nix hosts/local-*.nix + +# editor / assistant local config +.claude/ From 3f29ad6cec08cd2281857ef692a90300f1e6f0a6 Mon Sep 17 00:00:00 2001 From: randogoth Date: Fri, 28 Aug 2026 12:48:25 +0300 Subject: [PATCH 08/10] remove lowercase agents.md (superseded by AGENTS.md) Co-Authored-By: Claude Opus 4.8 --- agents.md | 310 ------------------------------------------------------ 1 file changed, 310 deletions(-) delete mode 100644 agents.md diff --git a/agents.md b/agents.md deleted file mode 100644 index 1946ad7..0000000 --- a/agents.md +++ /dev/null @@ -1,310 +0,0 @@ -# agents.md — NixOS (Lix) Configuration Agent - -## Role - -You are an expert **NixOS configuration agent** specializing in: - -- **Flake-first NixOS** -- **Lix-flavored Nix (preferred but not mandatory)** -- **Modular NixOS + Home Manager systems** -- **Flake-contained host modules (“configuration.nix-style”)** -- Deterministic, reproducible configurations - -You assist by **editing or proposing Nix code**. -You do **not** explain Nix concepts unless explicitly requested. - ---- - -## Authority Model - -1. **`flake.nix` is the sole entry point** - - All evaluation flows through `outputs` - - No channel-based workflows - - No implicit `NIX_PATH` - - No reliance on `/etc/nixos` - -2. **All systems are flakes** - - Legacy `configuration.nix` as an entry point is forbidden - - Files that *look* like `configuration.nix` are allowed **only as flake-contained host modules** - ---- - -## Required Workflow - -### Before Making Any Changes - -You **must present a concise plan** and wait for confirmation. - -The plan must include: -- Bullet points only -- Exact file paths to be touched -- High-level intent per file -- No code -- No prose explanations - -Example: - -```text -Plan: -- Add flake.nix with pinned nixpkgs -- Add hosts/laptop.nix as host module -- Add modules/system/base.nix -- Wire host via nixosConfigurations -```` - -You may **only modify files listed in the approved plan**. - -Scope is **strict**. - -### Permission Gates (New Directive) - -You must ask for explicit confirmation before doing any of the following: - -- Running tests/builds/evaluations (e.g. `nix build`, `nix flake check`, `nixos-rebuild`, `home-manager switch`) -- Creating commits (`git commit`, `git revert`, etc.) -- Pushing to any remote (`git push`, etc.) - -If you already presented a plan, you must still ask again before crossing one of -these gates. - ---- - -## Flake-Contained `configuration.nix`-Style Modules (Host Modules) - -### Definition - -A **host module** is a NixOS module that: - -* Has the standard module signature: - - ```nix - { config, pkgs, ... }: - ``` -* Looks like a traditional `configuration.nix` -* Is **not** an entry point -* Is **only evaluated via `flake.nix`** -* Exists solely to **compose a specific machine** - -This pattern is **explicitly allowed and encouraged** when used correctly. - ---- - -### Allowed Responsibilities (Host Modules) - -Host modules **may**: - -* Compose the system via `imports` -* Set host-specific values: - - * `networking.hostName` - * `system.stateVersion` - * locale / timezone -* Apply small, truly host-unique overrides - -Example: - -```nix -# hosts/laptop.nix -{ config, pkgs, ... }: - -{ - imports = [ - ../modules/system/base.nix - ../modules/desktop/wayland.nix - ../modules/users/tlalit.nix - ]; - - networking.hostName = "laptop"; - system.stateVersion = "24.11"; -} -``` - ---- - -### Forbidden Responsibilities (Host Modules) - -Host modules **must not**: - -* Implement reusable features -* Contain large logic blocks -* Define users inline -* Enable services that could apply to more than one host -* Act as monolithic system definitions - -Rule of thumb (enforced): - -> **Host modules compose. Feature modules implement.** - -If a setting could plausibly apply to more than one host, it does **not** belong in a host module. - ---- - -## Module Categories - -### Host Modules - -* Path: `hosts/*.nix` -* Role: composition only -* Small, declarative -* No reusable logic - -### Feature Modules - -* Path: `modules/**` -* Role: implementation -* Reusable -* Use upstream NixOS options only -* **No custom option namespaces** - -Example feature module skeleton: - -```nix -{ config, lib, pkgs, ... }: - -{ - config = { - # implementation using upstream options - }; -} -``` - ---- - -## Home Manager Policy - -* Home Manager is **provided as a CLI tool system-wide** (`home-manager` in `environment.systemPackages`). -* Users manage their own HM configs (per-user, standalone). No system-wide HM module imports. -* `modules/users/*` must not declare `home-manager.users.*`; keep user accounts declarative via NixOS only. - ---- - -## Hardware Policy - -* GPU support and Btrfs support are desired -* Hardware modules are allowed -* **Hardware changes must be explicitly included in the plan** -* No surprise disk, bootloader, or kernel changes - ---- - -## Overlays Policy - -* Overlays are **allowed but discouraged** -* The agent **must not introduce overlays unless explicitly requested** -* Prefer: - - * explicit flake inputs - * local `callPackage` - * direct package references - -No proactive overlay usage. - ---- - -## Code Style Rules - -### Nix - -* Pure Nix only -* Prefer explicit attribute paths -* Prefer: - - * `lib.mkIf` - * `lib.mkMerge` - * `lib.optionals` -* Avoid: - - * `with pkgs;` - * implicit imports - * inline shell hacks - -### Formatting - -* All Nix code must conform to **`nixfmt-rfc-style`** -* Do not reformat unrelated files - ---- - -## Scripting Policy - -1. **Prefer Python 3** - - * Scripts must be deterministic and non-interactive - * Stored under `./scripts/` - * May run **at activation time only** - * Not at evaluation time - * Not at build time unless explicitly requested - -2. **Shell scripts** - - * Allowed only when unavoidable - * POSIX-compliant - * Minimal - * Generated via `writeShellScriptBin` if needed - -3. **Never embed large scripts inline** - ---- - -## Expected Repository Layout - -```text -. -├── flake.nix -├── flake.lock -├── hosts/ -│ └── hostname.nix -├── modules/ -│ ├── system/ -│ ├── hardware/ -│ ├── services/ -│ ├── desktop/ -│ ├── users/ -│ └── development/ -├── scripts/ -└── lib/ -``` - -* `flake.nix` → authority -* `hosts/` → composition -* `modules/` → behavior - ---- - -## Safety & Reproducibility - -* No imperative installs -* No network access at evaluation -* Inputs must be pinned -* All changes must be declarative - ---- - -## Output Rules - -* Plans: **plan only** -* Code: **code only** -* Questions: **one precise question only** - -``` - ---- - -If you want, next we can: -- Add **machine-checkable lint rules** derived from this -- Write a **migration appendix** for legacy `/etc/nixos` -- Create a **Codex system prompt** that mirrors this file exactly -``` - ---- - -## Test VM (current) - -- Built from our installer ISO (`nixos-minimal-25.11.20260130.63590ac-x86_64-linux.iso`). -- Hostname: `nanuqsaurus`. -- User: `admin` / password `admin`. -- Network: DHCP on primary interface (e.g., `ensp1s0` in VM); SSH reachable once IP obtained. -- Purpose: sandbox for validating flake changes before baking into the ISO. -- Workflow: sync repo to VM and `nixos-rebuild switch --flake /etc/nixos#installer`; rebuild ISO **only on explicit request** (ISO must remain hardware-agnostic, no embedded host-specific files). -- Host-local files: the installer writes `hosts/local-.nix` and `hosts/local--hardware.nix` on the target. These are ignored by git. When syncing, exclude them to avoid deletion: - `rsync -a --exclude 'hosts/local-*.nix' --exclude 'hosts/*-hardware.nix' --exclude '.git' . admin@:/home/admin/nixos-sync` From 9f28569fef3d10b85a6f664203ddd486097f4927 Mon Sep 17 00:00:00 2001 From: randogoth Date: Fri, 28 Aug 2026 16:05:43 +0300 Subject: [PATCH 09/10] shell-init: record the first command of every shell in atuin bash-preexec arms its DEBUG trap from __bp_interactive_mode at the end of PROMPT_COMMAND, but its lazy installer runs mid-way through the first prompt because zoxide and ghostty append their hooks after it. Those hooks disarm the trap again, so the first interactive command of every session never reached preexec and atuin silently dropped it. Arm from PS0 instead, which is expanded once per interactive command before the DEBUG trap and is therefore independent of PROMPT_COMMAND ordering. Also guard the script against running twice: it is installed as both loginShellInit and interactiveShellInit, and /etc/profile sources /etc/bashrc, which registered starship's precmd/preexec hooks twice in every login shell. --- scripts/shell-init.sh | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/scripts/shell-init.sh b/scripts/shell-init.sh index 0a3f4d1..0eb0168 100644 --- a/scripts/shell-init.sh +++ b/scripts/shell-init.sh @@ -37,18 +37,43 @@ fi (( INTERACTIVE )) || return 0 +# ---- run-once guard -------------------------------------------------------- + +# Installed as both loginShellInit (/etc/profile) and interactiveShellInit +# (/etc/bashrc), and /etc/profile sources /etc/bashrc, so a login shell would run +# this twice and register starship's hooks twice. Not exported: child shells must +# initialise on their own. +if [[ -n ${NANUQSAURUS_SHELL_INIT_DONE:-} ]]; then + log_shell_init "already initialised, skipping" + return 0 +fi +NANUQSAURUS_SHELL_INIT_DONE=1 + # ---- bash-preexec ---------------------------------------------------------- if [[ -n ${BASH_VERSION:-} && -z ${bash_preexec_imported:-} && -r "@BASHPREEXEC@" ]]; then # shellcheck disable=SC1090 . "@BASHPREEXEC@" - # Do NOT call __bp_install here. bash-preexec must install on the FIRST prompt, - # after starship/atuin/zoxide/ghostty have modified PROMPT_COMMAND, so that - # __bp_interactive_mode stays the LAST PROMPT_COMMAND entry. Installing eagerly - # lets later hooks land after it and silently breaks preexec (atuin recording). log_shell_init "bash-preexec loaded" fi +# bash-preexec arms its DEBUG trap from __bp_interactive_mode, which it appends to +# the END of PROMPT_COMMAND -- but its lazy installer runs in the MIDDLE of the +# first prompt, because zoxide (below) and ghostty (~/.bashrc) append their hooks +# after it. Those hooks then disarm the trap again, so the first command of every +# session never reaches preexec and atuin never records it. PS0 is expanded once +# per interactive command, before the DEBUG trap, so arming there is independent of +# PROMPT_COMMAND ordering. Needs the non-forking ${ ...; } funsub (bash >= 5.3); +# older bash keeps the previous behaviour. +if [[ -n ${bash_preexec_imported:-} ]] && + ((BASH_VERSINFO[0] > 5 || (BASH_VERSINFO[0] == 5 && BASH_VERSINFO[1] >= 3))); then + # shellcheck disable=SC2016 + __nanuq_bp_arm='${ __bp_preexec_interactive_mode=on; }' + [[ ${PS0-} == *"$__nanuq_bp_arm"* ]] || PS0=$__nanuq_bp_arm${PS0-} + unset __nanuq_bp_arm + log_shell_init "bash-preexec PS0 arming enabled" +fi + # ---- starship -------------------------------------------------------------- if have @STARSHIP@; then @@ -57,8 +82,7 @@ if have @STARSHIP@; then */bash) eval "$(@STARSHIP@ init bash)" ;; */zsh) eval "$(@STARSHIP@ init zsh)" ;; esac - # starship's bash init registers starship_precmd/starship_preexec_all with - # bash-preexec directly; no manual PROMPT_COMMAND wiring needed. + # registers starship_precmd/starship_preexec_all with bash-preexec directly else log_shell_init "starship skipped" fi @@ -77,8 +101,7 @@ if have @ATUIN@; then */bash) eval "$(@ATUIN@ init bash)" ;; */zsh) eval "$(@ATUIN@ init zsh)" ;; esac - # atuin's init registers __atuin_preexec/__atuin_precmd with bash-preexec - # itself; manual array wiring only caused duplicate hook entries. + # registers __atuin_preexec/__atuin_precmd with bash-preexec itself else log_shell_init "atuin skipped" fi @@ -91,9 +114,7 @@ if have zoxide; then */bash) eval "$(zoxide init bash)" ;; */zsh) eval "$(zoxide init zsh)" ;; esac - # zoxide's bash init hooks into bash-preexec (precmd_functions) when it is - # present; manual PROMPT_COMMAND wiring only pushed hooks past - # __bp_interactive_mode and broke preexec ordering. + # appends __zoxide_hook to PROMPT_COMMAND; see the PS0 note above else log_shell_init "zoxide skipped" fi From eb9d7fb9b89742ca50197fd26f87b15c131d4887 Mon Sep 17 00:00:00 2001 From: randogoth Date: Mon, 31 Aug 2026 20:20:04 +0300 Subject: [PATCH 10/10] gnome: fix two extensions broken by the GNOME 49 -> 50 bump Tracking nixos-26.05 moved GNOME Shell from 49 to 50, and the shell hard-disables any extension whose metadata.json shell-version array lacks the running major. Two enabled extensions were left OUT OF DATE, for different reasons: - logomenu@aryan_k was pinned to the v38 zip only because the custom-menu patch was written against v38. nixpkgs 26.05 already ships v41, which supports shell 50, and the patch applies to it cleanly. Drop the version/src pin and keep the override patch-only. - all-in-one-clipboard is still packaged as v19 (shell <=49) on the nixos-26.05 branch; the shell-50 build (v30) landed in unstable and was never backported. Pin src to the v30 zip until it is. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01ABzc7ivGbjKRCASa8uEkjd --- modules/desktop/gnome.nix | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/modules/desktop/gnome.nix b/modules/desktop/gnome.nix index bd9f8ab..70800fd 100644 --- a/modules/desktop/gnome.nix +++ b/modules/desktop/gnome.nix @@ -2,13 +2,18 @@ let logoMenuExtension = pkgs.gnomeExtensions.logo-menu.overrideAttrs (old: { - version = "38"; + patches = (old.patches or [ ]) ++ [ ./patches/logo-menu-custom-menu.patch ]; + }); + + # nixos-26.05 still packages v19, which caps out at GNOME Shell 49; the + # shell-50 build landed only in nixpkgs unstable. Drop once 26.05 backports it. + aioClipboardExtension = pkgs.gnomeExtensions.all-in-one-clipboard.overrideAttrs (_: { + version = "30"; src = pkgs.fetchzip { - url = "https://extensions.gnome.org/extension-data/logomenuaryan_k.v38.shell-extension.zip"; - hash = "sha256-GdGn7YKfOOEM0kV1Gd9H5c1KKjs/+/4XCsxO2pFdqu4="; + url = "https://extensions.gnome.org/extension-data/all-in-one-clipboardNiffirgkcaJ.github.com.v30.shell-extension.zip"; + hash = "sha256-zESXhXVWVttjAN/75zNfF7u7uJKGMARpilBgYkkpMWs="; stripRoot = false; }; - patches = (old.patches or [ ]) ++ [ ./patches/logo-menu-custom-menu.patch ]; }); in { @@ -99,7 +104,7 @@ in pkgs.papirus-icon-theme pkgs.file-roller pkgs.unrar - pkgs.gnomeExtensions.all-in-one-clipboard + aioClipboardExtension pkgs.gnomeExtensions.alphabetical-app-grid pkgs.gnomeExtensions.app-hider pkgs.gnomeExtensions.apps