Files
moonix/DECISIONS.md
nevaforget af101afd14 fix(pipewire): denoising through configPackages, desktop gets steam and bitwig
The RNNoise config was deployed to /etc/xdg/pipewire, which PipeWire
never reads: it resolves $PIPEWIRE_CONFIG_DIR, then the home config, then
its compiled-in /etc/pipewire, and knows nothing of XDG_CONFIG_DIRS. An
installed system therefore had no denoising while carrying the file.

It now goes through services.pipewire.configPackages, with the LADSPA
plugin substituted to its store path since LADSPA_PATH has no /usr here.

Alongside, on the desktop host only: steam via its module (which also
turns on 32-bit graphics), bitwig-studio, and rtkit so a DAW can ask for
realtime scheduling at all.
2026-08-20 18:25:36 +02:00

553 lines
35 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Decisions
## 2026-08-20 Input denoising was deployed to a path PipeWire never reads
- **Who**: Dominik, ClaudeCode
- **Why**: The Arch machine runs RNNoise input denoising, and the config for it
lives in the shared tree as
`defaults/xdg/pipewire/pipewire.conf.d/99-input-denoising.conf`. It was in
`modules/desktop.nix` too, in the generic sweep to `/etc/xdg/` — and inert
there. PipeWire resolves its config directory from `$PIPEWIRE_CONFIG_DIR`,
then `$XDG_CONFIG_HOME/pipewire` or `$HOME/.config/pipewire`, then the
compiled-in `/etc/pipewire`; `XDG_CONFIG_DIRS` appears nowhere in
`src/pipewire/conf.c`. On Arch the file works because `post-install.sh` seeds
it into the home directory. Same class as the swaylock config on 2026-08-18: a
file installed to a plausible path that the program does not read.
- **Tradeoffs**: (1) **Which option** — the module accepts both
`services.pipewire.extraConfig.pipewire.<name>` (Nix attributes, rendered to
JSON) and `services.pipewire.configPackages` (a package carrying
`share/pipewire/pipewire.conf.d/*.conf`); both end up in the same merged
directory (`pipewire.nix:59-75`). The NixOS wiki uses `extraConfig` for its
examples, which are written by hand and have no file behind them. Taking that
route here would mean translating the shared file into Nix and maintaining the
VAD values in two places, so `configPackages` was chosen — which is also what
the module's own option documentation shows for a conf.d file
(`pipewire.nix:258-279`). (2) **The plugin path** — the file names the plugin
as `librnnoise_ladspa`, resolved through `LADSPA_PATH`, which is
`/usr/lib/ladspa` on Arch and does not exist here. There is no LADSPA
counterpart to `services.pipewire.extraLv2Packages`, so the absolute store
path is substituted into the file, the same treatment the wallpaper paths get.
- **How**: `denoisingConfig` via `runCommand` + `substitute`, handed to
`services.pipewire.configPackages`. The `xdg/pipewire/` entry in
`environment.etc` is removed rather than kept for layout parity — after the
swaylock case, an inert copy is a liability. Verified by building the package:
the conf.d file is there, the plugin line points at
`rnnoise-plugin-1.10/lib/ladspa/librnnoise_ladspa.so`, and that file exists.
- **Note**: the filter chain shows up as a separate "Noise Canceling source"
input. It does not replace the raw microphone; applications have to select it.
## 2026-08-20 Steam, Bitwig and rtkit belong to the desktop alone
- **Who**: Dominik, ClaudeCode
- **Why**: Both programs are wanted on the desktop and on no other machine, so
they go into `hosts/desktop/default.nix` rather than the shared module every
host imports.
- **Tradeoffs**: (1) **Steam**`pkgs.steam` in `systemPackages` is the wrong
handle: the module wraps the binary in an FHS environment and turns on
`hardware.graphics.enable32Bit` itself (`nixos/modules/programs/steam.nix:215-218`),
which the bare package would leave off. (2) **Bitwig** — the `bitwig-studio`
attribute currently resolves to `bitwig-studio6-6.0.11`; `bitwig-studio5`
(5.3.13) exists as a separate attribute if a downgrade is ever needed.
Dominik chose 6. (3) **Realtime**`security.rtkit.enable` was off. The
rlimits the PipeWire module installs (`rtprio` 95, `nice` -19, `memlock`)
apply to members of the `pipewire` group, and the account is in
`networkmanager wheel video input plugdev docker` only, so a DAW had no path
to realtime scheduling at all. Enabled on this host, where the DAW is.
- **How**: `programs.steam.enable`, `security.rtkit.enable` and
`environment.systemPackages = [ pkgs.bitwig-studio ]` in the host file.
Verified by evaluating both hosts: on `desktop` all three are set and 32-bit
graphics came along; on `thinkpad` Steam and rtkit stay `false`.
## 2026-08-20 The browser is LibreWolf from nixpkgs, not a self-packaged Waterfox
- **Who**: Dominik, ClaudeCode
- **Why**: A rebuilt desktop had no browser at all — nothing in the package set
provided one, while Arch installs `waterfox-bin`. nixpkgs has no `waterfox`
attribute (checked against the pinned revision, with `firefox` resolving in
the same package set as a control), so matching Arch meant packaging it here.
- **Tradeoffs**: (1) **Waterfox from elsewhere** — the one community flake,
`sammypanda/nixos-waterfox`, is a single commit whose README says "Currently
broken?". Flathub's `net.waterfox.waterfox` tracks releases itself but carries
no verified badge (the listing reads "Medium Risk"), needs the Flatpak stack,
and a NixOS Discourse thread reports crashes and system freezes with that
exact package. Both rejected. (2) **Waterfox packaged here** — this was built
and it worked: the upstream tarball plus `autoPatchelfHook`, `waterfox
--version` reporting 6.6.17, no missing library, and a SHA-512 identical to
the one the Arch package pins. It was then discarded on the maintenance
argument: version and hash are bumped by hand, `nix flake update` does not
move a plain tarball, and nothing signals a missed release. For `stasis` or
`sweet-cursors` that is irrelevant; for the most exposed program on the
machine it means silently running a browser with known holes. A version-check
script was offered as a mitigation and not taken. (3) **What replaces it**
`librewolf` is in nixpkgs, so its bumps arrive with `nix flake update`, and it
is fully substitutable: 179.6 MiB fetched, nothing built locally
(`librewolf-bin` would build six derivations and was not taken). Firefox was
the other candidate; LibreWolf is closer to what Waterfox is for. The price is
stricter defaults — `resistFingerprinting`, clearing on shutdown, restricted
WebGL — which have to be relaxed by hand where they get in the way.
- **How**: `librewolf` in `environment.systemPackages` in `modules/desktop.nix`.
`pkgs/waterfox.nix` is deleted rather than left lying around unused. The
browser therefore differs from Arch, which keeps `waterfox-bin`.
## 2026-08-20 Printing is driverless, VPN gets its plugin, VLC replaces mpv
- **Who**: Dominik, ClaudeCode
- **Why**: Comparing the package set against the Arch lists turned up gaps that
were never decided, only missing: no media player, no VPN plugin, no printing.
`networking.networkmanager.plugins` was at its default `[ ]`, which means
NetworkManager cannot create an OpenVPN connection at all — the type is not
offered by nmcli, nmtui or nm-applet — while Arch installs
`networkmanager-openvpn`. Printing was absent on both sides.
- **Tradeoffs**: (1) **Printing** — the Arch host drives the Samsung M267x 287x
the old way: a `socket://192.168.1.165` queue with the ULD PPD. Reproducing
that here would have meant an unfree driver package plus the printer's address
in version control, and Dominik ruled the printer out of the repository. Asked
over IPP, the device answers with `image/urf` and `application/PCLm` among its
document formats and IPP 2.0 — so driverless printing works and CUPS builds
the queue from what the printer reports about itself. `hardware.printers.
ensurePrinters` and `samsung-unified-linux-driver` (which does carry a
matching `M267x.ppd`) are therefore both unused. The price is a dependency on
mDNS discovery, and Avahi has to run for it; the gain is that any
AirPrint-capable printer works, not just this one. (2) **Media player** — mpv
is what Arch installs, with two AUR scripts; Dominik chose VLC instead, so
this is deliberately not an alignment. (3) **Where it lives**
`modules/desktop.nix`, which every host imports (`flake.nix:95`), rather than
per host: none of it is machine specific.
- **How**: `services.printing.enable`, `services.avahi` with `nssmdns4` (the
part that makes `.local` resolve) and `openFirewall`, which opens UDP 5353.
`networking.networkmanager.plugins = [ pkgs.networkmanager-openvpn ]` plus the
`openvpn` binary, and `vlc`, in `environment.systemPackages`. Verified by
evaluating both `desktop` and `thinkpad`: printing and Avahi on, 5353 in
`allowedUDPPorts`, the plugin and both packages present.
- **Still missing against the Arch lists**: no browser at all — Arch installs
`waterfox-bin`, which nixpkgs does not have. Also absent: `viewnior`,
`blueberry`, `auto-cpufreq`, `snapper`/`snap-pac`, `plocate`, `nmap`, `sshfs`,
`smbclient` and walker with the elephant providers. `ufw` is deliberately not
missing: NixOS' own firewall is enabled on every host with no port open.
## 2026-08-20 The desktop declares its real disks
- **Who**: Dominik, ClaudeCode
- **Why**: `hosts/desktop` still carried the two `SET-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 512G
`nvme-XPG_GAMMIX_S11_Pro_2K322LAES7JG` and `/home` goes on the 1T
`nvme-CT1000P1SSD8_2030E2BAC109`.
- **Tradeoffs**: The invalid paths were a safety net — a forgotten `--disk`
flag 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 that `disko-install --disk main|home` still
overrides them. Note that the 1T currently holds an ext4 `Data` partition —
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-install` came up on every interactive login on the test
VM. Per zsh's own documentation the `zsh/newuser` module runs it for as long
as the user has none of `.zshenv`, `.zprofile`, `.zshrc` or `.zlogin` in
`$HOME`/`$ZDOTDIR` (`zshall`, "New User Installation", and the function
description in `zshcontrib`); 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 NixOS `programs.zsh` module does not
cover the case.
- **Tradeoffs**: (1) **Which side** — disabling the `zsh/newuser` module
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 the `systemd.user.tmpfiles` seed already in the module
for waypaper and swaylock. (2) **What is in the file** — on Arch this file
reads `source /etc/zsh/zshrc.moonarch`, and copying that here would source the
shared zshrc twice, once from `/etc/zshrc` and once from the home file:
duplicate aliases, a second `compinit`, the plugins loaded twice and
`$HOME/.local/bin` prepended to `PATH` twice. 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``userZshrc` via `pkgs.writeText`, seeded as
`C %h/.zshrc 0644 - - - ${userZshrc}` next to the two existing rules. `C`
copies once and never overwrites, so later edits to the file survive a
rebuild. Verified by evaluating `systemd.user.tmpfiles.rules` for the `testvm`
host.
## 2026-08-20 The sourced zshrc is defended against the /etc/zshrc defaults
- **Who**: Dominik, ClaudeCode
- **Why**: `programs.zsh.interactiveShellInit` sources the shared
`defaults/shell/zshrc`, but the shell on the test VM did not look like the one
on Arch. The file is not at fault — the pinned `moonarch` input is the same
revision as the working tree and the file is byte-identical. The generated
`/etc/zshrc` is: `nixos/modules/programs/zsh/zsh.nix` places
`interactiveShellInit` at line 277, then `environment.shellAliases` at 285,
then `promptInit` at 288. Three defaults therefore land after our file and
win. (1) `promptInit` defaults to `autoload -U promptinit && promptinit &&
prompt suse`, and `prompt suse` assigns `PS1` — the same parameter as
`PROMPT` — so the Catppuccin prompt was replaced by the SUSE one. (2)
`environment.shellAliases` carries `ls`, `ll` and `l` as `mkDefault`
(`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
`promptInit` would place it in the slot NixOS intends for it, at the price of
splitting one shared file across two options and diverging from Arch. Setting
`promptInit = ""` keeps the zshrc the single owner. (2) **Alias removal**
redefining `ls`/`ll`/`l` in Nix would duplicate the eza commands in a second
place, where they can drift from the zshrc. `null` removes the alias
(`zsh.nix:20` filters null values out) and leaves the definition in one file.
Scoped to `programs.zsh.shellAliases` rather than `environment.shellAliases`,
so bash keeps the coreutils `ls` — it has no eza aliases to fall back on. (3)
**Plugins** — a `/usr/share/zsh/plugins` symlink 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.shellAliases` with `ls`/`ll`/`l` set to `null`,
`programs.zsh.syntaxHighlighting.enable` and
`programs.zsh.autosuggestions.enable` with `strategy = [ "history"
"completion" ]`, the value the zshrc sets on Arch. Verified by evaluating
`environment.etc.zshrc.text` for the `testvm` host before and after: the
prompt and alias blocks are now empty, and both plugins are sourced from the
store.
- **Remaining exception**: the zshrc's `orphans` and `uninstall` aliases call
`pacman`. They are dead on NixOS but harmless, and rewriting them per distro
would fork the shared file.
- **Not changed**: `compinit` runs twice — once from `enableGlobalCompInit`
(`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 run` invocation 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-anywhere` and 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/installer` builds an ISO (`nix build .#installer-iso`) that
carries this flake as `/etc/moonix`, disko and one wrapper. The guest runs
`moonix-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.nix` at 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 1` sits inside a function called from the
`until` loop meant to repeat the prompt
(`share/disko/lib/types/luks.nix:151`). The installer uses a disko patched to
`return 1` there, 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-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**: 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
replace them: the popout ships inside the config tree that is already deployed.
- **Tradeoffs**: The flake keeps the ability to package a moonarch program —
`stasis` and `sweet-cursors` still 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 in `flake.nix` were the only record of which version was
deployed, and that record now lives in the retired repos' own history.
- **How**: Inputs `moonlock` and `moonset` removed, `moonarchPackages` entries
with them, `pkgs/moonlock.nix` and `pkgs/moonset.nix` deleted, `flake.lock`
regenerated. `modules/desktop.nix` drops both packages from `systemPackages`,
the generated `environment.etc."moonlock/moonlock.toml"` and
`"moonset/moonset.toml"`, and the moonlock PAM stack — swaylock's comes from
`programs.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.md` for 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.command` only as `mkDefault`
(`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, but `niri-greeter.kdl` is where `layout "de"` and `numlock`
live, and nothing in nixpkgs 26.11 exports `XKB_DEFAULT_LAYOUT` from
`services.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.toml` itself, so the committed file from
`defaults/etc/greetd/` is deliberately *not* deployed verbatim; declaring the
same path in `environment.etc` would collide. The values are duplicated into
module options, which is the price for the wallpaper store path. (3)
**swaylock config** — nixpkgs passes only `--prefix` to meson, so swaylock's
`SYSCONFDIR` sits inside the store and `/etc/swaylock/config` is never read.
The config is therefore seeded into `$HOME` with the same `C` tmpfiles 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 a `let`. Moved into `flake.nix`
and passed as `_module.args.moonarchThemes` rather than duplicating the
override, which would drift.
- **How**: `moongreet` input, `moonarchPackages.moongreet` and
`pkgs/moongreet.nix` removed. `modules/greetd.nix` now configures
`services.displayManager.regreet` (background, GTK, commands, theme, icon
theme, cursor theme, font, `extraCss` pointing at the CSS in the moonarch
input) and keeps the niri `default_session`. `modules/desktop.nix` gains
`swaylock`, the seeded `~/.config/swaylock/config` and — moved out of
`greetd.nix`, next to the package it belongs to — the moonlock PAM stack.
`initial_session` is set per host, because the accounts are named differently.
- **Remaining exception**: `moonlock` stays packaged and installed.
`moonset/src/power.rs` spawns it by name and has no `lock_command` option, 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 and
`environment.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 — `passwd` after installing
stays mandatory, and `hosts/testvm` has 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.nix` into the repo — fails
silently unless the file is also `git add`ed, 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.nix` defines `moonarch.disk.{enable,device,encrypt,
espSize,swapSize}` and builds `disko.devices` from it, so a new machine is a
device path rather than a new layout. Installation is a single
`disko-install --flake … --disk main <device>` from the ISO.
`hardware-configuration.nix` is not needed: `nvme`, `ahci`, `sd_mod` and the
USB HID modules are in the initrd by default via
`boot.initrd.includeDefaultModules`, and `nixpkgs.hostPlatform` comes from
`nixosSystem`. `hosts/testvm` uses 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 via `nixos-enter`
after 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_offset` kernel 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 `/.swapvol` subvolume, 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 adding `resume_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 `pkgver` and flake
input. A separate repo keeps the Arch repos untouched, at the cost of one
extra commit per version bump (tag in `flake.nix`, then `nix 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>.source` pointing 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`, `gsettings` and `install`; the argument for Home
Manager was convention, not need. Two of the three cases disappeared
entirely: dconf defaults can be set system-wide via
`programs.dconf.profiles.user.databases`, and stasis reads
`/etc/stasis/stasis.rune` as a documented fallback.
- **How**: Rejected. For files that genuinely have no system-wide fallback,
`systemd.user.tmpfiles` with the `C` directive seeds a copy into `$HOME`
without 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`, `loginctl` and
`moonlock` through absolute `/usr/bin` paths, and moongreet scanned
`/usr/share/{wayland-,x}sessions`. All of it fails on NixOS.
- **Tradeoffs**: `systemd.tmpfiles` symlinks 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 walk
`XDG_DATA_DIRS` with `/usr/share` as 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 outside `PATH`. 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-switcher` and
`moonarch-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-zen` and 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 with `Error::DeviceMissing` and never creates an
output.
- **How**: `virtio-vga-gl` with `gl=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**: `disko` would 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 produces `hardware-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.