Steam is started by the user manager and only inherits XCURSOR_PATH, so Xcursor falls back to the missing ~/.icons/default and draws the core theme. The name matches modules/greetd.nix.
424 lines
17 KiB
Nix
424 lines
17 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`. The symlinks keep the shared config working unchanged.
|
|
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. The file is a
|
|
# system config rather than an XDG one, which is why moonarch keeps it under
|
|
# 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}
|
|
'';
|
|
|
|
# niri prefers ~/.config/niri/config.kdl over /etc/niri/config.kdl and does not
|
|
# merge the two, so a home config would otherwise replace the whole deployed
|
|
# one. This seed pulls the system config back in through `include`, which niri
|
|
# 26.04 really does load (verified by pointing an include at a broken file and
|
|
# watching `niri validate` fail on it).
|
|
#
|
|
# It exists for nwg-displays, which writes the output layout to
|
|
# ~/.config/niri/monitor.kdl and adds an include for it here. Without a
|
|
# config.kdl in the home directory nwg-displays refuses to start at all
|
|
# ("Couldn't find niri config directory", exit 0, no window).
|
|
niriUserConfig = pkgs.writeText "niri-config.kdl" ''
|
|
include "/etc/niri/config.kdl"
|
|
'';
|
|
|
|
# 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, which points at 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"
|
|
'';
|
|
|
|
# The theme deployed Kvantum/kvantum.kvconfig selects. Of the four places
|
|
# Kvantum searches (style/Kvantum.cpp), three are under $HOME and the fourth
|
|
# is a DATADIR compiled into the style plugin — which under Nix is the
|
|
# plugin's own store path. A separate theme package is therefore unreachable
|
|
# system-wide, and the directory is seeded into the home directory below.
|
|
kvantumTheme = pkgs.catppuccin-kvantum.override {
|
|
accent = "lavender";
|
|
variant = "mocha";
|
|
};
|
|
|
|
# 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: moonarch's own copy of this
|
|
# file sources the shared zshrc, which here would run it 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
|
|
# moonarch's tree 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";
|
|
"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 moonarch's tree; waypaper itself only reads
|
|
# the copy in the home directory, seeded below.
|
|
"xdg/waypaper/config.ini".source = waypaperConfig;
|
|
|
|
# No stasis entry: /etc/stasis/ is only its fallback for a missing user
|
|
# config, and the seed below means there never is one.
|
|
|
|
# Configs that live outside defaults/xdg/ in moonarch as well.
|
|
"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. The system path is used directly, so nothing has to be placed
|
|
# in the home directory to bridge there.
|
|
|
|
# config.kdl spawns the polkit agent by its FHS path
|
|
# (/usr/lib/polkit-gnome/...). Provide that path rather than diverging the
|
|
# config, which moonarch owns.
|
|
systemd.tmpfiles.rules = [
|
|
"d /usr/lib 0755 root root -"
|
|
"L+ /usr/lib/polkit-gnome - - - - ${pkgs.polkit_gnome}/libexec"
|
|
];
|
|
|
|
# --- Shell ---
|
|
programs.zsh.enable = true;
|
|
|
|
# moonarch's shared zshrc, which it 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 lookups are
|
|
# guarded and silently find nothing here. These modules source the same
|
|
# plugins from the store instead, with the strategy the zshrc sets.
|
|
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";
|
|
};
|
|
}];
|
|
};
|
|
|
|
# Qt applications are wrapped, so each one looks for plugins in its own
|
|
# closure and never finds a style installed alongside it — which is why VLC
|
|
# kept coming up in the stock Qt style. Enabling this puts the profile's Qt5
|
|
# and Qt6 plugin directories on QT_PLUGIN_PATH, which the wrappers append to.
|
|
# platformTheme and style stay null: it would add packages that are already
|
|
# listed below, and niri's environment block sets both variables already.
|
|
qt.enable = true;
|
|
|
|
# --- 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.moonarch-scripts
|
|
|
|
# Browser. moonarch 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
|
|
|
|
# Bar, notifications, clipboard.
|
|
# Neither waybar nor swaync are installed: the Quickshell bar owns both
|
|
# roles. The "kept as a reserve" notes in moonarch's 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
|
|
viewnior
|
|
|
|
# File manager stack. thunar and tumbler come from their modules above;
|
|
# ffmpegthumbnailer is what tumbler uses for video thumbnails.
|
|
gvfs
|
|
udisks2
|
|
ntfs3g
|
|
ffmpegthumbnailer
|
|
# Backend for thunar-archive-plugin above; without it its menu entries do
|
|
# nothing.
|
|
xarchiver
|
|
zip
|
|
unzip
|
|
|
|
# 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
|
|
nwg-displays
|
|
wl-color-picker
|
|
|
|
stasis
|
|
claude-code
|
|
];
|
|
|
|
security.pam.services.swaylock = {};
|
|
|
|
# Polkit agent for privilege prompts inside the session.
|
|
security.polkit.enable = true;
|
|
|
|
# nixpkgs.config.allowUnfree covers the system build only. `nix profile` and
|
|
# friends evaluate a flake purely, so they see neither that setting nor
|
|
# ~/.config/nixpkgs/config.nix, and refuse every unfree package. The hosts
|
|
# already declare unfree as acceptable; this makes the imperative commands
|
|
# agree instead of demanding NIXPKGS_ALLOW_UNFREE=1 in front of each one.
|
|
# Set by PAM at session start, so it takes a re-login, not just a rebuild.
|
|
environment.sessionVariables.NIXPKGS_ALLOW_UNFREE = "1";
|
|
|
|
# niri sets both in its own environment block, but that only reaches what niri
|
|
# itself spawns. Anything under the systemd user manager — quickshell, and so
|
|
# everything started from its launcher — never sees them, and Qt applications
|
|
# come up unstyled. Set here they travel through /etc/profile into
|
|
# niri-session's import-environment, and from there into the user manager.
|
|
environment.sessionVariables.QT_QPA_PLATFORMTHEME = "qt6ct";
|
|
environment.sessionVariables.QT_STYLE_OVERRIDE = "kvantum";
|
|
|
|
# Same gap, cursors: niri's cursor block reaches xwayland-satellite, so X11
|
|
# clients have a themed pointer available, but Steam is started by the user
|
|
# manager and arrives with XCURSOR_PATH alone. Xcursor then falls back to
|
|
# ~/.icons/default, which does not exist here, and draws the core theme.
|
|
# The name matches modules/greetd.nix, so greeter and session agree.
|
|
environment.sessionVariables.XCURSOR_THEME = "Sweet-cursors";
|
|
environment.sessionVariables.XCURSOR_SIZE = "24";
|
|
|
|
# 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 moonarch's post-install.sh does imperatively.
|
|
systemd.user.tmpfiles.rules = [
|
|
"C %h/.config/Kvantum/catppuccin-mocha-lavender 0755 - - - ${kvantumTheme}/share/Kvantum/catppuccin-mocha-lavender"
|
|
"C %h/.config/waypaper/config.ini 0644 - - - ${waypaperConfig}"
|
|
# stasis prefers the user config over /etc and generates its own if none
|
|
# exists, so the moonarch config has to be in place before it starts.
|
|
"C %h/.config/stasis/stasis.rune 0644 - - - ${xdg}/stasis/stasis.rune"
|
|
"C %h/.config/swaylock/config 0644 - - - ${swaylockConfig}"
|
|
# nwg-displays needs this file to exist before it will start, and niri would
|
|
# otherwise ignore the monitor.kdl it writes next to it.
|
|
"C %h/.config/niri/config.kdl 0644 - - - ${niriUserConfig}"
|
|
"C %h/.zshrc 0644 - - - ${userZshrc}"
|
|
];
|
|
}
|