Files
nevaforget 4489d42702 docs(desktop): record why pactl is not installed
Steam prints "pactl: command not found" and it means nothing: it probes
wpctl and pactl both and uses whichever is on PATH, which wireplumber
provides. Closed as expected behaviour in nixpkgs#525793.
2026-08-20 18:43:31 +02:00

360 lines
14 KiB
Nix

# ABOUTME: Deploys the XDG configs from the moonarch repo and the package set.
# ABOUTME: Niri, Quickshell, terminal, theming, portals.
{ pkgs, lib, moonarchSrc, moonarchPkgs, moonarchThemes, ... }:
let
xdg = "${moonarchSrc}/defaults/xdg";
etc = "${moonarchSrc}/defaults/etc";
# 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" { } ''
mkdir -p "$out/bin"
ln -s ${pkgs.awww}/bin/awww "$out/bin/swww"
ln -s ${pkgs.awww}/bin/awww-daemon "$out/bin/swww-daemon"
'';
wallpaper = "${moonarchSrc}/defaults/backgrounds/wallpaper.jpg";
wallpaperBlur = "${moonarchSrc}/defaults/backgrounds/wallpaper-blur.jpg";
# waypaper's config points at /usr/share/moonarch/wallpaper.jpg, which does
# not exist here. Generated with the store path instead of copied verbatim.
waypaperConfig = pkgs.runCommand "waypaper-config.ini" { } ''
substitute ${xdg}/waypaper/config.ini "$out" \
--replace-fail /usr/share/moonarch/wallpaper.jpg ${wallpaper}
'';
# Same treatment for swaylock's wallpaper path. swaylock searches
# ~/.swaylock/config, $XDG_CONFIG_HOME/swaylock/config and
# SYSCONFDIR/swaylock/config — and its sysconfdir is inside the store here,
# so the home copy seeded below is the only one it ever reads. On Arch the
# same file ships to /etc/swaylock/config, which is why it lives in
# defaults/etc/ rather than defaults/xdg/.
swaylockConfig = pkgs.runCommand "swaylock-config" { } ''
substitute ${etc}/swaylock/config "$out" \
--replace-fail /usr/share/moonarch/wallpaper-blur.jpg ${wallpaperBlur}
'';
# PipeWire searches $PIPEWIRE_CONFIG_DIR, then $XDG_CONFIG_HOME/pipewire, then
# its compiled-in /etc/pipewire — never /etc/xdg (verified in PipeWire's
# src/pipewire/conf.c). The shared file therefore cannot be deployed the way
# the other XDG configs are; it goes through the module's configPackages,
# which is what its own option documentation shows for a conf.d file.
#
# The file names the plugin as `librnnoise_ladspa`, resolved through
# LADSPA_PATH — /usr/lib/ladspa on Arch, nothing here. Substituted to the
# store path, like the wallpaper paths above. LADSPA has no counterpart to
# services.pipewire.extraLv2Packages, so the path belongs in the file.
denoisingConfig = pkgs.runCommand "moonarch-pipewire-denoising" { } ''
mkdir -p "$out/share/pipewire/pipewire.conf.d"
substitute ${xdg}/pipewire/pipewire.conf.d/99-input-denoising.conf \
"$out/share/pipewire/pipewire.conf.d/99-input-denoising.conf" \
--replace-fail "plugin = librnnoise_ladspa" \
"plugin = ${pkgs.rnnoise-plugin}/lib/ladspa/librnnoise_ladspa.so"
'';
# zsh/newuser runs its setup wizard on every interactive login for as long as
# the user has none of .zshenv, .zprofile, .zshrc or .zlogin — the whole
# configuration living in /etc/zshrc does not count. Seeded below to stop
# that, and deliberately without configuration: on Arch this file carries
# `source /etc/zsh/zshrc.moonarch`, which here would source the shared zshrc
# a second time, on top of the one /etc/zshrc already did.
userZshrc = pkgs.writeText "zshrc" ''
# The moonarch zsh configuration is system-wide, in /etc/zshrc.
#
# This file keeps zsh from running zsh-newuser-install and is the place for
# your own additions: zsh reads it after /etc/zshrc, so it wins over it.
'';
in
{
# --- XDG configs -> /etc/xdg/ ---
#
# Deployed verbatim from the moonarch repo; defaults/ stays the single
# source of truth for both distros.
#
# 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
# the Arch package and with any user config that includes it.
"niri/config.kdl".source = "${xdg}/niri/config.kdl";
"xdg/niri/config.kdl".source = "${xdg}/niri/config.kdl";
"xdg/quickshell/moonarch".source = "${xdg}/quickshell/moonarch";
"xdg/foot/foot.ini".source = "${xdg}/foot/foot.ini";
"xdg/fastfetch/config.jsonc".source = "${xdg}/fastfetch/config.jsonc";
# defaults/xdg/kanshi/config is an empty placeholder — on Arch too. Not
# deployed: monitor profiles are machine specific and belong in
# ~/.config/kanshi/config. An empty file parses fine; a missing one is what
# kanshi reports as "failed to parse config file", which is why the unit in
# modules/services.nix waits for the home config to exist.
"xdg/Kvantum/kvantum.kvconfig".source = "${xdg}/Kvantum/kvantum.kvconfig";
"xdg/qt6ct/qt6ct.conf".source = "${xdg}/qt6ct/qt6ct.conf";
"xdg/gtk-3.0/settings.ini".source = "${xdg}/gtk-3.0/settings.ini";
"xdg/gtk-4.0/settings.ini".source = "${xdg}/gtk-4.0/settings.ini";
# No xdg/pipewire/ entry: PipeWire does not read /etc/xdg, so the file was
# deployed and inert. It goes through services.pipewire.configPackages
# below instead.
# Kept for layout parity with the Arch package; waypaper itself only reads
# the copy in the home directory, seeded below.
"xdg/waypaper/config.ini".source = waypaperConfig;
# stasis reads /etc/stasis/ as its system-wide fallback, never /etc/xdg/.
"stasis/stasis.rune".source = "${xdg}/stasis/stasis.rune";
# Configs outside /etc/xdg/ in the Arch package as well.
"mpv/mpv.conf".source = "${etc}/mpv/mpv.conf";
"xdg-desktop-portal/niri-portals.conf".source =
"${etc}/xdg-desktop-portal/niri-portals.conf";
};
# Generic-family defaults. Same content as
# defaults/etc/fonts/conf.d/65-moonarch-fonts.conf, but routed through the
# fontconfig module: /etc/fonts/conf.d/ is owned by NixOS and rejects
# environment.etc entries.
# localConf is written to /etc/fonts/local.conf verbatim, so it has to be a
# complete XML document — not just the inner elements.
fonts.fontconfig.localConf = ''
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
<fontconfig>
<description>Moonarch generic-family defaults</description>
<!-- binding="strong" required: the default weak <prefer> ranks behind
stock generic fallbacks, so it would not take effect. -->
<alias binding="strong">
<family>sans-serif</family>
<prefer>
<family>UbuntuSans Nerd Font</family>
</prefer>
</alias>
<alias binding="strong">
<family>monospace</family>
<prefer>
<family>UbuntuSansMono Nerd Font</family>
</prefer>
</alias>
</fontconfig>
'';
# --- Compositor ---
programs.niri.enable = true;
# niri searches $XDG_CONFIG_HOME/niri/config.kdl, then /etc/niri/config.kdl —
# not /etc/xdg. On Arch a one-line ~/.config/niri/config.kdl bridges there
# via `include`; here the system path is used directly, so nothing has to be
# placed in the home directory.
# config.kdl spawns the polkit agent by its FHS path
# (/usr/lib/polkit-gnome/...). Provide that path rather than diverging the
# config, which is shared with Arch.
systemd.tmpfiles.rules = [
"d /usr/lib 0755 root root -"
"L+ /usr/lib/polkit-gnome - - - - ${pkgs.polkit_gnome}/libexec"
];
# --- Shell ---
programs.zsh.enable = true;
# Same file the Arch package installs as /etc/zsh/zshrc.moonarch. zsh reads
# /etc/zshrc before ~/.zshrc, so a user file still wins over it.
programs.zsh.interactiveShellInit = ''
source ${moonarchSrc}/defaults/shell/zshrc
'';
# /etc/zshrc places promptInit after interactiveShellInit, and its default
# ends in `prompt suse`, which assigns PS1 — the same parameter as PROMPT.
# Left at the default it overwrites the prompt the sourced zshrc just set.
# The zshrc owns the prompt, so nothing else has to run here.
programs.zsh.promptInit = "";
# The zshrc sources both plugins from /usr/share/zsh/plugins, the path the
# Arch packages use; the lookups are guarded and silently find nothing here.
# These modules source the same plugins from the store instead. The strategy
# is the one the zshrc sets on Arch.
programs.zsh.syntaxHighlighting.enable = true;
programs.zsh.autosuggestions = {
enable = true;
strategy = [ "history" "completion" ];
};
# environment.shellAliases carries ls, ll and l as defaults, and /etc/zshrc
# renders them after interactiveShellInit, so they shadow the eza aliases of
# the sourced zshrc. null drops an alias. Scoped to zsh: bash keeps the
# coreutils ls, which is the only thing it has.
programs.zsh.shellAliases = {
ls = null;
ll = null;
l = null;
};
# --- File manager ---
#
# Counterpart to thunar, thunar-volman, thunar-archive-plugin, tumbler and
# ffmpegthumbnailer in packages/official.txt.
programs.thunar = {
enable = true;
plugins = with pkgs; [ thunar-volman thunar-archive-plugin ];
};
services.tumbler.enable = true;
# --- Theme, systemwide instead of the gsettings calls in post-install.sh ---
programs.dconf = {
enable = true;
profiles.user.databases = [{
settings."org/gnome/desktop/interface" = {
gtk-theme = "Colloid-Grey-Dark-Catppuccin";
icon-theme = "Colloid-Grey-Catppuccin-Dark";
color-scheme = "prefer-dark";
font-name = "UbuntuSans Nerd Font 11";
};
}];
};
# --- Portals ---
xdg.portal = {
enable = true;
extraPortals = with pkgs; [ xdg-desktop-portal-gnome xdg-desktop-portal-gtk ];
};
fonts.packages = with pkgs; [
nerd-fonts.ubuntu-sans
openmoji-color
];
# --- Audio ---
#
# RNNoise input denoising, from the shared config. The filter chain appears as
# a separate "Noise Canceling source" input; it does not replace the raw
# microphone, so applications have to select it.
services.pipewire.configPackages = [ denoisingConfig ];
# --- Printing ---
#
# Driverless only, and no printer is declared here. Asked over IPP, the
# Samsung M267x 287x answers with image/urf and application/PCLm among its
# document formats, so CUPS builds the queue from the capabilities the device
# reports about itself — no PPD, no driver package, nothing about anyone's
# network in this repository. Any AirPrint-capable printer works the same way,
# including ones this machine has never seen.
#
# Avahi is the discovery half: without it the printer is not found and its
# .local name does not resolve. openFirewall defaults to true and opens
# UDP 5353 for mDNS.
services.printing.enable = true;
services.avahi = {
enable = true;
nssmdns4 = true;
openFirewall = true;
};
# --- VPN ---
#
# networking.networkmanager.plugins defaults to an empty list, and without the
# OpenVPN plugin NetworkManager cannot create such a connection at all —
# neither nmcli, nmtui nor nm-applet offer the type. Counterpart to
# networkmanager-openvpn in packages/official.txt. nm-applet, installed below,
# is what hands the keyring password to the connection.
networking.networkmanager.plugins = [ pkgs.networkmanager-openvpn ];
environment.systemPackages = with pkgs; [
# Own programs
moonarchPkgs.sweet-cursors
moonarchPkgs.stasis
moonarchPkgs.moonarch-scripts
# Browser. Arch runs waterfox-bin, which nixpkgs does not carry; see
# DECISIONS.md for why this is not packaged here.
librewolf
# Session lock. The power menu is a Quickshell popout, not a package.
swaylock
# Compositor extras
xwayland-satellite
kanshi
# Bar, notifications, clipboard.
# Neither waybar nor swaync are installed: the Quickshell bar owns both
# roles. The "kept as a reserve" notes in the Arch config date from the
# migration and do not apply to a fresh setup.
quickshell
libnotify
cliphist
wl-clipboard
# Display and wallpaper
wlsunset
nwg-look
brightnessctl
awww
swww-compat
waypaper
# Terminal
foot
# Audio. No pulseaudio for pactl: Steam probes wpctl and pactl both and
# takes whichever is on PATH (nixpkgs#525793), and wireplumber puts wpctl
# there. Its "pactl: command not found" is noise from Steam, not a gap.
pavucontrol
alsa-utils
# Network and bluetooth. openvpn is the binary the NetworkManager plugin
# below drives; it also stands on its own for a config file.
networkmanagerapplet
blueman
openvpn
# Media
vlc
# File manager stack. thunar and tumbler come from their modules above;
# ffmpegthumbnailer is what tumbler uses for video thumbnails.
gvfs
udisks2
ntfs3g
ffmpegthumbnailer
# Theming. Same derivations the greeter uses, see flake.nix.
moonarchThemes.gtk
moonarchThemes.icons
libsForQt5.qtstyleplugin-kvantum
qt6Packages.qtstyleplugin-kvantum
qt6Packages.qt6ct
qt6.qt5compat
# CLI tools referenced by zshrc and the helper scripts
bat
btop
eza
fastfetch
fd
fzf
jq
lazygit
ripgrep
neovim
git
# System
fwupd
upower
polkit_gnome
wdisplays
wl-color-picker
];
# Polkit agent for privilege prompts inside the session.
security.polkit.enable = true;
# Seed configs for programs that read only from the home directory and have
# no system-wide fallback. 'C' copies once and never overwrites, so user
# edits survive. This is what post-install.sh does imperatively on Arch.
systemd.user.tmpfiles.rules = [
"C %h/.config/waypaper/config.ini 0644 - - - ${waypaperConfig}"
"C %h/.config/swaylock/config 0644 - - - ${swaylockConfig}"
"C %h/.zshrc 0644 - - - ${userZshrc}"
];
}