hosts/desktop carried two SET-VIA-disko-install placeholders, so the file did not say which disks the machine has. The invalid paths only ever caught a missing --disk flag, never a wrong one, and paid for it by leaving the hardware undocumented. The system disk is the 512G XPG, /home goes on the 1T Crucial; disko-install --disk main|home still overrides both. The 1T currently holds an ext4 Data partition, which the install erases. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
26 KiB
26 KiB
Decisions
2026-08-20 – The desktop declares its real disks
- Who: Dominik, ClaudeCode
- Why:
hosts/desktopstill carried the twoSET-VIA-disko-install--disk-*placeholders from the entry below, so the file did not say which disks the machine has. The ThinkPad names its disk, and nothing about the desktop's two is secret: read off the machine, the system disk is the 512Gnvme-XPG_GAMMIX_S11_Pro_2K322LAES7JGand/homegoes on the 1Tnvme-CT1000P1SSD8_2030E2BAC109. - Tradeoffs: The invalid paths were a safety net — a forgotten
--diskflag failed the install instead of erasing something. That net only ever caught the case of a missing flag, and it paid for it by making a rebuild of an installed desktop impossible to evaluate honestly and by leaving the hardware undocumented. With correct paths a forgotten flag installs to the intended disk, which is the outcome the flag would have asked for anyway. A wrong flag was never caught by either variant. - How: The paths in
hosts/desktop/default.nix, with the comment now saying where they came from and thatdisko-install --disk main|homestill overrides them. Note that the 1T currently holds an ext4Datapartition — installing erases it. README's "Adding another machine" step 4 no longer advertises the placeholders.
2026-08-20 – A placeholder ~/.zshrc stops the zsh new-user wizard
- Who: Dominik, ClaudeCode
- Why:
zsh-newuser-installcame up on every interactive login on the test VM. Per zsh's own documentation thezsh/newusermodule runs it for as long as the user has none of.zshenv,.zprofile,.zshrcor.zloginin$HOME/$ZDOTDIR(zshall, "New User Installation", and the function description inzshcontrib); it exits at once for root, which is why only the user account saw it. Here the whole configuration is/etc/zshrc, which is not a personal file, so the condition never cleared. Independent of the ordering fixes below — the trigger is the set of files in the home directory, not the content of/etc/zshrc. The NixOSprograms.zshmodule does not cover the case. - Tradeoffs: (1) Which side — disabling the
zsh/newusermodule system-wide would need no home state, but the documented off switch is on the administrator's zsh build, not a NixOS option, and it would also take the wizard away from a user who wants it. Seeding the file uses the mechanism zsh itself names and reuses thesystemd.user.tmpfilesseed already in the module for waypaper and swaylock. (2) What is in the file — on Arch this file readssource /etc/zsh/zshrc.moonarch, and copying that here would source the shared zshrc twice, once from/etc/zshrcand once from the home file: duplicate aliases, a secondcompinit, the plugins loaded twice and$HOME/.local/binprepended toPATHtwice. The seeded file therefore carries comments only. It doubles as the documented place for the user's own additions, which zsh reads after/etc/zshrc. - How:
modules/desktop.nix—userZshrcviapkgs.writeText, seeded asC %h/.zshrc 0644 - - - ${userZshrc}next to the two existing rules.Ccopies once and never overwrites, so later edits to the file survive a rebuild. Verified by evaluatingsystemd.user.tmpfiles.rulesfor thetestvmhost.
2026-08-20 – The sourced zshrc is defended against the /etc/zshrc defaults
- Who: Dominik, ClaudeCode
- Why:
programs.zsh.interactiveShellInitsources the shareddefaults/shell/zshrc, but the shell on the test VM did not look like the one on Arch. The file is not at fault — the pinnedmoonarchinput is the same revision as the working tree and the file is byte-identical. The generated/etc/zshrcis:nixos/modules/programs/zsh/zsh.nixplacesinteractiveShellInitat line 277, thenenvironment.shellAliasesat 285, thenpromptInitat 288. Three defaults therefore land after our file and win. (1)promptInitdefaults toautoload -U promptinit && promptinit && prompt suse, andprompt suseassignsPS1— the same parameter asPROMPT— so the Catppuccin prompt was replaced by the SUSE one. (2)environment.shellAliasescarriesls,llandlasmkDefault(nixos/modules/config/shells-environment.nix:235), shadowing the eza aliases. (3) Unrelated to ordering: the zshrc sources zsh-syntax-highlighting and zsh-autosuggestions from/usr/share/zsh/plugins, the path of the Arch packages. The lookups are[[ -f ]]-guarded, so both plugins were silently absent. - Tradeoffs: (1) Where the prompt is owned — moving the prompt into
promptInitwould place it in the slot NixOS intends for it, at the price of splitting one shared file across two options and diverging from Arch. SettingpromptInit = ""keeps the zshrc the single owner. (2) Alias removal — redefiningls/ll/lin Nix would duplicate the eza commands in a second place, where they can drift from the zshrc.nullremoves the alias (zsh.nix:20filters null values out) and leaves the definition in one file. Scoped toprograms.zsh.shellAliasesrather thanenvironment.shellAliases, so bash keeps the coreutilsls— it has no eza aliases to fall back on. (3) Plugins — a/usr/share/zsh/pluginssymlink farm via tmpfiles would make the existing lookups succeed and need no Nix-side option, but it rebuilds an FHS path for a file that the NixOS modules already know how to source. The modules were chosen, as with the polkit exception the other way around (2026-08-07). - How:
modules/desktop.nix—programs.zsh.promptInit = "",programs.zsh.shellAliaseswithls/ll/lset tonull,programs.zsh.syntaxHighlighting.enableandprograms.zsh.autosuggestions.enablewithstrategy = [ "history" "completion" ], the value the zshrc sets on Arch. Verified by evaluatingenvironment.etc.zshrc.textfor thetestvmhost before and after: the prompt and alias blocks are now empty, and both plugins are sourced from the store. - Remaining exception: the zshrc's
orphansanduninstallaliases callpacman. They are dead on NixOS but harmless, and rewriting them per distro would fork the shared file. - Not changed:
compinitruns twice — once fromenableGlobalCompInit(zsh.nix:266), once in the zshrc. Duplicated work, no defect, left alone.
2026-08-20 – Installing from an own ISO, one command
- Who: Maintainer, ClaudeCode
- Why: The documented install was a five-line
nix runinvocation typed at the guest console, reading the configuration from Gitea — so only pushed states were installable, and a typo in a long command cost the whole run. - Tradeoffs:
nixos-anywhereand an SSH-driven install were ruled out: the machine is installed in front of it, not over the network. A script or flake app in the repository still has to be fetched before it can run, which is the problem it was meant to solve. Baking the target closure into the image would make the install work offline, at four to six gigabytes per rebuild — not taken. - How:
hosts/installerbuilds an ISO (nix build .#installer-iso) that carries this flake as/etc/moonix, disko and one wrapper. The guest runsmoonix-install <host>: partition and format per the host file, install, then clone the repository into the account's~/flakes/moonix, hand it to the account and point/etc/nixos/flake.nixat it. The image is built from the local tree, so unpushed changes are installable. Network is still needed — the packages come from the binary cache. - Also: disko aborts the whole format script when the two passphrase
entries differ, because
exit 1sits inside a function called from theuntilloop meant to repeat the prompt (share/disko/lib/types/luks.nix:151). The installer uses a disko patched toreturn 1there, so a typo asks again instead of restarting the install.
2026-08-20 – The checkout on a machine lives in ~/flakes/moonix
- Who: Maintainer, ClaudeCode
- Why:
disko-installleaves 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/nixosisnixos-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 withinstall -d -ofixes that by making a system directory user-writable, which is a workaround rather than a layout./opt/moonixwould mirrormoonarch, which archinstall clones to/opt/moonarch, but that copy is root-owned and never committed from — these files are.~/Reposis the scratch area for development checkouts on the workstation, not a permanent location.~/.config/flakesis 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.nixis a symlink to itsflake.nix.man 8 nixos-rebuilddocuments 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 switchtherefore needs no arguments and picks the host by hostname. Theflakesdirectory is plural on purpose:moonarchleaving itsflake = falsestate, 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 whatdisko-installdid.
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
/homebelongs on the large one.modules/disk.nixonly 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
/mntand leaving/homesmall 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/homeas its own btrfs keeps both disks independent and leaves the existing subvolume names alone. - How:
moonarch.disk.home.enableadds a second GPT with one partition, wrapped in LUKScrypted-homewhenencryptis set, holding the/homesubvolume. The main disk drops/homein 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/desktopdeclares deliberately invalid device paths, becausedisko-install --disk main|homeoverrides them and a forgotten flag should fail rather than erase the wrong disk. Without anixos-hardwareprofile the host setsenableRedistributableFirmwareandhardware.cpu.amd.updateMicrocodeitself, and forcesservices.fwupdoff — 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.enableRedistributableFirmwarewas never set. Thenixos-hardwareprofile does not set it either —common/cpu/amdreads 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 = truein the host file, which the profile'smkDefaultthen turns into a microcode update.services.fprintd.enablealongside it — the NixOS module wirespam_fprintdinto the PAM stacks it owns; a finger is enrolled withfprintd-enrollafter 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 afterdisko-installwithchmod: 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:
initialPasswordputs a plaintext password in the repository. Weighed againsthashedPasswordFile, which keeps the secret out of the repo but needs the file on the target disk before the first boot — either throughdisko-install --extra-filesor acpinto 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,mutableUsersleft at its default sopasswdoverrides it permanently. Newmodules/user.nixdeclaresmoonarch.userwithout a default, because the name belongs to the machine;users.users, the autologininitial_session.userand snapper'sALLOW_USERSall read it, so the login name exists once per host instead of five times. README loses thenixos-enterstep and the stale "Known gaps" section —Super+Creaches the Quickshell clipboard popout, which is wired up and whosecliphistbackend is installed.
2026-08-14 – moonset and moonlock dropped; the power menu is a Quickshell popout
- Who: Maintainer, ClaudeCode
- Why: The power menu became
PowerPopout.qmlin the moonarch Quickshell config (seemoonarch/DECISIONS.md, same date), and moonset was the last thing invoking moonlock. Both packages therefore leave this flake with nothing to replace them: the popout ships inside the config tree that is already deployed. - Tradeoffs: The flake keeps the ability to package a moonarch program —
stasisandsweet-cursorsstill use that path — so nothing was generalised away. What is lost is the pinning discipline for two programs that no longer exist here; the tags inflake.nixwere the only record of which version was deployed, and that record now lives in the retired repos' own history. - How: Inputs
moonlockandmoonsetremoved,moonarchPackagesentries with them,pkgs/moonlock.nixandpkgs/moonset.nixdeleted,flake.lockregenerated.modules/desktop.nixdrops both packages fromsystemPackages, the generatedenvironment.etc."moonlock/moonlock.toml"and"moonset/moonset.toml", and the moonlock PAM stack — swaylock's comes fromprograms.niri.enable.
2026-08-14 – regreet and swaylock from nixpkgs, autologin after LUKS
- Who: Maintainer, ClaudeCode
- Why: moongreet and moonlock were dropped as self-maintained projects; the
reasoning is in
moonarch/DECISIONS.mdfor the same date. Here the change is mostly subtraction: two flake inputs, two build recipes and a generated config disappear in favour of a nixpkgs module and a nixpkgs package. Since LUKS already asks for a passphrase at boot, the greeter was also asking for a second one for no gain, so autologin replaces it on both hosts. - Tradeoffs: (1) Greeter host — the regreet module sets
services.greetd.settings.default_session.commandonly asmkDefault(dbus-run-session cage -- regreet), so the existing unconditional niri line keeps winning. Taking cage would have meant one Wayland compositor fewer in the greeter path, butniri-greeter.kdlis wherelayout "de"andnumlocklive, and nothing in nixpkgs 26.11 exportsXKB_DEFAULT_LAYOUTfromservices.xserver.xkb.layout— verified. A greeter on a US layout rejects every password with an affected character, so niri stays and Arch and NixOS keep sharing that one file. (2) regreet.toml — the module writes/etc/greetd/regreet.tomlitself, so the committed file fromdefaults/etc/greetd/is deliberately not deployed verbatim; declaring the same path inenvironment.etcwould collide. The values are duplicated into module options, which is the price for the wallpaper store path. (3) swaylock config — nixpkgs passes only--prefixto meson, so swaylock'sSYSCONFDIRsits inside the store and/etc/swaylock/configis never read. The config is therefore seeded into$HOMEwith the sameCtmpfiles rule waypaper uses, which also means later user edits survive but updates do not propagate. (4) Colloid overrides — desktop and greeter need the same derivation, and two module files cannot share alet. Moved intoflake.nixand passed as_module.args.moonarchThemesrather than duplicating the override, which would drift. - How:
moongreetinput,moonarchPackages.moongreetandpkgs/moongreet.nixremoved.modules/greetd.nixnow configuresservices.displayManager.regreet(background, GTK, commands, theme, icon theme, cursor theme, font,extraCsspointing at the CSS in the moonarch input) and keeps the niridefault_session.modules/desktop.nixgainsswaylock, the seeded~/.config/swaylock/configand — moved out ofgreetd.nix, next to the package it belongs to — the moonlock PAM stack.initial_sessionis set per host, because the accounts are named differently. - Remaining exception:
moonlockstays packaged and installed.moonset/src/power.rsspawns it by name and has nolock_commandoption, so its Lock action would otherwise fail. Idle and lid-close already go through swaylock. Two lockers coexist until moonset is released with that call changed; then the input, the recipe, the PAM stack andenvironment.etc."moonlock/moonlock.toml"all go. - Consequence: With autologin, the greeter no longer forces an account to
have a password, but swaylock still needs one —
passwdafter installing stays mandatory, andhosts/testvmhas no password declared at all.
2026-08-11 – Disk layout via disko after all, as a shared module
- Who: Maintainer, ClaudeCode
- Why: The Calamares route decided below turned out to be six manual steps,
one of which — copying
hardware-configuration.nixinto the repo — fails silently unless the file is alsogit added, because Nix only sees tracked files in a git repository. It also does not scale: there will be more machines than this one, and each would repeat the whole procedure. - Tradeoffs: The argument against disko was that no target machine existed and device paths would have to be guessed. That has expired. Keeping Calamares would avoid writing a layout module, but leaves partitioning, encryption and subvolumes outside version control, where they cannot be reviewed or repeated.
- How:
modules/disk.nixdefinesmoonarch.disk.{enable,device,encrypt, espSize,swapSize}and buildsdisko.devicesfrom it, so a new machine is a device path rather than a new layout. Installation is a singledisko-install --flake … --disk main <device>from the ISO.hardware-configuration.nixis not needed:nvme,ahci,sd_modand the USB HID modules are in the initrd by default viaboot.initrd.includeDefaultModules, andnixpkgs.hostPlatformcomes fromnixosSystem.hosts/testvmuses the same module against/dev/vda; its 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.<name>declares none, so it is set once vianixos-enterafter installing rather than committing a hash to the repository.
2026-08-11 – Swapfile without hibernation
- 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_offsetkernel parameter. On btrfs that offset changes whenever the swapfile is recreated, which is a standing source of breakage. Avoiding it means LUKS → LVM → btrfs LV plus a swap LV, one layer more. A swap partition cannot be resized later without repartitioning; a swapfile can. - How: 8G swapfile in a
/.swapvolsubvolume, no hibernation, no LVM. Sized as a safety valve against memory pressure, not as a replacement for the 30 GiB of RAM. Revisit by growing the swapfile and addingresume_offset— neither needs a reinstall.
2026-08-07 – Separate repo instead of Nix files in the application repos
- 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
build recipe in one commit — no drift between PKGBUILD
pkgverand flake input. A separate repo keeps the Arch repos untouched, at the cost of one extra commit per version bump (tag inflake.nix, thennix flake update). - How: New repo
moonix, consuming the application repos as flake inputs pinned to release tags.
2026-08-07 – Configs deployed as files, not translated to Nix options
- 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. - How:
environment.etc.<path>.sourcepointing into the moonarch flake input. Only files containing FHS paths are generated instead of copied.
2026-08-07 – No Home Manager
- 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
equally doable with
ln,gsettingsandinstall; the argument for Home Manager was convention, not need. Two of the three cases disappeared entirely: dconf defaults can be set system-wide viaprograms.dconf.profiles.user.databases, and stasis reads/etc/stasis/stasis.runeas a documented fallback. - How: Rejected. For files that genuinely have no system-wide fallback,
systemd.user.tmpfileswith theCdirective seeds a copy into$HOMEwithout overwriting later edits.
2026-08-07 – FHS paths fixed in the source, not bridged with symlinks
- Who: Maintainer
- Why: moonlock, moongreet and moonset invoked
systemctl,loginctlandmoonlockthrough absolute/usr/binpaths, and moongreet scanned/usr/share/{wayland-,x}sessions. All of it fails on NixOS. - Tradeoffs:
systemd.tmpfilessymlinks would have avoided touching the code, but they recreate an FHS layout under NixOS and only cover the paths known at the time — a new one surfaces at runtime, when the user presses the button. - How: Programs resolve via
PATH, session directories walkXDG_DATA_DIRSwith/usr/shareas fallback. Behaviour on Arch is unchanged. Released as moonlock 0.6.21, moongreet 0.10.2, moonset 0.9.2. - Remaining exception: the polkit agent is spawned by its FHS path from
defaults/xdg/niri/config.kdl, which is shared with Arch, where the binary lives outsidePATH. That one stays a symlink.
2026-08-07 – waybar and swaync not installed
- 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.
- Tradeoffs: Installing them would mirror the config source exactly, at the price of carrying two unused implementations.
- How: Neither package, config nor the waybar-only helper scripts are
installed. That also retires
moonarch-cpugov,moonarch-sink-switcherandmoonarch-nightlight: the waybar config was their only caller, and the Quickshell popouts cover the same ground.
2026-08-07 – QEMU/KVM as the test host, with a caveat
- 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-zenand virtio-gpu carries Wayland. However, passing the guest through to the host GPU triggered an amdgpu hard recovery twice, killing the QEMU process. Software rendering is not an alternative: without a 3D capable virtio GPU, niri's TTY backend fails every buffer import withError::DeviceMissingand never creates an output. - How:
virtio-vga-glwithgl=on, and the VM is shut down when not in use. The underlying amdgpu problem is a host issue, unrelated to Moonarch.
2026-08-07 – Disk layout via Calamares, not disko
- 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:
diskowould put partitioning and encryption under version control, which pays off when reinstalling often or setting up several machines alike. For a single machine it is extra work without return, and it would require guessing device paths. Calamares already covers partitioning, LUKS and hardware detection, and produceshardware-configuration.nix— the one file that cannot be written in advance. - How:
hosts/moonarch/holds only machine independent settings. Installation runs through Calamares, then the generated hardware file is copied into the host directory. See README.md.