From 55c81dbb8ab4f8a0c68f32ebc0a7f84f3560bd25 Mon Sep 17 00:00:00 2001 From: nevaforget <> Date: Thu, 20 Aug 2026 12:09:23 +0200 Subject: [PATCH] docs: correct false claims in the README Verified against nix eval, upstream sources and a local test: - testvm accepts SSH password authentication (NixOS default), the README claimed the opposite - only /boot goes through by-partlabel; the filesystems inside the container come up as /dev/mapper/crypted - kanshi parses an empty config fine; a missing one is the error case, in the README and in the desktop module comment - modules/user.nix ABOUTME claimed a login name shared by every host - the flake imports six modules, not five Rewrites "Keeping a machine up to date": the checkout is ~/flakes/moonix and /etc/nixos/flake.nix a symlink to it, which man 8 nixos-rebuild documents. Drops the git and ssh-keygen basics. Adds an optional LUKS header backup step, and records the checkout decision in DECISIONS.md. --- DECISIONS.md | 122 +++++++++++++++++++++++++++++---- README.md | 162 +++++++++++++++++++++++++++++++++++--------- modules/desktop.nix | 9 +-- 3 files changed, 244 insertions(+), 49 deletions(-) diff --git a/DECISIONS.md b/DECISIONS.md index 5a293c3..4e1d598 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -1,8 +1,106 @@ # Decisions +## 2026-08-20 – The checkout on a machine lives in `~/flakes/moonix` + +- **Who**: Maintainer, ClaudeCode +- **Why**: `disko-install` leaves no checkout behind, and the README documented + one that was invented rather than derived: a clone in the home root plus a + symlink, justified with reasons that did not hold. The configuration has to be + editable and committable on the machine it describes, with the account's + identity on the commits and the account's key on the push. +- **Tradeoffs**: `/etc/nixos` is `nixos-rebuild`'s default path, but a repository + there belongs to root: commits would be root's and pushing would need root's + key. Handing the directory to the account with `install -d -o` fixes that by + making a system directory user-writable, which is a workaround rather than a + layout. `/opt/moonix` would mirror `moonarch`, which archinstall clones to + `/opt/moonarch`, but that copy is root-owned and never committed from — + these files are. `~/Repos` is the scratch area for development checkouts on + the workstation, not a permanent location. `~/.config/flakes` is + configuration space for programs reading it on this machine; the repository is + not read as configuration, it is the source a system is built from. +- **How**: The checkout is `~/flakes/moonix`, owned by the account, and + `/etc/nixos/flake.nix` is a symlink to its `flake.nix`. `man 8 nixos-rebuild` + documents exactly this: the file "may be a symlink to a flake.nix in an actual + flake; thus /etc/nixos need not be a flake", and `--flake` "defaults to the + directory containing the target of the symlink". `sudo nixos-rebuild switch` + therefore needs no arguments and picks the host by hostname. The `flakes` + directory is plural on purpose: `moonarch` leaving its `flake = false` state, + dev flakes for the compiled projects and devShells would live beside it. A + machine that is never edited locally keeps no checkout and builds from the + Gitea reference instead, which is what `disko-install` did. + +## 2026-08-20 – Desktop host, /home on a second disk + +- **Who**: Maintainer, ClaudeCode +- **Why**: The desktop has a 512G system disk and a 1T disk, and `/home` + belongs on the large one. `modules/disk.nix` only knew a single disk, so the + layout had nowhere to put the second. +- **Tradeoffs**: A single btrfs spanning both disks would be one filesystem and + one LUKS container, but it ties the two together: losing either loses the + pool, and the system disk stops being restorable on its own. Mounting the 1T + somewhere under `/mnt` and leaving `/home` small keeps the module untouched, + at the price of configs and data living apart and snapper needing two configs + with different meanings. A second disk carrying `/home` as its own btrfs + keeps both disks independent and leaves the existing subvolume names alone. +- **How**: `moonarch.disk.home.enable` adds a second GPT with one partition, + wrapped in LUKS `crypted-home` when `encrypt` is set, holding the `/home` + subvolume. The main disk drops `/home` in that case, so the subvolume exists + exactly once. No second ESP: the firmware boots from the main disk. Both + disks want the same passphrase — systemd's initrd caches the first one and + retries it on the second container, so the boot prompts once even though + formatting asks four times. `hosts/desktop` declares deliberately invalid + device paths, because `disko-install --disk main|home` overrides them and a + forgotten flag should fail rather than erase the wrong disk. Without a + `nixos-hardware` profile the host sets `enableRedistributableFirmware` and + `hardware.cpu.amd.updateMicrocode` itself, and forces `services.fwupd` off — + desktop mainboard vendors mostly do not publish to the LVFS. + +## 2026-08-20 – ThinkPad: redistributable firmware and the fingerprint reader + +- **Who**: Maintainer, ClaudeCode +- **Why**: `hardware.enableRedistributableFirmware` was never set. The + `nixos-hardware` profile does not set it either — `common/cpu/amd` + reads it (`hardware.cpu.amd.updateMicrocode = lib.mkDefault + config.hardware.enableRedistributableFirmware`), so with the option off the + machine ran on the microcode in its BIOS and without the firmware the WLAN + card and amdgpu load. +- **Tradeoffs**: The firmware is redistributable but not free, so enabling it + is a deliberate step rather than a default. Declining it means no microcode + updates on a CPU whose errata are fixed exactly there. +- **How**: `hardware.enableRedistributableFirmware = true` in the host file, + which the profile's `mkDefault` then turns into a microcode update. + `services.fprintd.enable` alongside it — the NixOS module wires `pam_fprintd` + into the PAM stacks it owns; a finger is enrolled with `fprintd-enroll` after + installing. + +## 2026-08-19 – Initial password in the host file, account name as an option + +- **Who**: Maintainer, ClaudeCode +- **Why**: An install that leaves the account without a password produces a + system whose lock screen never opens — swaylock authenticates through + pam_unix. The documented remedy, `nixos-enter --root /mnt -c passwd`, is the + path that fails after `disko-install` with `chmod: changing permission of + '/mnt/dev': Operation not permitted` (disko issue #638, closed as not + planned). Alongside that, the host files carried a real name in five places. +- **Tradeoffs**: `initialPassword` puts a plaintext password in the repository. + Weighed against `hashedPasswordFile`, which keeps the secret out of the repo + but needs the file on the target disk before the first boot — either through + `disko-install --extra-files` or a `cp` into the still-mounted `/mnt`. Both + are extra steps in exchange for protecting a value that is meant to be + replaced on first login anyway. The 2026-08-11 decision to keep any password + out of the repository is therefore reversed for the initial one. +- **How**: `initialPassword = "moonarch"` in both hosts, `mutableUsers` left at + its default so `passwd` overrides it permanently. New `modules/user.nix` + declares `moonarch.user` without a default, because the name belongs to the + machine; `users.users`, the autologin `initial_session.user` and snapper's + `ALLOW_USERS` all read it, so the login name exists once per host instead of + five times. README loses the `nixos-enter` step and the stale "Known gaps" + section — `Super+C` reaches the Quickshell clipboard popout, which is wired up + and whose `cliphist` backend is installed. + ## 2026-08-14 – moonset and moonlock dropped; the power menu is a Quickshell popout -- **Who**: Dominik, ClaudeCode +- **Who**: Maintainer, ClaudeCode - **Why**: The power menu became `PowerPopout.qml` in the moonarch Quickshell config (see `moonarch/DECISIONS.md`, same date), and moonset was the last thing invoking moonlock. Both packages therefore leave this flake with nothing to @@ -21,7 +119,7 @@ ## 2026-08-14 – regreet and swaylock from nixpkgs, autologin after LUKS -- **Who**: Dominik, ClaudeCode +- **Who**: Maintainer, ClaudeCode - **Why**: moongreet and moonlock were dropped as self-maintained projects; the reasoning is in `moonarch/DECISIONS.md` for the same date. Here the change is mostly subtraction: two flake inputs, two build recipes and a generated config @@ -69,7 +167,7 @@ ## 2026-08-11 – Disk layout via disko after all, as a shared module -- **Who**: Dominik, ClaudeCode +- **Who**: Maintainer, ClaudeCode - **Why**: The Calamares route decided below turned out to be six manual steps, one of which — copying `hardware-configuration.nix` into the repo — fails silently unless the file is also `git add`ed, because Nix only sees tracked @@ -91,12 +189,12 @@ generated file was deleted, having described the UUIDs of a qcow2 that no longer exists. - **Consequence**: The user password no longer arrives with the installer. - `users.users.dkressler` declares none, so it is set once via `nixos-enter` + `users.users.` declares none, so it is set once via `nixos-enter` after installing rather than committing a hash to the repository. ## 2026-08-11 – Swapfile without hibernation -- **Who**: ClaudeCode, on Dominik's request for a recommendation +- **Who**: ClaudeCode, on the maintainer's request for a recommendation - **Why**: The layout module needs a swap default. - **Tradeoffs**: Hibernation needs swap at least the size of RAM plus a `resume_offset` kernel parameter. On btrfs that offset changes whenever the @@ -110,7 +208,7 @@ ## 2026-08-07 – Separate repo instead of Nix files in the application repos -- **Who**: Dominik, ClaudeCode +- **Who**: Maintainer, ClaudeCode - **Why**: The Nix build recipes had to live somewhere without disturbing the existing repos. - **Tradeoffs**: Files inside each project repo would keep version, code and @@ -122,7 +220,7 @@ ## 2026-08-07 – Configs deployed as files, not translated to Nix options -- **Who**: Dominik +- **Who**: Maintainer - **Why**: One set of config files should stay valid on NixOS and Arch alike. - **Tradeoffs**: Native Nix options (`programs.niri.settings`) are idiomatic and type checked, but every config would exist twice and drift on each change. @@ -131,7 +229,7 @@ ## 2026-08-07 – No Home Manager -- **Who**: Dominik +- **Who**: Maintainer - **Why**: Initially proposed for the three home-directory tasks in `post-install.sh` (GTK4 symlinks, `gsettings`, seeding the stasis config). - **Tradeoffs**: None in favour, as it turned out. Every one of those tasks is @@ -146,7 +244,7 @@ ## 2026-08-07 – FHS paths fixed in the source, not bridged with symlinks -- **Who**: Dominik +- **Who**: Maintainer - **Why**: moonlock, moongreet and moonset invoked `systemctl`, `loginctl` and `moonlock` through absolute `/usr/bin` paths, and moongreet scanned `/usr/share/{wayland-,x}sessions`. All of it fails on NixOS. @@ -163,7 +261,7 @@ ## 2026-08-07 – waybar and swaync not installed -- **Who**: Dominik +- **Who**: Maintainer - **Why**: Quickshell provides both the bar and the notifications. The config source still carries waybar and swaync, marked "kept as a reserve" — a note left over from an earlier migration. @@ -176,7 +274,7 @@ ## 2026-08-07 – QEMU/KVM as the test host, with a caveat -- **Who**: Dominik, ClaudeCode +- **Who**: Maintainer, ClaudeCode - **Why**: VirtualBox was the first choice, but its 3D acceleration is documented as broken with Wayland compositors. - **Tradeoffs**: QEMU needs no DKMS module on `linux-zen` and virtio-gpu @@ -190,7 +288,7 @@ ## 2026-08-07 – Disk layout via Calamares, not disko -- **Who**: Dominik +- **Who**: Maintainer - **Why**: Stage 3 targets bare metal, but no target machine exists yet — neither the number of disks nor their device paths are known. - **Tradeoffs**: `disko` would put partitioning and encryption under version diff --git a/README.md b/README.md index 492d5dc..a021c94 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ pkgs/ Build recipes sweet-cursors.nix cursor theme moonarch-scripts.nix battery and camera helpers modules/ + user.nix moonarch.user option, set per host disk.nix partitioning: GPT, optional LUKS2, btrfs subvolumes desktop.nix /etc deployment, package set, theme greetd.nix greetd + regreet, autologin per host @@ -30,10 +31,11 @@ modules/ hosts/ testvm/ QEMU test VM thinkpad/ ThinkPad T14 Gen 3 (AMD) + desktop/ self-built AMD desktop, second disk for /home ``` -`nixosModules.moonarch` is the entry point: it imports all three modules and -hands them the packages and the config source. +`nixosModules.moonarch` is the entry point: it imports disko's NixOS module +alongside the five above and hands them the packages and the config source. ## Usage @@ -43,13 +45,15 @@ Build a single package: nix build .#stasis ``` -Build a whole system without activating it: +Build a whole system without activating it, which is also how a host is checked +for evaluation errors without touching anything: ```bash nix build .#nixosConfigurations.testvm.config.system.build.toplevel ``` -Activate: +Activate. This runs on the machine being changed, out of a checkout that sits +there — see "Keeping a machine up to date" for how that checkout gets there: ```bash sudo nixos-rebuild switch --flake .#testvm @@ -70,6 +74,7 @@ the NixOS ISO and run one command. ```bash sudo nix --experimental-features "nix-command flakes" run \ 'github:nix-community/disko/latest#disko-install' -- \ + --write-efi-boot-entries \ --flake 'git+https://gitea.moonarch.de/nevaforget/moonix.git#thinkpad' \ --disk main /dev/disk/by-id/nvme-SKHynix_HFS001TEJ9X102N_SDB7N7740101A8708 ``` @@ -77,20 +82,20 @@ the NixOS ISO and run one command. The LUKS passphrase is asked for twice while formatting. The repo is fetched from the flake reference, so the ISO needs no `git` and nothing is cloned. -2. Set the user password. Nothing has created one yet. Autologin gets past the - greeter without it, but swaylock authenticates through pam_unix and cannot - unlock a screen for an account that has no password: + `--write-efi-boot-entries` is not optional here. Without it `disko-install` + forces `boot.loader.efi.canTouchEfiVariables` off, overriding the `true` in + the host file, and the install finishes without an NVRAM boot entry. Leave it + out only when the disk is installed in one machine and then moved to another. - ```bash - sudo nixos-enter --root /mnt -c 'passwd dkressler' - ``` +2. Reboot. -3. Reboot. - -To skip step 2, put a `mkpasswd -m sha-512` hash into -`users.users.dkressler.initialHashedPassword` — the commented-out line in -`hosts/thinkpad/default.nix` shows where. That commits a password hash to the -repository. +The account comes up with the initial password declared in the host file +(`users.users..initialPassword`, currently `moonarch`). Autologin gets +past the greeter either way, but swaylock authenticates through pam_unix and +cannot unlock a screen for an account without a password, so one has to exist +from the first boot. Change it with `passwd` afterwards — `mutableUsers` is at +its default, so that sticks and the declared value is only ever used to create +the account. `hardware-configuration.nix` is not used and not needed. NixOS puts `nvme`, `ahci`, `sd_mod` and the USB HID modules into the initrd by default @@ -98,9 +103,11 @@ repository. flake, and the `nixos-hardware` profile for the T14 Gen 3 (AMD) covers firmware, microcode, power management and graphics quirks. -The generated `fileSystems` entries point at `/dev/disk/by-partlabel/`, so the -booted system does not care whether the disk enumerates the same way next time. -Only the partitioning step reads `moonarch.disk.device`. +Only the partitioning step reads `moonarch.disk.device`. Nothing the booted +system uses depends on it: the ESP entry and the generated +`boot.initrd.luks.devices` point at `/dev/disk/by-partlabel/`, and the +filesystems inside the container come up as `/dev/mapper/crypted`. Either way +the disk may enumerate differently next time. ### Adding another machine @@ -117,9 +124,32 @@ Only the partitioning step reads `moonarch.disk.device`. `encrypt = false` drops the LUKS layer, `swapSize = ""` the swapfile, and `espSize` resizes the ESP. Everything else is shared. + + A machine with a second disk for `/home` adds `home`, which moves the + subvolume off the first disk: + + ```nix + moonarch.disk.home = { + enable = true; + device = "/dev/disk/by-id/..."; + }; + ``` + + Set the login name in the same file. `moonarch.user` has no default and is + read by `users.users`, the autologin session and snapper, so the name is + declared once per machine. 3. Add a `nixosConfigurations.` entry in `flake.nix`. 4. Install with the same `disko-install` command, with `#` and that - machine's disk. + machine's disk. A second disk is another flag: + + ```bash + --disk main /dev/disk/by-id/... --disk home /dev/disk/by-id/... + ``` + + Both disks are erased. `disko-install` overrides whatever paths the host + file declares, which is why `hosts/desktop` carries invalid placeholders: + forgetting a flag fails the install instead of erasing a disk that was not + meant to be touched. Without a matching `nixos-hardware` profile, set `hardware.cpu.intel.updateMicrocode` or `hardware.cpu.amd.updateMicrocode` in @@ -131,13 +161,79 @@ the host file — the profile is what provides it for the ThinkPad. |---|---|---| | ESP, `espSize` (1G) | `/boot` | vfat, `umask=0077` | | `root` | `/` | btrfs, `compress=zstd`, `noatime` | -| `home` | `/home` | same | +| `home` | `/home` | same; on the second disk where `home.enable` is set | | `nix` | `/nix` | same; separate to keep the store out of snapshots | | `swap` | `/.swapvol` | swapfile, `swapSize` (8G) | With `encrypt = true` everything below the ESP sits in a LUKS2 container named `crypted`. Hibernation is not set up — see `DECISIONS.md`. +A second disk gets its own container, `crypted-home`, and no ESP. Both are +formatted separately, so `disko-install` asks for a passphrase four times +instead of two. Give them the same one: both are unlocked from the initrd, +where systemd caches the first passphrase and retries it on the second +container, so booting prompts once. + +Worth doing once the disks are formatted, though nothing here depends on it: back +up the LUKS headers. They hold the encrypted keyslots, and a damaged header loses +the data whatever the passphrase is. A header only changes when its keyslots do, +so once after the install and again after a passphrase change is enough: + +```bash +sudo cryptsetup luksHeaderBackup /dev/disk/by-partlabel/disk-main-root \ + --header-backup-file /run/media//-main.header +``` + +A second disk carries its own header on `disk-home-home`. Whoever holds such a +file can attack the passphrase offline, so it belongs on a medium that gets put +away — not in this repository and not in a synced directory. + +## Keeping a machine up to date + +`disko-install` built the system from the flake reference and left no checkout +behind, so a freshly installed machine runs its configuration without holding +it. The checkout goes to `~/flakes/moonix` and belongs to the account, so commits +and pushes carry the account's identity. It is not read as configuration by +anything on the machine — it is the source a system is built from, which is why +it sits outside `~/.config`: + +```bash +git clone git@gitea.moonarch.de:nevaforget/moonix.git ~/flakes/moonix +sudo ln -s ~/flakes/moonix/flake.nix /etc/nixos/flake.nix +``` + +`nixos-rebuild` treats `/etc/nixos/flake.nix` as if `--flake` had been given and +resolves the flake to the directory the symlink points into, so `/etc/nixos` +itself is neither a repository nor a flake. Without an attribute the +configuration is picked by hostname, which is why the `nixosConfigurations` are +named after the machines. + +Changing the machine is then an edit, a build and a commit: + +```bash +cd ~/flakes/moonix +$EDITOR hosts/thinkpad/default.nix +sudo nixos-rebuild switch +git commit -am 'thinkpad: ...' +git push +``` + +The build reads the working tree, so a change is testable before it is +committed. Nix only sees tracked files though — a new file under `hosts/` or +`modules/` has to be `git add`ed or evaluation fails on the missing path. + +A machine that is never edited locally can skip the checkout and build from the +reference, which is what `disko-install` did: + +```bash +sudo nixos-rebuild switch \ + --flake 'git+https://gitea.moonarch.de/nevaforget/moonix.git#thinkpad' +``` + +Every `switch` leaves a generation behind. A bad one is left through the boot +menu, where the previous generation is still listed, or with `sudo +nixos-rebuild switch --rollback` from a system that still comes up. + ## Version bumps `stasis` is pinned to a release tag. After a new upstream release: @@ -163,9 +259,11 @@ NixOS module owns the destination: | `waypaper/config.ini` | wallpaper path, rewritten and seeded into `$HOME` | | fontconfig defaults | `/etc/fonts/conf.d` is owned by the NixOS module | -`kanshi/config` is not deployed: the file is empty, kanshi rejects it and -restarts in a loop. Monitor profiles are machine specific and belong in -`~/.config/kanshi/config`; the service starts only when that file exists. +`kanshi/config` is not deployed: the file in the config source is empty, and +monitor profiles are machine specific — they belong in +`~/.config/kanshi/config`. A missing config file is what kanshi treats as an +error (`failed to parse config file`), which with `Restart=on-failure` would +spin, so the service starts only once that file exists. Programs that read only from `$HOME` and have no system-wide fallback get a copy seeded through `systemd.user.tmpfiles` with the `C` directive, which never @@ -192,18 +290,16 @@ cp /usr/share/edk2/x64/OVMF_VARS.4m.fd ~/VMs/moonix-vars.fd Then run the `disko-install` command from above inside the live session, with `#testvm` and `--disk main /dev/vda`. -It deliberately contains no testing shortcuts — no passwordless sudo, no SSH -password authentication. Set those at runtime when needed, for example through -a drop-in under `/run/systemd/system/`, so they do not end up in version -control. +`sudo` asks for a password here like on every other host. SSH accepts one: +`services.openssh.settings.PasswordAuthentication` is at its NixOS default of +`true`, and the account's password is the `initialPassword` from the host file, +which is in this repository. The guest is reachable on the launcher's forward on +`127.0.0.1:2222` with it. Anything a test needs beyond that belongs at runtime — +a drop-in under `/run/systemd/system/`, a key in `~/.ssh/authorized_keys` — so +it does not end up in version control. The guest needs a 3D capable virtio GPU: niri's TTY backend fails every buffer import with `Error::DeviceMissing` otherwise, and software rendering is not a workaround. On the current host that path has twice triggered an amdgpu hard recovery, killing the QEMU process — shut the VM down when not testing. -## Known gaps - -`Super+C` opens the clipboard history through a launcher that is not installed -here, so the binding does nothing. `cliphist` records the clipboard, only the -picker front-end is missing. diff --git a/modules/desktop.nix b/modules/desktop.nix index 61a467b..c742c3f 100644 --- a/modules/desktop.nix +++ b/modules/desktop.nix @@ -52,10 +52,11 @@ in "xdg/quickshell/moonarch".source = "${xdg}/quickshell/moonarch"; "xdg/foot/foot.ini".source = "${xdg}/foot/foot.ini"; "xdg/fastfetch/config.jsonc".source = "${xdg}/fastfetch/config.jsonc"; - # defaults/xdg/kanshi/config is an empty placeholder — on Arch too. kanshi - # rejects it ("failed to parse config file") and restarts in a loop, so it - # is not deployed. Monitor profiles are machine specific and belong in - # ~/.config/kanshi/config. + # defaults/xdg/kanshi/config is an empty placeholder — on Arch too. Not + # deployed: monitor profiles are machine specific and belong in + # ~/.config/kanshi/config. An empty file parses fine; a missing one is what + # kanshi reports as "failed to parse config file", which is why the unit in + # modules/services.nix waits for the home config to exist. "xdg/Kvantum/kvantum.kvconfig".source = "${xdg}/Kvantum/kvantum.kvconfig"; "xdg/qt6ct/qt6ct.conf".source = "${xdg}/qt6ct/qt6ct.conf"; "xdg/gtk-3.0/settings.ini".source = "${xdg}/gtk-3.0/settings.ini";