Install only the helper scripts that are still called

cpugov, sink-switcher and nightlight were referenced solely by the waybar
config; the Quickshell popouts cover all three. btnote, capsnote, vpn and
waybar-hidpp are unreferenced anywhere.

Adds the two system units for battery threshold restore and camera
shutoff, which had been missed.
This commit is contained in:
2026-08-07 17:12:53 +02:00
parent 777a975f1b
commit 2f3ac2557a
9 changed files with 229 additions and 154 deletions
+80 -53
View File
@@ -1,30 +1,39 @@
# moonix
Moonarch on NixOS — packages and NixOS modules that reproduce the Moonarch
desktop from the same sources as the Arch packages.
NixOS configuration for a scrollable-tiling Wayland desktop: niri as the
compositor, Quickshell as the bar and launcher, moongreet as the login greeter,
moonlock as the screen locker, Catppuccin Mocha throughout.
Arch stays the primary target. This repo is the parallel NixOS path: it
consumes `moonarch/defaults/` unchanged and builds the Rust projects with Nix
instead of `makepkg`.
Ships Nix packages for the components that are not in nixpkgs — moonlock,
moongreet, moonset, stasis, sweet-cursors — and NixOS modules that assemble
them into a working session.
The XDG configuration files (niri, Quickshell, foot, GTK, Qt) come from the
`moonarch` repository, which is consumed as a flake input. One set of config
files therefore stays valid on both NixOS and Arch.
## Layout
```
flake.nix Inputs (project repos pinned to tags) and outputs
pkgs/ Build recipes — counterparts to the PKGBUILDs
moonlock.nix
moongreet.nix
moonset.nix
stasis.nix
sweet-cursors.nix
flake.nix Inputs and outputs
pkgs/ Build recipes
moonlock.nix screen locker (GTK4, PAM, fingerprint)
moongreet.nix greetd greeter
moonset.nix session power menu
stasis.nix idle manager
sweet-cursors.nix cursor theme
modules/
desktop.nix /etc deployment, package set, theme
greetd.nix greetd + moongreet
services.nix systemd system and user services
hosts/
moonarch-vm/ QEMU test VM
testvm/ QEMU test VM
thinkpad/ ThinkPad T14 Gen 3 (AMD)
```
`nixosModules.moonarch` is the entry point: it imports all three modules and
hands them the packages and the config source.
## Usage
Build a single package:
@@ -33,30 +42,30 @@ Build a single package:
nix build .#moonlock
```
Build the whole system without activating it:
Build a whole system without activating it:
```bash
nix build .#nixosConfigurations.moonarch-vm.config.system.build.toplevel
nix build .#nixosConfigurations.testvm.config.system.build.toplevel
```
Activate:
```bash
sudo nixos-rebuild switch --flake .#moonarch-vm
sudo nixos-rebuild switch --flake .#testvm
```
## Installing on bare metal
## Installing on a machine
The disk layout is not declared here. Calamares handles partitioning,
encryption and hardware detection, and produces the one file that cannot be
written in advance: `hardware-configuration.nix` with the machine's UUIDs,
LUKS devices and kernel modules.
The disk layout is not declared here. The graphical installer handles
partitioning, encryption and hardware detection, and produces the one file that
cannot be written in advance: `hardware-configuration.nix` with the machine's
UUIDs, LUKS devices and kernel modules.
1. Boot the NixOS ISO and run Calamares. Choose `dkressler` as the user name —
that is what `hosts/moonarch/default.nix` declares. Pick btrfs, snapper
expects it.
2. Reboot into the fresh system. It runs on the configuration Calamares
generated, without Moonarch.
1. Boot the NixOS ISO and run the installer. Choose `dkressler` as the user
name — that is what `hosts/thinkpad/default.nix` declares. Pick btrfs,
snapper expects it.
2. Reboot into the fresh system. It runs on the generated configuration,
without this repo.
3. Enable flakes and install git — neither is available yet:
```bash
@@ -70,10 +79,10 @@ LUKS devices and kernel modules.
```bash
git clone https://gitea.moonarch.de/nevaforget/moonix.git
cp /etc/nixos/hardware-configuration.nix moonix/hosts/moonarch/
cp /etc/nixos/hardware-configuration.nix moonix/hosts/thinkpad/
```
5. Add the import to `hosts/moonarch/default.nix`:
5. Add the import to `hosts/thinkpad/default.nix`:
```nix
imports = [ ./hardware-configuration.nix ];
@@ -82,59 +91,77 @@ LUKS devices and kernel modules.
6. Switch and reboot:
```bash
sudo nixos-rebuild switch --flake ~/moonix#moonarch
sudo nixos-rebuild switch --flake ~/moonix#thinkpad
```
If step 6 breaks the system, pick the previous generation in the boot menu —
the Calamares install stays available as generation 1.
the installer's configuration stays available as generation 1.
Still missing afterwards: a `nixos-hardware` profile for the specific model
(firmware, power management, model quirks). It can be added at any time.
The `nixos-hardware` profile for the T14 Gen 3 (AMD) is already wired up:
firmware, power management and graphics quirks come with it.
`nixosConfigurations.moonarch` therefore evaluates but is not installable on
its own: without `hardware-configuration.nix` it has no filesystems.
Until step 5 is done, `nixosConfigurations.thinkpad` does not evaluate:
```
error: Failed assertions:
- The 'fileSystems' option does not specify your root file system.
```
That is expected — the filesystems live in `hardware-configuration.nix`.
## Version bumps
The project repos are pinned to release tags. After tagging a new version:
The application repos are pinned to release tags. After tagging a new version:
```bash
# adjust the tag in flake.nix, then
nix flake update moonlock
```
## Configuration sources
## Configuration files
`moonarch/defaults/` is the single source of truth for both distributions.
Files are deployed as-is wherever possible. Three exceptions exist, each
because the file contains an FHS path that does not exist under Nix:
Config files are deployed unchanged wherever possible. Three are generated
instead, because they contain a path that does not exist under Nix:
| File | Reason |
|---|---|
| `moongreet.toml` | wallpaper path, generated with the store path |
| `waypaper/config.ini` | wallpaper path, generated and seeded into `$HOME` |
| `moongreet.toml` | wallpaper path, rewritten to the store path |
| `waypaper/config.ini` | wallpaper path, rewritten and seeded into `$HOME` |
| fontconfig defaults | `/etc/fonts/conf.d` is owned by the NixOS module |
Two files from `defaults/xdg/` are deliberately not deployed:
`kanshi/config` is not deployed: the file is empty, kanshi rejects it and
restarts in a loop. Monitor profiles are machine specific and belong in
`~/.config/kanshi/config`; the service starts only when that file exists.
- `walker/` — walker is not part of this setup, the Quickshell launcher
replaced it
- `kanshi/config` — a 0-byte placeholder, unused on Arch as well; kanshi
rejects it and restarts in a loop
Programs that read only from `$HOME` and have no system-wide fallback get a
copy seeded through `systemd.user.tmpfiles` with the `C` directive, which never
overwrites later edits.
## Test VM
`hosts/moonarch-vm` targets a QEMU guest. The launcher script lives outside
this repo at `~/VMs/moonix-vm.sh`.
`hosts/testvm` targets a QEMU guest. The launcher script lives outside this
repo at `~/VMs/moonix-vm.sh`.
It deliberately contains no testing shortcuts — no passwordless sudo, no SSH
password authentication. Set those at runtime when needed, for example through
a drop-in under `/run/systemd/system/`, so they do not end up in version
control.
The guest needs a 3D capable virtio GPU: niri's TTY backend fails every buffer
import with `Error::DeviceMissing` otherwise, and software rendering is not a
workaround. On the current host that path has twice triggered an amdgpu hard
recovery, killing the QEMU process — shut the VM down when not testing.
## Not covered
## Known gaps
- Clipboard history and the `walker -d` menus in
`moonarch-sink-switcher`, `moonarch-vpn` and `moonarch-cpugov` — walker was
dropped without a replacement yet
- Bare metal installation (disk layout, encryption, bootloader)
Four entry points from the niri config and the helper scripts call a launcher
that is not installed here, and therefore do nothing:
| Entry point | Function |
|---|---|
| `Super+C` | clipboard history |
| `moonarch-sink-switcher` | audio sink picker |
| `moonarch-vpn` | VPN picker |
| `moonarch-cpugov` | CPU governor picker |
`cliphist` records the clipboard, so only the picker front-end is missing.
+15 -9
View File
@@ -2,10 +2,11 @@
# ABOUTME: Pulls the Moonarch project repos as sources and builds them with Nix.
{
description = "Moonarch on NixOS packages and modules";
description = "NixOS configuration for a niri and Quickshell Wayland desktop";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nixos-hardware.url = "github:NixOS/nixos-hardware";
# Project sources. These repos are not flakes themselves, they are
@@ -68,6 +69,10 @@
src = inputs.stasis;
version = "1.4.1";
};
moonarch-scripts = pkgs.callPackage ./pkgs/moonarch-scripts.nix {
src = inputs.moonarch;
};
};
in
{
@@ -89,22 +94,23 @@
};
nixosConfigurations = {
# QEMU test VM.
moonarch-vm = nixpkgs.lib.nixosSystem {
# QEMU/KVM test VM.
testvm = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./hosts/moonarch-vm
./hosts/testvm
self.nixosModules.moonarch
];
};
# Bare metal. Machine independent settings only — no disk layout and
# no hardware profile, both depend on a target machine that is not
# decided yet. Not installable as is.
moonarch = nixpkgs.lib.nixosSystem {
# ThinkPad T14 Gen 3 (AMD Ryzen 7 PRO 6850U).
# Needs hosts/thinkpad/hardware-configuration.nix from the installer
# before it evaluates — see README.
thinkpad = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
./hosts/moonarch
inputs.nixos-hardware.nixosModules.lenovo-thinkpad-t14-amd-gen3
./hosts/thinkpad
self.nixosModules.moonarch
];
};
-55
View File
@@ -1,55 +0,0 @@
# ABOUTME: Host configuration for the NixOS test VM running under QEMU/KVM.
# ABOUTME: Holds everything specific to the VM so the bare-metal host stays clean.
{ pkgs, ... }:
{
imports = [ ./hardware-configuration.nix ];
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.kernelParams = [ "quiet" ];
networking.hostName = "moonarch-vm";
networking.networkmanager.enable = true;
time.timeZone = "Europe/Berlin";
i18n.defaultLocale = "de_DE.UTF-8";
console.keyMap = "de";
users.users.kresdo = {
isNormalUser = true;
description = "Dominik Kressler";
extraGroups = [ "networkmanager" "wheel" "video" "input" ];
shell = pkgs.zsh;
};
# Development access from the host via the QEMU port forward on 127.0.0.1:2222.
services.openssh = {
enable = true;
settings.PasswordAuthentication = true;
};
# Throwaway test VM: passwordless sudo so the host can drive rebuilds and
# read logs over SSH. Deliberately confined to this host configuration —
# the bare-metal host must not inherit it.
security.sudo.wheelNeedsPassword = false;
# QEMU guest agent and clipboard/resolution integration.
services.qemuGuest.enable = true;
services.spice-vdagentd.enable = true;
# Render in software. Passing the guest through to the host GPU via
# virtio-gpu 3D triggered an amdgpu hard recovery on the host, killing the
# QEMU process. Slower, but it leaves the host graphics stack alone.
environment.variables.LIBGL_ALWAYS_SOFTWARE = "1";
# greetd starts niri directly, without a login shell, so the variable has to
# be set on the unit as well.
systemd.services.greetd.environment.LIBGL_ALWAYS_SOFTWARE = "1";
nix.settings.experimental-features = [ "nix-command" "flakes" ];
nixpkgs.config.allowUnfree = true;
system.stateVersion = "26.05";
}
+38
View File
@@ -0,0 +1,38 @@
# ABOUTME: Host configuration for the QEMU/KVM test VM.
# ABOUTME: Launcher script lives outside this repo at ~/VMs/moonix-vm.sh.
{ pkgs, ... }:
{
imports = [ ./hardware-configuration.nix ];
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.kernelParams = [ "quiet" ];
networking.hostName = "testvm";
networking.networkmanager.enable = true;
time.timeZone = "Europe/Berlin";
i18n.defaultLocale = "de_DE.UTF-8";
console.keyMap = "de";
users.users.kresdo = {
isNormalUser = true;
description = "Dominik Kressler";
extraGroups = [ "networkmanager" "wheel" "video" "input" ];
shell = pkgs.zsh;
};
# Development access from the host via the QEMU port forward on 127.0.0.1:2222.
services.openssh.enable = true;
# QEMU guest agent and clipboard/resolution integration.
services.qemuGuest.enable = true;
services.spice-vdagentd.enable = true;
nix.settings.experimental-features = [ "nix-command" "flakes" ];
nixpkgs.config.allowUnfree = true;
system.stateVersion = "26.05";
}
@@ -1,16 +1,15 @@
# ABOUTME: Host configuration for the bare metal machine.
# ABOUTME: Machine independent settings only — no disk layout, no hardware profile.
# ABOUTME: Host configuration for the ThinkPad T14 Gen 3 (AMD).
# ABOUTME: hardware-configuration.nix is added during installation, see README.
{ pkgs, ... }:
{
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.kernelPackages = pkgs.linuxPackages_zen;
boot.kernelParams = [ "quiet" ];
networking.hostName = "moonarch";
networking.hostName = "thinkpad";
networking.networkmanager.enable = true;
time.timeZone = "Europe/Berlin";
+5 -30
View File
@@ -1,5 +1,5 @@
# ABOUTME: Deploys the Moonarch XDG configs and desktop package set.
# ABOUTME: Counterpart to the package() function in moonarch-git/PKGBUILD.
# ABOUTME: Deploys the XDG configs from the moonarch repo and the package set.
# ABOUTME: Niri, Quickshell, terminal, theming, portals.
{ pkgs, lib, moonarchSrc, moonarchPkgs, ... }:
@@ -7,8 +7,6 @@ let
xdg = "${moonarchSrc}/defaults/xdg";
etc = "${moonarchSrc}/defaults/etc";
# The moonarch-* helper scripts from defaults/bin, installed as a single
# package. Counterpart to `install -Dm755 defaults/bin/moonarch-* -t /usr/bin/`.
# waypaper calls `swww`/`swww-daemon`, but the maintained successor ships as
# `awww`. Same compatibility symlinks the Arch package creates.
swww-compat = pkgs.runCommand "swww-compat" { } ''
@@ -25,28 +23,6 @@ let
substitute ${xdg}/waypaper/config.ini "$out" \
--replace-fail /usr/share/moonarch/wallpaper.jpg ${wallpaper}
'';
moonarch-scripts = pkgs.stdenv.mkDerivation {
pname = "moonarch-scripts";
version = "0-unstable";
src = moonarchSrc;
dontBuild = true;
installPhase = ''
runHook preInstall
# moonarch-waybar* are excluded: waybar is not part of this setup, the
# Quickshell bar replaced it.
for f in defaults/bin/moonarch-*; do
case "$(basename "$f")" in
moonarch-waybar*) continue ;;
esac
install -Dm755 "$f" -t "$out/bin/"
done
runHook postInstall
'';
meta.description = "Moonarch helper scripts for Waybar, power and devices";
};
in
{
# --- XDG configs -> /etc/xdg/ ---
@@ -54,8 +30,7 @@ in
# Deployed verbatim from the moonarch repo; defaults/ stays the single
# source of truth for both distros.
#
# Deliberately excluded: defaults/xdg/walker/ — walker is not part of this
# setup, the Quickshell launcher replaced it.
# defaults/xdg/walker/ is not deployed: the Quickshell launcher covers it.
environment.etc = {
# niri reads /etc/niri, not /etc/xdg/niri. Both are provided: the former
# is what niri actually loads, the latter keeps the layout consistent with
@@ -172,12 +147,12 @@ in
];
environment.systemPackages = with pkgs; [
# Moonarch's own programs
# Own programs
moonarchPkgs.moonlock
moonarchPkgs.moonset
moonarchPkgs.sweet-cursors
moonarchPkgs.stasis
moonarch-scripts
moonarchPkgs.moonarch-scripts
# Compositor extras
xwayland-satellite
+49 -3
View File
@@ -1,5 +1,5 @@
# ABOUTME: systemd user and system services for the Moonarch session.
# ABOUTME: Rebuilt from defaults/etc/systemd/ because the units use FHS paths.
# ABOUTME: systemd system and user services for the desktop session.
# ABOUTME: Rebuilt from moonarch/defaults/etc/systemd/, whose units use FHS paths.
{ pkgs, lib, moonarchPkgs, ... }:
@@ -45,6 +45,52 @@
};
};
# --- System services from defaults/etc/systemd/system/ ---
#
# Rebuilt rather than copied, same reason as the user units: the originals
# reference /usr/bin.
systemd.services.moonarch-batsaver = {
description = "Restore battery conservation mode threshold";
after = [ "sysinit.target" ];
wantedBy = [ "multi-user.target" ];
unitConfig.ConditionPathExists = [
"/sys/class/power_supply/BAT0/charge_control_end_threshold"
"/var/lib/moonarch/batsaver-threshold"
];
serviceConfig = {
Type = "oneshot";
ExecStart = "${moonarchPkgs.moonarch-scripts}/bin/moonarch-batsaver-restore";
NoNewPrivileges = true;
ProtectHome = true;
PrivateTmp = true;
ProtectKernelModules = true;
ProtectControlGroups = true;
RestrictNamespaces = true;
RestrictRealtime = true;
LockPersonality = true;
};
};
systemd.services.moonarch-camera = {
description = "Switch off all cameras on boot";
after = [ "sysinit.target" ];
wantedBy = [ "multi-user.target" ];
unitConfig.ConditionPathExistsGlob = "/sys/class/video4linux/video*";
serviceConfig = {
Type = "oneshot";
ExecStart = "${moonarchPkgs.moonarch-scripts}/bin/moonarch-camera-boot";
NoNewPrivileges = true;
ProtectHome = true;
PrivateTmp = true;
ProtectKernelModules = true;
ProtectControlGroups = true;
RestrictNamespaces = true;
RestrictRealtime = true;
LockPersonality = true;
};
};
# Upstream ships a unit in the Arch package but not in the tarball, so it is
# declared here. Mirrors /usr/lib/systemd/user/stasis.service.
systemd.user.services.stasis = {
@@ -89,7 +135,7 @@
# Night light stays disabled by default: it is a user toggle. Enabling it
# here would create a system-scope symlink that overrides any
# `systemctl --user disable` — same reasoning as in moonarch-git/PKGBUILD.
# `systemctl --user disable`.
systemd.user.services.wlsunset = {
description = "Wlsunset night light (blue light filter)";
partOf = [ "graphical-session.target" ];
+39
View File
@@ -0,0 +1,39 @@
# ABOUTME: Helper scripts for battery conservation and camera control.
# ABOUTME: Installs the subset of moonarch/defaults/bin that is still called.
{ lib, stdenvNoCC, src }:
let
# Only the scripts something still calls. The rest of defaults/bin is either
# referenced solely by the waybar config, which this setup does not use
# (cpugov, sink-switcher, nightlight — all three covered by Quickshell
# popouts), or unreferenced anywhere (btnote, capsnote, vpn, waybar-hidpp).
scripts = [
"moonarch-batsaver-apply" # called by -toggle
"moonarch-batsaver-restore" # called by moonarch-batsaver.service
"moonarch-batsaver-toggle" # called by BatteryPopout.qml
"moonarch-camera-apply" # called by -toggle
"moonarch-camera-boot" # called by moonarch-camera.service
"moonarch-camera-list" # called by CameraService.qml
"moonarch-camera-toggle" # called by CameraService.qml
];
in
stdenvNoCC.mkDerivation {
pname = "moonarch-scripts";
version = "0-unstable";
inherit src;
dontBuild = true;
dontConfigure = true;
installPhase = ''
runHook preInstall
install -Dm755 ${lib.concatMapStringsSep " " (s: "defaults/bin/${s}") scripts} -t "$out/bin/"
runHook postInstall
'';
meta = {
description = "Moonarch helper scripts for battery and camera control";
platforms = lib.platforms.linux;
};
}