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/ 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` 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/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 ff9c8e2..66a10f5 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"; @@ -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/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`). 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..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 { @@ -30,6 +35,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 +49,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,7 +102,9 @@ in pkgs.gnome-extension-manager pkgs.mission-center pkgs.papirus-icon-theme - pkgs.gnomeExtensions.all-in-one-clipboard + pkgs.file-roller + pkgs.unrar + aioClipboardExtension pkgs.gnomeExtensions.alphabetical-app-grid pkgs.gnomeExtensions.app-hider pkgs.gnomeExtensions.apps @@ -129,7 +139,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..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 @@ -16,7 +19,6 @@ ../system/printer.nix ../system/impermanence.nix ../desktop/desktop-entries.nix - ../desktop/file-manager.nix ../desktop/gnome.nix ../users/admin.nix ]; 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 = "/"; 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; 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: 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