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.
553 lines
35 KiB
Markdown
553 lines
35 KiB
Markdown
# 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.
|