Files
moonarch/DECISIONS.md
T
nevaforget dc47d1a6ec fix(nightlight): default OFF, drop wlsunset from auto-enable list
Coordinated fix with moonarch-pkgbuilds: post-install.sh enabled
wlsunset by default, while the PKGBUILD shipped a global-scope
WantedBy symlink. Together that made the toggle's user-scope disable
a no-op — filter persisted across reboots regardless of user intent.

Removing wlsunset from USER_SERVICES makes "off" the install default;
the toggle now works in user scope only, where disable can take effect.
2026-05-04 14:15:58 +02:00

30 KiB
Raw Blame History

Decisions

2026-05-04 Nightlight default OFF, no global enablement

  • Who: Dominik, ClaudeCode
  • Why: Filter survived reboots even after the user toggled it off. Root cause: moonarch-pkgbuilds/moonarch-git/PKGBUILD looped over every user service in defaults/etc/systemd/user/*.service and dropped a WantedBy symlink into /etc/systemd/user/graphical-session.target.wants/. That path is global scope. moonarch-nightlight runs systemctl --user disable wlsunset, which can only remove user-scope symlinks under ~/.config/. Systemd's own warning during disable spelled it out: "The following unit files have been enabled in global scope. This means they will still be started automatically after a successful disablement in user scope." Verified empirically — is-enabled stayed enabled, root symlink untouched. Additionally, scripts/post-install.sh enabled wlsunset by default in its USER_SERVICES array, so even without the global symlink the filter would default ON.
  • Tradeoffs: Three options weighed. (1) Default OFF + user-scope toggle — minimal change, fresh installs start without filter, toggle creates ~/.config/.../wants/ symlink that user-disable can actually remove. (2) Default ON + user-scope toggle — same plumbing, post-install enables in user scope; filter on by default but disable persists. (3) Status-file gate inside the unit — service stays enabled, ExecStartPre checks a file and exits when off. Picked (1): no behavioral default imposed on fresh installs, no extra plumbing, the toggle stays the single source of truth. Could have moved enablement to a per-user systemctl --user --global enable from the .install hook, but that fights the "this is a UI toggle" framing.
  • How: moonarch-pkgbuilds/moonarch-git/PKGBUILD — symlink loop now skips a skip_enable list (currently wlsunset.service); skipped services are still installed under /etc/systemd/user/ but not wanted by graphical-session.target at the global level. moonarch-pkgbuilds/moonarch-git/moonarch.installpre_upgrade() deletes any pre-existing /etc/systemd/user/graphical-session.target.wants/wlsunset.service to clean up systems that received the old packaging. moonarch/scripts/post-install.shwlsunset removed from USER_SERVICES; comment explains why. moonarch/CLAUDE.md — Nightlight section reflects "Default OFF" and the global-scope-symlink hazard.

2026-05-04 Battery threshold permissions: udev rule → pkexec helper

  • Who: Dominik, ClaudeCode
  • Why: The wheel-write-via-udev approach for /sys/class/power_supply/BAT0/charge_control_end_threshold had been broken since 2026-04-08 (commit ac2b210, "audit remediation Q-W3"). That commit added ACTION=="add" to 90-moonarch-battery.rules to "avoid firing on every battery event" — but that filter is precisely what the rule needs not to have. On Lenovo, the threshold attribute does not exist yet at the add event (the driver creates it slightly later); the rule fires, chmod fails silently because 2>/dev/null swallows the error, and permissions are never set. The unfiltered original rule worked by accident: add failed silently as well, but a subsequent change event on the same device caught the now-existing attribute and set permissions. After the audit commit, change events stopped re-firing the rule and the toggle was permanently broken — moonarch-batsaver-toggle returned Permission denied. Verified via journalctl + manual chmod: rule fires for hidpp_battery_0 (visible exit-1 errors), no trace for BAT0; manual chmod g+w on BAT0's threshold succeeds (sysfs accepts the change), so the permission model itself works — only the rule path failed.
  • Tradeoffs: Three approaches considered. (A) Restore the original unfiltered rule — fixes the symptom by accident, leaves the failure mode intact (silent fail at add, retry hopefully at change). (B) Switch to tmpfiles.d — Arch Wiki explicitly warns this can run before driver modules load, undefined for sysfs. (C) pkexec helper with polkit-rule — standard pattern (Battery-Health-Charging GNOME extension uses exactly this). Picked C with default Standard-pkexec prompt rather than no-password polkit rule: minor UX cost (password once per pkauth session, ≈5min cache), eliminates the entire sysfs-permission problem class, no privilege-escalation surface from a misvalidated helper. The wheel-can-write-sysfs design was a moonarch-specific deviation from common Linux practice — bringing it in line with the standard root-orientiert helper pattern.
  • How: defaults/bin/moonarch-batsaver-apply (new): privileged helper invoked via pkexec; strictly validates argument (digits only, range 1-100), writes sysfs (idempotent — skips kernel write when value already matches to avoid Lenovo EINVAL on same-value writes), writes state file. defaults/bin/moonarch-batsaver-toggle (rewritten): user-side reads current threshold, picks 80↔100, dispatches pkexec /usr/bin/moonarch-batsaver-apply $NEW, then signals waybar. defaults/etc/udev/rules.d/90-moonarch-battery.rules deleted (and the now-empty defaults/etc/udev/ parent removed). PKGBUILD: udev install line removed. moonarch-doctor: removed the udev-effectiveness check (no longer relevant). moonarch-batsaver.service and moonarch-batsaver-restore (also new in this commit, extracted from the old inline ExecStart for readability) keep root-owned boot-time restore — no permission concerns there. CLAUDE.md Battery-Conservation-Mode section updated to describe the new flow.

2026-05-04 Cleanup: remove invented zsh override layer, harden moondoc

  • Who: Dominik, ClaudeCode
  • Why: Audit revealed two classes of cruft introduced by earlier ClaudeCode sessions without explicit decision or DECISIONS.md entry. (1) An invented user-override mechanism (~/.zshrc.d/*.zsh snippet loop, ~/.zshrc.local fallback) was wired into defaults/shell/zshrc and scripts/post-install.sh. Not a zsh convention, not documented, redundant to the user's own ~/.zshrc. post-install.sh created the ~/.zshrc.d directory unconditionally on every fresh install — leaving an empty directory in every user's home. (2) moonarch-doctor had only existence checks (/etc/zsh/zshrc.moonarch exists?, /usr/share/moonarch/ exists?) which are redundant with the package check, and hardcoded service/script lists that drift silently when moonarch-git's payload changes. The udev rule for charge_control_end_threshold (battery conservation) had no effectiveness check at all — a broken rule would not show up.
  • Tradeoffs: Could have left the invented override layer alone (no active harm) but it muddies defaults/shell/zshrc and produces empty directories on every fresh install. Could have kept the existence checks (cosmetic noise, no harm) but they create false positives — doctor reports "pass" while the actual mechanism may be broken. Kept the 7 hardcoded check_config_match entries for /etc/xdg/foot/, /etc/greetd/, /etc/moongreet/ etc. — the source-to-destination mapping is not 1:1 (foot → /etc/xdg/foot/, greetd → /etc/greetd/, moongreet → /etc/moongreet/), so dynamic discovery would need a manifest. Acceptable hardcoding for now.
  • How: defaults/shell/zshrc~/.zshrc.d/*.zsh source loop and ~/.zshrc.local fallback removed; second ABOUTME line that referenced them removed. scripts/post-install.sh — Zsh-block now writes ~/.zshrc with only source /etc/zsh/zshrc.moonarch (no mkdir, no ~/.zshrc.d reference); stale "rustup default stable" hint and "User overrides in ~/.zshrc.d/" hint removed from next-steps. scripts/lib.sh — dead confirm() (orphaned since transform.sh deletion 2026-04-21) removed. scripts/moonarch-doctor — user-services and helper-scripts lists now derived from pacman -Qql moonarch-git plus an explicit list of post-install-enabled externals (currently stasis); useless existence checks for /etc/zsh/zshrc.moonarch and /usr/share/moonarch/ removed; new udev-effectiveness check for charge_control_end_threshold (group=wheel + group-writable). defaults/bin/moonarch-waybar-cpugov, moonarch-waybar-gpustat — German ABOUTME comments translated to English for consistency.

2026-04-24 Stasis: flip ignore_remote_media to false for browser video

  • Who: Dominik, ClaudeCode
  • Why: Idle was firing on a second machine even while a video was playing in the browser. Original config carried the comment "browser uses D-Bus inhibit" and set ignore_remote_media true, deliberately excluding browser MPRIS on the assumption that Firefox/Chromium would keep the session alive via org.freedesktop.ScreenSaver.Inhibit. Verified against browser behavior: both browsers only raise that inhibit during fullscreen video playback (Firefox also requires dom.screenwakelock.enabled, default off on Linux). Windowed playback — the common case, YouTube in a tab — sends no inhibit, so stasis saw zero inhibitors and zero media players and ran the full idle plan to suspend. Upstream example config lists r"firefox.*" in inhibit_apps for exactly this reason; Moonarch removed it without a working substitute.
  • Tradeoffs: Three options. (A) Put firefox/chromium back into inhibit_apps — works but inhibits whenever the browser process is running, even with zero tabs playing; wrong shape. (B) Tell users to flip dom.screenwakelock.enabled per browser — pushes per-user config, fragile across browsers. (C) Let browser MPRIS count as a media player by setting ignore_remote_media false — stasis already tracks playback state, so it only inhibits during actual playback. Picked C. Cost: any MPRIS source stasis classifies as "remote" (e.g. a Chromecast bridge) now also inhibits; acceptable — that is usually what a user wants anyway, and the inhibit releases the moment playback stops.
  • How: defaults/xdg/stasis/stasis.runeignore_remote_media truefalse, comment rewritten to document the fullscreen-only D-Bus behavior. PKGBUILD deploys to /etc/xdg/stasis/stasis.rune; existing users still need the 2026-04-22 seed mechanism (or a manual merge) to pick it up in ~/.config/stasis/stasis.rune.

2026-04-22 moonarch-doctor housekeeping: drop stale check, add missing services

  • Who: Dominik, ClaudeCode
  • Why: Noticed while running moondoc on a healthy system that it reported Paru [moonarch-pkgbuilds] repo missing from /etc/paru.conf — a false failure. The paru PKGBUILD-repo mechanism was retired on 2026-04-20 in favor of the registry-only flow, and the moonarch-git install hook strips the legacy paru.conf section on upgrade; the doctor script was not updated in lockstep. Audit of the rest of the script surfaced two related gaps: the user-services loop skipped walker.service and nautilus.service, even though the PKGBUILD ships both in /etc/systemd/user/ and enables them via graphical-session.target.wants symlinks. A silently missing walker or nautilus would not show up in diagnostics.
  • How: Removed the [moonarch-pkgbuilds] check from scripts/moonarch-doctor. Added walker and nautilus to the user-service loop. Updated CLAUDE.md user-services listing to match (also filled in the missing wlsunset). The [moonarch] pacman-repo check stays — that is the path that matters now.

2026-04-22 Seed Stasis user config from post-install.sh

  • Who: Dominik, ClaudeCode
  • Why: Moonarch shipped defaults/xdg/stasis/stasis.rune (deployed to /etc/xdg/stasis/stasis.rune by the PKGBUILD) on the assumption that stasis honors the XDG system config hierarchy. It does not. Verified against upstream source (v1.1.0, src/config/mod.rs:30 + src/config/bootstrap.rs): stasis only reads ~/.config/stasis/stasis.rune (primary) or /etc/stasis/stasis.rune (fallback, no xdg/). On every start with no user config, ensure_user_config_exists() writes its own hardcoded default (laptop/desktop template compiled into the binary) to ~/.config/stasis/stasis.rune. Net effect: Moonarch's Idle-Manager tuning (AC/battery plans, moonlock integration, inhibit apps, niri DPMS commands) was never active on fresh installs — users got the upstream defaults with swaylock as locker.
  • Tradeoffs: Three options were considered. (A) Service drop-in with --config /etc/stasis/stasis.rune — cleanest, upgrades propagate via package, no user-home touching; rejected because it takes stasis out of the standard config path users expect when they want to customize, and requires packaging a drop-in unit. (B) Seed once from post-install.sh into ~/.config/stasis/stasis.rune — chosen: user-owned file, immediately editable, no magic. Cost: package updates to the template never reach users who already ran post-install; they have to merge manually. (C) Recurring systemd user service that keeps seeding — ruled out, too clever, user edits would race with it. Stasis has no config hierarchy / merging, so "system default + user override" cannot be modeled at all.
  • How: Added a block to scripts/post-install.sh before the user-services-enable step: if ~/.config/stasis/stasis.rune does not exist and /etc/xdg/stasis/stasis.rune does, install -Dm644 copies the template into the user home. Order is correct — moonarch-git is installed earlier in the script (deploys /etc/xdg/…), and stasis.service is only enabled (not started) later, so the seed is in place before stasis ever runs. The /etc/xdg/stasis/ payload stays as the canonical template source inside the package.

2026-04-21 post-install.sh pulls aur.txt, rust for paru build, rustup out of official

  • Who: Dominik, ClaudeCode
  • Why: Three related gaps uncovered while fixing the paru bootstrap: (1) moonarch-git cannot depend on AUR packages, so every AUR package in aur.txt (walker-bin, elephant-*-bin, awww's theme, waypaper, stasis, …) was silently never installed by post-install.sh — a fresh install would have a working pacman but no launcher, no idle manager, no theming. (2) makepkg -si for paru (AUR source build) needs rust as makedep; neither archinstall nor post-install.sh installed it, so the restored paru bootstrap would have crashed on rust-less systems. (3) rustup sat in official.txt "for something we don't remember" — turned out to be a leftover; rust suffices for the paru build, and rustup is only needed for dev toolchain management which is a per-user concern, not a Moonarch default.
  • Tradeoffs: Installing rust (~350MB) just to bootstrap paru feels heavy. Alternative paru-bin avoids the compile step entirely; rejected because Dominik's documented workflow uses paru (source) and consistency with that matters more than ~30s of build time. rustup can be re-added to official.txt if the dev workflow grows that demand.
  • How: (1) post-install.sh now runs read_packages "$AUR_PACKAGES" | paru -S --needed after paru -S moonarch-git. (2) sudo pacman -S base-devel rust before the paru git-clone. (3) rustup removed from official.txt (remains in PKGBUILD optdepends for discoverability).

2026-04-21 Restore AUR bootstrap for paru in post-install.sh

  • Who: Dominik, ClaudeCode
  • Why: Commit 0726451 (2026-03-29) replaced the working git clone https://aur.archlinux.org/paru.git && makepkg -si bootstrap with sudo pacman -S paru, on the (wrong) assumption that paru had landed in [extra]. Verified against archlinux.org API: paru and paru-bin are AUR-only, not in any official repo. Fresh installs have been silently broken since — the command fails, the installer aborts before the moonarch registry is configured.
  • Tradeoffs: Bootstrap builds paru from source, which needs base-devel (already pulled by archinstall). Alternative paru-bin would skip the compile step; chose paru to match the upstream recommendation the user follows. Alternative "bundle paru in the Moonarch registry" would be internally consistent but adds a dependency on a running Gitea during install.
  • How: Replaced the pacman -S paru line in post-install.sh with the original bootstrap — mktemp -d, git clone, makepkg -si --noconfirm, rm -rf. Wrapped in an EXIT trap so the tempdir gets cleaned up even on failure.

2026-04-21 Drop transform.sh and legacy update.sh shim

  • Who: Dominik, ClaudeCode
  • Why: transform.sh was added 2026-03-29 to onboard users from existing Arch+Wayland systems, but was never actually used — Moonarch is installed fresh via archinstall. Maintaining it meant duplicated paru/repo/key setup, a second entry point with its own backup/pre-flight logic, and a second reason for the /opt/moonarch clone. scripts/update.sh was a deprecation shim from before moonarch-update moved into the moonarch-git package; obsolete now that the package ships moonup/moondoc.
  • Tradeoffs: Existing Arch+Wayland users now have to install paru, add the [moonarch] repo + key manually, then paru -S moonarch-git. That's three commands instead of one script — acceptable since nobody actually uses that path. If the need returns, transform can be resurrected from git history.
  • How: Deleted scripts/transform.sh and scripts/update.sh. lib.sh stays (still sourced by post-install.sh). README "Transform" section removed, project-structure listing trimmed. Fresh-install flow via archinstall + post-install.sh is unchanged.

2026-04-20 Registry-only install: drop paru --pkgbuilds from setup scripts

  • Who: Dominik, ClaudeCode
  • Why: Two parallel paths for finding moonarch packages (Arch registry via [moonarch] in pacman.conf, and paru's PKGBUILD-repo via [moonarch-pkgbuilds] in paru.conf) caused ambiguity during debugging: paru -S moonarch-git was resolving against the registry's stale DB (zombie r99 entry) while the PKGBUILD source would have had r105. Also: the PKGBUILD-repo path triggered a local build on every client, while the registry ships prebuilt binaries. With the registry DB now stable (see moonarch-pkgbuilds/DECISIONS.md, same date), the second mechanism is redundant.
  • Tradeoffs: If the registry is down or broken, clients have no local-build fallback. Acceptable — a broken registry is a server-side bug to fix, not something to mask with a parallel mechanism that complicates diagnostics.
  • How: post-install.sh and transform.sh no longer write Mode = arp or a [moonarch-pkgbuilds] section to /etc/paru.conf, and no longer call paru -Syu --pkgbuilds. They run pacman -Sy + paru -S moonarch-git against the registry. The moonarch-git install hook now strips the legacy paru.conf entries on upgrade (see moonarch-pkgbuilds repo).

2026-04-19 moonup i18n: reuse pacman gettext catalog + inline fallback

  • Who: Dominik, ClaudeCode
  • Why: moonup prompts looked foreign next to pacman/paru output ([y/N] vs [J/n], English strings on a German system). User wanted consistency with the rest of the pacman UX.
  • Tradeoffs: Full .po/.mo build-chain for moonarch was overkill for ~15 strings. gettext-only approach misses most moonarch-specific strings (no matching msgids in pacman catalog). Chose hybrid: TEXTDOMAIN=pacman for strings that match upstream msgids (prompts like Proceed with installation?, Do you want to remove these packages?, [Y/n], Starting full system upgrade...), inline _t() helper for moonarch-specific strings.
  • How: moonarch-update sets TEXTDOMAIN=pacman/TEXTDOMAINDIR=/usr/share/locale. _t "en" "de" picks by ${LANG%%.*} matching de_*. confirm() now uses :: prefix, default Y, accepts y/Y/j/J. No PKGBUILD change — gettext is in base. pacman msgids with trailing \n (e.g. Starting full system upgrade...\n) require ANSI-C quoting $'...\n' to match.

2026-04-08 Battery conservation mode: udev + sysfs + Waybar toggle

  • Who: Dominik, ClaudeCode
  • Why: Laptops with charge_control_end_threshold support benefit from limiting charge to 80% to extend battery lifespan. Needed a user-friendly toggle without requiring sudo.
  • Tradeoffs: udev RUN approach for permissions (group wheel gets write access) vs polkit/pkexec (password prompt on every toggle). Chose udev for seamless UX. State persisted in /var/lib/moonarch/ (system-wide, not per-user) — acceptable since charge threshold is a hardware setting, not a user preference. Fixed 80% threshold instead of configurable — KISS, matches industry standard.
  • How: udev rule grants wheel group write on charge_control_end_threshold. Toggle script writes sysfs + state file. systemd oneshot service restores on boot. Waybar shows ♥ icon when active, hidden when inactive. Click on battery module toggles.

2026-04-07 Walker theme: gtk-inherit → moonarch with fixed colors

  • Who: Dominik, ClaudeCode
  • Why: gtk-inherit theme relied on GTK4 color inheritance which works but doesn't update live when switching GTK themes (Walker service needs restart). Explicit color definitions make the theme self-contained and predictable.
  • Tradeoffs: Colors no longer auto-follow GTK theme changes. Acceptable since moonarch uses a fixed Catppuccin Mocha palette anyway.
  • How: Renamed theme to moonarch, added @define-color with Colloid-Grey-Dark-Catppuccin values, reduced border/shadow weight.

2026-04-07 Migrate archinstall config to v4 format

  • Who: Dominik, ClaudeCode
  • Why: archinstall v4.1 introduced new canonical key names. Old keys (audio_config, bootloader, custom-commands) are soft-deprecated and auto-mapped, but custom-commands (hyphen) vs custom_commands (underscore) was risky.
  • Tradeoffs: Config now requires archinstall v4+. Older ISOs with v2/v3 may not parse the new keys.
  • How: Migrated audio_config into app_config, bootloader into bootloader_config, custom-commands to custom_commands, gfx_driver value updated.

2026-04-07 kanshi config: no ABOUTME, no overwrite on transform

  • Who: Dominik, ClaudeCode
  • Why: ABOUTME comments in kanshi config broke the profile parser in wdisplays-persistent store.c, preventing config saves. Additionally, transform.sh was overwriting user display profiles on every run.
  • Tradeoffs: kanshi default template is now empty (no comments). Users get no guidance in the seed file, but wdisplays-persistent provides the GUI for config management.
  • How: Removed ABOUTME from defaults/xdg/kanshi/config. Added skip logic in transform.sh to preserve existing kanshi user configs.

2026-04-07 Move paru repo config into moonarch.install hook

  • Who: Dominik, ClaudeCode
  • Why: paru PKGBUILD repo config was only set up by post-install.sh and transform.sh. If paru updated and overwrote /etc/paru.conf, or the config was missing on existing systems, moonarch-git couldn't update itself — bootstrap loop where the fix requires the package that delivers the fix.
  • Tradeoffs: Config setup is now in two places: moonarch.install (for updates) and post-install.sh (for first install before moonarch-git exists). Acceptable duplication to break the circular dependency.
  • How: Added paru repo config (Mode=arp + [moonarch-pkgbuilds] section) to moonarch.install post_install/post_upgrade hook. Kept post-install.sh setup for bootstrap. Removed redundant setup from transform.sh.

2026-04-02 Rename paru PKGBUILD repo, move config to /etc/paru.conf

  • Who: Dominik, ClaudeCode
  • Why: paru PKGBUILD repo and pacman package registry both used [moonarch] as section name. When pkgver-bot pushed version bumps, paru tried to resolve PKGBUILD targets (moongreet-git, moonlock-git) against the pacman repo — which only contains moonarch-git — causing "nicht alle benötigten Pakete gefunden" errors.
  • Tradeoffs: Renaming the PKGBUILD repo section means existing installations need a one-time manual fix. Using /etc/paru.conf instead of ~/.config/paru/paru.conf is consistent with moonarch's system-wide config philosophy.
  • How: Renamed PKGBUILD repo section from [moonarch] to [moonarch-pkgbuilds]. Moved paru config (Mode + repo) from user-level ~/.config/paru/paru.conf to system-wide /etc/paru.conf. Updated post-install.sh accordingly.

2026-04-01 Replace dunst with swaync as notification daemon

  • Who: Dominik, ClaudeCode
  • Why: Dunst lacks wp_fractional_scale_v1 support, causing aliased/jagged font rendering on external monitors in mixed-DPI setups (laptop eDP-1 at 2.5x, externals at 1x). Confirmed by testing: removing fractional scaling fixed the issue. swaync uses GTK4 which handles fractional scaling natively.
  • Tradeoffs: swaync is heavier than dunst (GTK4 + libadwaita dependency). Loses dunstctl CLI (replaced by swaync-client). Gains notification center panel with DnD toggle, grouping, MPRIS widget support. Waybar already had swaync-client integration with exec-if guard.
  • How: Replaced dunst with swaync in packages/official.txt and archinstall config. Niri spawn-at-startup updated. Waybar dunstctl widget removed (swaync-client widget already present). New swaync config.json and style.css based on catppuccin/swaync upstream theme with Lavender accent instead of Blue.

2026-03-31 Audit: shell script quoting fixes, PKGBUILD permissions

  • Who: ClaudeCode, Dom
  • Why: Security audit found command injection risk in moonarch-cpugov (unquoted array expansion with pkexec), word-splitting in moonarch-btnote (upower output from Bluetooth devices), and nmcli argument injection in moonarch-vpn. PKGBUILD for moongreet had world-readable cache dir.
  • Tradeoffs: eval in cpugov is safe because COMMANDS values are hardcoded string literals, not user input. Alternative (function dispatch) would be cleaner but over-engineered for 3 fixed entries. moonarch-btnote switched from for-loop to while+read with process substitution to avoid subshell.
  • How: (1) eval "${COMMANDS[$choice]}" in cpugov. (2) while IFS= read -r + process substitution + quoted $DEVICE_DATA in btnote. (3) -- guard before $connection in vpn nmcli calls. (4) install -dm700 for moongreet cache dirs in PKGBUILD. (5) else err logging in post-install.sh when USER_DEFAULTS missing.

2026-03-29 /opt/moonarch stays root-owned, no chown to user

  • Who: Dominik, ClaudeCode
  • Why: Multi-user system — chown to UID 1000 locks out other users from moonarch-update
  • Tradeoffs: sudo required for git operations in update.sh vs. simpler user-owned repo
  • How: Repo stays at /opt/moonarch owned by root:root. update.sh uses sudo git for fetch/pull. All scripts already use sudo for system-level operations, so this is consistent.

2026-03-29 Add transform.sh for existing Arch+Wayland systems

  • Who: Dominik, ClaudeCode
  • Why: Users with existing Arch+Wayland setups should be able to adopt Moonarch without reinstalling
  • Tradeoffs: Hard overwrite of all configs (user + system) vs. selective/merge approach — chose hard overwrite for simplicity and consistency
  • How: New transform.sh with pre-flight summary, backup, DM conflict resolution, and --dry-run flag. Shared helpers extracted to lib.sh.

2026-03-29 Package moonarch as moonarch-git PKGBUILD

  • Who: Dominik, ClaudeCode
  • Why: System artifacts (XDG configs, helper scripts, zsh config, wallpaper) should be managed by pacman for clean deployment, versioning, rollback, and deinstallation
  • Tradeoffs: /etc/xdg/ configs NOT in backup= (moonarch philosophy: system defaults flow through, users override in ~/.config/). /etc/greetd/ and /etc/moongreet/ NOT owned by package (owned by greetd/moongreet-git, overwritten via .install hook). Helper scripts move from /usr/local/bin/ to /usr/bin/ (FHS for package-managed files)
  • How: moonarch-git PKGBUILD in moonarch-pkgbuilds repo. sweet-cursors-git as separate package. moonarch-update simplified (no git-sync, pacman handles file deployment). Installer scripts (post-install.sh, transform.sh) remain for orchestration, will be refactored in a follow-up to delegate file deployment to paru -S moonarch-git

2026-03-30 Replace Rofi with Walker as application launcher

  • Who: Dominik, ClaudeCode
  • Why: Walker is Wayland-native (GTK4 + gtk4-layer-shell), has built-in providers for clipboard, bluetooth, audio (wireplumber), and Niri integration. Reduces custom shell scripts from 8 to 3. Rofi required a Wayland fork (rofi-lbonn-wayland-git) and every applet was a custom bash script.
  • Tradeoffs: Walker is newer/less battle-tested than Rofi. Requires separate Elephant daemon with per-provider packages. Dmenu mode lacks Rofi's -a/-u (active/urgent) and -mesg flags. Settings menu (moonarch-setmen) dropped entirely — apps are findable via Walker's app search.
  • How: Walker + Elephant as systemd user services. Native providers replace 5 rofi scripts (launcher, clipboard, bluetooth, volume, sink-switcher). 3 scripts ported to walker dmenu (vpn, cpugov, sink-switcher). Walker theme inherits GTK4 system theme colors (gtk-inherit). Old rofi configs preserved in legacy/rofi/.

2026-03-30 Use nm-applet as VPN secret agent, add WireGuard support

  • Who: Dominik, ClaudeCode
  • Why: VPN auth previously spawned a foot terminal for nmcli --ask, which was fragile and ugly. WireGuard connections were invisible to the VPN script and Waybar indicator because both only checked for OpenVPN (tun0 / vpn type).
  • Tradeoffs: nm-applet adds a tray indicator (mitigated with --indicator mode which is minimal). Requires nm-applet running at session start. Alternative was gnome-keyring or a custom secret agent — nm-applet is simpler and handles all NM secret types.
  • How: nm-applet started via niri spawn-at-startup. moonarch-vpn rewritten to support both vpn and wireguard types, uses nm-applet for auth instead of foot terminal, sends notify-send for connect/disconnect results. Waybar VPN module uses nmcli active connection check instead of /proc/sys/net/ipv4/conf/tun0, plus RTMIN+9 signal for instant updates after toggle.

2026-03-30 Standardize GTK theme to Colloid-Grey-Dark-Catppuccin

  • Who: Dominik, ClaudeCode
  • Why: gsettings had Colloid-Dark-Catppuccin while config files had Colloid-Catppuccin — inconsistent. Grey accent matches the icon theme (Colloid-Grey-Catppuccin-Dark). Explicit -Dark variant is more reliable than depending on prefer-dark color-scheme setting.
  • Tradeoffs: Explicit dark locks out light mode toggle — acceptable since Moonarch is dark-only by design.
  • How: Updated transform.sh, post-install.sh, gtk-3.0/settings.ini, and gsettings to Colloid-Grey-Dark-Catppuccin. GTK4 symlinks updated accordingly.