Compare commits

...

27 Commits

Author SHA1 Message Date
nevaforget d101b23351 docs: translate CLAUDE.md to English
Update PKGBUILD version / update-pkgver (push) Successful in 5s
Per the committed=English rule. Also documents the windowed-video idle-inhibit added in the same series.
2026-06-16 10:46:05 +02:00
nevaforget e6b7f53794 feat: keep windowed browser video awake via wayland-pipewire-idle-inhibit
stasis ignores browser audio (pactl, browser-excluded), so windowed/muted browser video let the screen sleep. Add wayland-pipewire-idle-inhibit (AUR + user service) holding a Wayland idle-inhibitor while audio plays. Enabled on fresh installs and checked by moonarch-doctor.
2026-06-16 10:46:05 +02:00
nevaforget 806841d435 feat(portal): keep windowed browser video from sleeping the screen
Update PKGBUILD version / update-pkgver (push) Successful in 3s
Add /etc/xdg-desktop-portal/niri-portals.conf with Inhibit=none. The gtk
portal reports Inhibit success even though nothing implements it under Niri,
so Firefox/Waterfox skips the native Wayland idle-inhibit. With no backend the
browser falls back to zwp_idle_inhibit, which Niri honors.
2026-06-12 11:10:00 +02:00
nevaforget f9f73db10f docs(stasis): correct monitor_media comment to match real behavior
monitor_media detects media via pactl sink-inputs and excludes browser
audio by design; it does not catch windowed browser video. ignore_remote_media
only affects remote players (Spotify-remote, Chromecast).
2026-06-12 11:10:00 +02:00
nevaforget dbc2997de0 fix(fonts): repair Waybar font and add system-wide fontconfig defaults
Update PKGBUILD version / update-pkgver (push) Successful in 3s
Nerd Fonts renamed "Ubuntu" -> "UbuntuSans"; Waybar's dead family name token-matched to Hack. Correct the explicit name and ship owned conf.d defaults mapping the generic families to the moonarch fonts.

- waybar/style.css: "Ubuntu Nerd Font" -> "UbuntuSans Nerd Font"
- conf.d/65-moonarch-fonts.conf: sans-serif/monospace, binding="strong" (weak prefer ranks behind the generic fallback)
- document fontconfig defaults in CLAUDE.md and DECISIONS.md
2026-06-08 13:39:25 +02:00
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
nevaforget 952776c4f9 batsaver: switch to pkexec helper, drop broken udev permission hack
Update PKGBUILD version / update-pkgver (push) Successful in 2s
The wheel-write-via-udev approach for charge_control_end_threshold has
been broken since 2026-04-08: the audit-remediation commit added
ACTION=="add" to the rule, but the threshold attribute doesn't exist
yet at the add event on Lenovo, so chmod fails silently and permissions
are never set. moonarch-batsaver-toggle has been returning Permission
denied since.

Replace the udev-rule approach with a pkexec helper:

  defaults/bin/moonarch-batsaver-apply    privileged: validate + write
  defaults/bin/moonarch-batsaver-toggle   user: read sysfs, dispatch via pkexec
  defaults/bin/moonarch-batsaver-restore  boot-time root restore (extracted
                                          from inline ExecStart for clarity)

Default Standard-pkexec prompt — password cached per session for the
~5min auth window; no polkit no-password rule, no privilege escalation
surface from misvalidated input. Same pattern Battery-Health-Charging
GNOME extension uses.

The boot-time restore service now skips the kernel write when the
sysfs value already matches the saved state (Lenovo drivers reject
same-value writes with EINVAL).

DECISIONS.md documents the failure analysis and trade-offs.
CLAUDE.md updated to describe the new flow.
moonarch-doctor: udev-effectiveness check removed.
2026-05-04 12:17:31 +02:00
nevaforget f4d60d387e ci(update-pkgver): only trigger on package-relevant paths
Update PKGBUILD version / update-pkgver (push) Successful in 5s
Workflow had no path filter — every push to main fired the pkgver-bump
which in turn triggered the heavy build-and-publish pipeline in
moonarch-pkgbuilds, even for changes that the moonarch-git PKGBUILD
does not package: README/DECISIONS edits, post-install.sh, lib.sh,
or workflow tweaks themselves.

Restrict the trigger to defaults/, packages/, and the two scripts
that PKGBUILD actually installs (moonarch-update, moonarch-doctor).
Comment lists explicitly what the filter excludes so the next reader
doesn't have to reverse-engineer it.
2026-05-04 11:14:09 +02:00
nevaforget 1e8b0d4ab0 cleanup: remove invented zsh override layer, harden moondoc
Earlier ClaudeCode sessions had wired a `~/.zshrc.d/*.zsh` snippet loop
plus a `~/.zshrc.local` fallback into defaults/shell/zshrc and made
post-install.sh create the directory unconditionally — neither is a zsh
convention nor documented anywhere. Remove both, simplify post-install
to write only `source /etc/zsh/zshrc.moonarch`, drop stale rustup
next-step hint, drop dead `confirm()` in lib.sh (orphan since
transform.sh deletion 2026-04-21).

moonarch-doctor: replace useless existence checks (zshrc.moonarch,
/usr/share/moonarch/) with real signal. User-service and helper-script
lists now derive from `pacman -Qql moonarch-git` (drift-proof) plus an
explicit list of post-install-enabled externals (currently `stasis`).
New udev-effectiveness check for charge_control_end_threshold —
verifies group=wheel + group-writable, surfaces broken rules instead
of staying silent.

Translate two German ABOUTME comments (moonarch-waybar-cpugov,
moonarch-waybar-gpustat) to English for consistency.
2026-05-04 11:09:45 +02:00
nevaforget 7b6ba3b0d1 fix(vpn): use 'id' keyword instead of '--' in nmcli connection up/down
Update PKGBUILD version / update-pkgver (push) Successful in 2s
Newer nmcli no longer parses '--' as end-of-options for 'connection up',
it treats it as the connection name and fails with "Unbekannte
Verbindung »--«". The 'id' keyword is the canonical way to mark the
following argument as the connection name.
2026-04-24 20:41:08 +02:00
nevaforget aef7f64b59 refactor(mpv): move ModernZ overrides into mpv.conf via script-opts-append
Update PKGBUILD version / update-pkgver (push) Successful in 2s
- drop defaults/etc/mpv/script-opts/modernz.conf (deploy timing conflict
  with mpv-modernz-git)
- put all ModernZ tweaks in mpv.conf: Catppuccin Lavender accent, scale
  0.75, title font 18, ontop_button off, autofit-larger 80%
- quote hex values to survive mpv's mid-line # comment parsing
- niri window-rule opens mpv floating
- doctor no longer compares mpv configs (now directly owned by pkg)
2026-04-24 17:52:12 +02:00
nevaforget 0064170430 feat: add mpv with ModernZ OSC as default video player
Update PKGBUILD version / update-pkgver (push) Successful in 3s
- mpv-modernz-git + mpv-thumbfast-git in aur.txt
- /etc/mpv/mpv.conf disables stock OSC + title bar
- /etc/mpv/script-opts/modernz.conf overrides colors to Catppuccin Mocha (lavender accent)
- moonarch-doctor checks deployed mpv configs
2026-04-24 17:32:05 +02:00
nevaforget 39094ee026 fix(stasis): flip ignore_remote_media so browser video inhibits idle
Browsers only raise org.freedesktop.ScreenSaver.Inhibit during fullscreen
playback; a windowed YouTube tab sent no inhibit, so stasis ran the full
idle plan to suspend despite active video. Letting browser MPRIS count
as a media player closes the gap without the wake-lock hacks.

See DECISIONS.md (2026-04-24).
2026-04-24 14:40:30 +02:00
nevaforget d5c1b8a155 fix: audit LOW fixes — nmcli guards, sink cancel, cpugov stderr, gpu interval
Update PKGBUILD version / update-pkgver (push) Successful in 2s
- moonarch-vpn: add `--` argument-terminator to `nmcli connection up/down`
  so a profile name starting with `-` is never interpreted as a flag.
- moonarch-sink-switcher: guard against empty `$sink` when walker is
  cancelled, since awk masks walker's non-zero exit. Prevents the error
  `pactl set-default-sink ""` on every dismissal.
- moonarch-waybar-cpugov: redirect stderr so non-cpufreq systems (VMs,
  some desktops) do not spam the journal on every 60s poll.
- waybar config: switch custom/gpu-usage from `restart-interval: 10` to
  `interval: 60`. The module lives in a closed drawer, a 10 s poll spawn
  was unnecessary background noise.
2026-04-24 13:59:20 +02:00
nevaforget 8aaf7cae5b fix: audit MEDIUM fixes — merge fallback, service hardening, CI token
- moonarch-waybar: on merge failure, remove the stale output so waybar
  falls back to the system config (previously it kept running with stale
  merged data despite the error notification claiming otherwise).
- moonarch-doctor: hoist INSTALLED assignment above both OFFICIAL and AUR
  blocks so the script survives set -u when only aur.txt is present.
- zshrc parse_git_branch: gate on git rev-parse and replace three grep
  subshells with bash pattern matching, cutting prompt latency from
  ~5 subprocesses per render to 2 (status + symbolic-ref).
- moonarch-batsaver.service: validate the threshold is an integer 1-100
  before writing to sysfs, add NoNewPrivileges and protection directives
  instead of relying on kernel validation alone.
- ci/act-runner/Dockerfile: drop the broad "pacman -Sy *" sudoers entry
  (only -S --needed is required by makepkg), and pin run.sh to
  act_runner:0.3.1 so it cannot drift ahead of the pinned binary.
- .gitea/workflows/update-pkgver.yaml: push via credential.helper=store
  with a chmod 600 temp file instead of `git -c http.extraHeader=...`,
  so the token no longer shows up in /proc/PID/cmdline.
2026-04-24 13:15:52 +02:00
nevaforget 89c3a9261e feat: add cursor-theme to GTK4 defaults for greeter
Update PKGBUILD version / update-pkgver (push) Successful in 3s
GTK3 defaults already had gtk-cursor-theme-name + size; GTK4 did not. Moongreet (GTK4) under greetd therefore fell back to the default cursor even with XCURSOR_THEME set on cage. Add the two lines so GTK4 apps pick up Sweet-cursors from system defaults before any window is created.
2026-04-24 10:53:26 +02:00
nevaforget e4ea267b6b feat: set cursor theme in moongreet config for greeter
Update PKGBUILD version / update-pkgver (push) Successful in 3s
Moongreet v0.9.0 reads cursor-theme/cursor-size from its TOML config
because GTK4 under greetd does not honour XCURSOR_THEME. Ship the
Sweet-cursors setting here so the greeter renders the intended cursor
on a fresh install without needing the env-prefix hack in greetd.
2026-04-24 08:57:28 +02:00
nevaforget 8485a63ab7 fix(moonup): read prompts from /dev/tty, robust GUI pause
Update PKGBUILD version / update-pkgver (push) Successful in 3s
Pacman/paru can drain or close stdin, so after the first interactive step
the EXIT trap's pause was silently skipped (the `-t 0` check failed) and
every subsequent confirm() prompt hit EOF — which with `[[ -z $response ]]`
auto-accepted, letting install/remove actions run unattended.

- _pause_on_exit: drop the `-t 0` guard and read from /dev/tty
- confirm(): read from /dev/tty so EOF on stdin can't masquerade as "yes"
- Move the trap installation above the gettext i18n init so an early
  failure (e.g. missing gettext) still triggers the pause message.
2026-04-23 08:08:52 +02:00
nevaforget 6e14258ad9 fix(doctor): drop obsolete paru repo check, cover walker + nautilus
Update PKGBUILD version / update-pkgver (push) Successful in 3s
The `[moonarch-pkgbuilds]` paru-repo check was a false failure: that
mechanism was retired on 2026-04-20 and the install hook strips the
legacy paru.conf section on upgrade.

Audit of the rest of the doctor surfaced two related gaps — the
user-services loop skipped `walker.service` and `nautilus.service`,
even though moonarch-git ships both and enables them via
graphical-session.target.wants. Added them to the loop and filled in
the missing `wlsunset` in the CLAUDE.md listing.
2026-04-22 08:56:23 +02:00
nevaforget 9432bc4831 fix(post-install): seed stasis config into user home
Stasis ignores /etc/xdg/ and only reads ~/.config/stasis/stasis.rune
(primary) or /etc/stasis/stasis.rune (fallback). On first start with no
user config it writes its own hardcoded default, so Moonarch's tuned
idle plans were never active on fresh installs.

Seed the template from /etc/xdg/stasis/stasis.rune into the user home
before stasis ever starts, only if the user file is missing. See
DECISIONS.md for verification against upstream v1.1.0.
2026-04-22 08:49:04 +02:00
nevaforget 373bfd4a9b fix(moonup): keep terminal open on errors via EXIT trap
The previous end-of-script `read` never ran when `set -e` aborted mid-way
(pacman conflict, paru failure, Ctrl+C), so foot closed on errors —
exactly when the user most needs to see the output.

Move the pause into a trap on EXIT, gated by `MOONUP_WAIT=1` so CLI use
stays non-interactive. Waybar on-click now sets the env var.
2026-04-22 08:23:25 +02:00
nevaforget c2cee85488 chore: drop moongreet polkit rule, now shipped by moongreet-git
Update PKGBUILD version / update-pkgver (push) Successful in 3s
The rule was never installed by any PKGBUILD from this repo anyway.
Moved to moongreet/config/polkit/ in v0.8.3 where it belongs — moonarch
should not own greeter-specific auth rules.
2026-04-21 09:12:11 +02:00
nevaforget 971a6eb577 refactor(install): restore fresh-install flow, drop transform.sh and personas
Long-standing gaps in post-install.sh plus cleanup:

- post-install.sh:18 was `sudo pacman -S paru` on the wrong
  assumption paru had landed in [extra]. Verified: paru/paru-bin
  are AUR-only. Restored the original git-clone + makepkg
  bootstrap, added the rust buildep that archinstall does not
  pull in.
- post-install.sh never installed AUR extras — walker, elephant,
  waypaper, stasis, themes all silently skipped. Now pulls
  packages/aur.txt after moonarch-git.
- packages/official.txt: drop glab, go, npm (unused) and rustup
  (only needed for the paru build, handled imperatively now).
- packages/aur.txt: add walker-bin (was missing entirely).
- transform.sh + legacy update.sh shim removed — transform was
  never used in practice.
- Apollo persona block out of CLAUDE.md, prior DECISIONS entries
  rewritten from Apollo/Ragnar to ClaudeCode.
- README Transform section and scripts/ listing trimmed.
- lib.sh ABOUTME updated — only post-install.sh sources it now.
2026-04-21 09:04:23 +02:00
nevaforget 0a38347cb9 feat(install): registry-only path, drop paru --pkgbuilds from setup
Update PKGBUILD version / update-pkgver (push) Successful in 2s
post-install.sh and transform.sh no longer write paru.conf entries for
the PKGBUILD repo — the Arch registry is the single source of truth.
pacman -Sy + paru -S moonarch-git now suffices. See DECISIONS.md.
2026-04-20 14:20:02 +02:00
nevaforget f4f6ede2a7 feat: i18n moonarch-update via pacman gettext + inline DE
Update PKGBUILD version / update-pkgver (push) Successful in 2s
Prompts and log lines now follow the user's LANG. Reuses pacman's
gettext catalog for strings with matching upstream msgids
(Proceed with installation?, [Y/n], Starting full system upgrade...,
Do you want to remove these packages?). Moonarch-specific strings
go through an inline _t "en" "de" helper keyed off ${LANG%%.*}.

confirm() switches to pacman-style: :: prefix, default Y, accepts
y/Y/j/J. No PKGBUILD change — gettext ships with base.
2026-04-19 15:47:28 +02:00
nevaforget ee85b87d3a fix: signal waybar after wlsunset starts to update nightlight icon
Waybar starts before wlsunset (due to ExecStartPre sleep) and only
checks the service status once. ExecStartPost sends SIGRTMIN+11 so
waybar refreshes the nightlight module after wlsunset is ready.
2026-04-16 10:36:05 +02:00
nevaforget fcac91b540 fix: add polkit rule to allow greeter user to reboot and shutdown
The greetd greeter session is inactive in logind, so polkit defaults
require admin authentication for power actions. This rule grants the
greeter user permission for reboot and power-off without authentication.
2026-04-15 14:23:49 +02:00
34 changed files with 487 additions and 690 deletions
+20 -1
View File
@@ -7,6 +7,15 @@ on:
push:
branches:
- main
paths:
# Only files that the moonarch-git PKGBUILD actually packages.
# README.md, DECISIONS.md, scripts/post-install.sh, scripts/lib.sh,
# CI workflow edits, etc. don't change the built package and must
# not trigger a rebuild.
- 'defaults/**'
- 'packages/**'
- 'scripts/moonarch-update'
- 'scripts/moonarch-doctor'
jobs:
update-pkgver:
@@ -21,6 +30,8 @@ jobs:
echo "$PKGVER" > /tmp/pkgver
- name: Update PKGBUILD
env:
PKGBUILD_TOKEN: ${{ secrets.PKGBUILD_TOKEN }}
run: |
PKGVER=$(cat /tmp/pkgver)
git clone https://gitea.moonarch.de/nevaforget/moonarch-pkgbuilds.git pkgbuilds
@@ -39,4 +50,12 @@ jobs:
git config user.email "gitea@moonarch.de"
git add moonarch-git/PKGBUILD
git commit -m "chore(moonarch-git): bump pkgver to $PKGVER"
git -c http.extraHeader="Authorization: token ${{ secrets.PKGBUILD_TOKEN }}" push
# Push via credential helper with a chmod 600 temp file, so the token
# never appears in /proc/PID/cmdline (as it would with `git -c
# http.extraHeader=...`).
CRED_FILE=$(mktemp)
chmod 600 "$CRED_FILE"
trap 'rm -f "$CRED_FILE"' EXIT
printf "https://pkgver-bot:%s@gitea.moonarch.de\n" "$PKGBUILD_TOKEN" > "$CRED_FILE"
git -c credential.helper="store --file=$CRED_FILE" push
+75 -42
View File
@@ -1,61 +1,94 @@
# Moonarch
Reproduzierbares Arch-Linux-Setup basierend auf archinstall + Post-Install-Automatisierung.
Reproducible Arch Linux setup based on archinstall + post-install automation.
## Projektstruktur
## Project Structure
- `config/` — archinstall-Konfiguration (inkl. custom-commands die das Repo nach /opt/moonarch klonen, root-owned)
- `scripts/`Post-Install- und Helper-Scripts
- `packages/`Paketlisten (offiziell + AUR), getrennt gepflegt
- `defaults/` — XDG-Configs, Shell-Config, Helper-Binaries, systemd Services, udev-Regeln, greetd/moongreet-Config, Wallpaper
- `config/` — archinstall configuration (incl. custom-commands that clone the repo to /opt/moonarch, root-owned)
- `scripts/`post-install and helper scripts
- `packages/`package lists (official + AUR), maintained separately
- `defaults/` — XDG configs, shell config, helper binaries, systemd services, udev rules, greetd/moongreet config, wallpaper
## Battery Conservation Mode
Laptops mit `charge_control_end_threshold`-Support (ThinkPad, Framework, etc.) erhalten einen Waybar-Toggle:
- Klick auf das Battery-Modul schaltet zwischen 80% und 100% Ladegrenze um
- Bei aktiver Conservation erscheint ein ♥-Icon neben der Battery-Anzeige
- Zustand wird in `/var/lib/moonarch/batsaver-threshold` persistiert und beim Boot via systemd-Service wiederhergestellt
- udev-Regel gibt Gruppe `wheel` Schreibzugriff auf den Threshold (kein sudo nötig)
- Auf Desktops ohne Battery-Support versteckt sich das Feature komplett
Laptops with `charge_control_end_threshold` support (ThinkPad, Framework, etc.) get a Waybar toggle:
- Clicking the battery module toggles the charge limit between 80% and 100%
- When conservation is active, a ♥ icon appears next to the battery indicator
- State is persisted in `/var/lib/moonarch/batsaver-threshold` and restored on boot via a systemd service (`moonarch-batsaver-restore`)
- Toggle flow: `moonarch-batsaver-toggle` (user script) reads sysfs, decides 80↔100, calls `pkexec /usr/bin/moonarch-batsaver-apply $NEW` for the privileged sysfs+state write step. Standard pkexec prompt (password once per session cache)
- On desktops without battery support the feature is hidden entirely
## Nightlight (Blaufilter)
## Nightlight (Blue Light Filter)
Waybar-Toggle für wlsunset (Wayland-nativer Blaufilter), persistenter Zustand via systemd:
- `wlsunset.service` (systemd User-Service) mit `After=kanshi.service` — startet erst wenn alle Outputs konfiguriert sind
- Klick auf das Nightlight-Modul in `group/brightness` toggled wlsunset an/aus (`enable --now` / `disable --now`)
- Zustand überlebt Reboots (enabled/disabled bleibt bestehen)
- Aktiver Zustand zeigt 󰌵 in Catppuccin Yellow, inaktiv 󰌶 in Standard-Textfarbe
- Signal SIGRTMIN+11 für sofortiges Waybar-Refresh
- Scripts: `moonarch-nightlight` (Toggle), `moonarch-waybar-nightlight` (Status-JSON)
Waybar toggle for wlsunset (Wayland-native blue light filter), persistent state via systemd:
- `wlsunset.service` (systemd user service) with `After=kanshi.service` — starts only once all outputs are configured
- **Default OFF** — fresh installs start without the filter. The PKGBUILD deliberately creates NO symlink for `wlsunset` in `/etc/systemd/user/graphical-session.target.wants/`, and post-install.sh does not enable the service.
- Clicking the nightlight module in `group/brightness` toggles wlsunset on/off (`enable --now` / `disable --now`)
- State survives reboots (user-scope symlink in `~/.config/systemd/user/...wants/`)
- Active state shows 󰌵 in Catppuccin Yellow, inactive 󰌶 in the default text color
- Signal SIGRTMIN+11 for immediate Waybar refresh
- Scripts: `moonarch-nightlight` (toggle), `moonarch-waybar-nightlight` (status JSON)
- **Important**: Never create a global-scope symlink at `/etc/systemd/user/...wants/wlsunset.service` — it overrides any user `disable` and makes the filter effectively impossible to turn off.
## Waybar Config Merger (moonarch-waybar)
Waybar wird über `moonarch-waybar` gestartet (nicht direkt). Der Wrapper merged eine optionale User-Config (`~/.config/waybar/userconfig`) mit der System-Config (`/etc/xdg/waybar/config`):
- `prepend`/`append`-Keys in der userconfig erweitern `modules-left`/`modules-center`/`modules-right` Arrays
- Alle anderen Top-Level-Keys werden als Modul-Definitionen per Object-Merge eingefügt
- Merge wird nur bei Änderungen ausgeführt (Timestamp-Vergleich)
- Bei Fehler: `notify-send` + `logger`, Waybar startet mit System-Config
- Generiert `~/.config/waybar/style.css` mit `@import` der System-Styles falls nicht vorhanden
- Benötigt `jq` (in PKGBUILD als Dependency)
- System-Config muss valides JSON sein (kein JSONC)
Waybar is started via `moonarch-waybar` (not directly). The wrapper merges an optional user config (`~/.config/waybar/userconfig`) with the system config (`/etc/xdg/waybar/config`):
- `prepend`/`append` keys in the userconfig extend the `modules-left`/`modules-center`/`modules-right` arrays
- All other top-level keys are inserted as module definitions via object merge
- The merge runs only on changes (timestamp comparison)
- On error: `notify-send` + `logger`, Waybar starts with the system config
- Generates `~/.config/waybar/style.css` with an `@import` of the system styles if not present
- Requires `jq` (declared as a dependency in the PKGBUILD)
- The system config must be valid JSON (no JSONC)
## mpv + ModernZ OSC
The video player is `mpv` with [ModernZ](https://github.com/Samillion/ModernZ) as the OSC, thumbnails via thumbfast:
- `mpv-modernz-git` provides `modernz.lua` + font + default config to `/etc/mpv/`
- `mpv-thumbfast-git` provides `thumbfast.lua` to `/etc/mpv/scripts/` (auto-detected by ModernZ)
- `defaults/etc/mpv/mpv.conf` is installed directly to `/etc/mpv/mpv.conf` by moonarch-git (owned)
- Stock OSC + title bar disabled, `autofit-larger=80%x80%` caps oversized windows
- ModernZ overrides via `script-opts-append` in mpv.conf: orange accent → Catppuccin Lavender (`#b4befe`), OSC scale 0.75, `window_title_font_size=18`, `ontop_button=no`
- **Important**: mpv treats `#` as a mid-line comment; hex colors must be quoted: `script-opts-append="modernz-seekbarfg_color=#b4befe"` (not `\#`, which only escapes and swallows the rest)
- Niri opens mpv floating (`window-rule` in `defaults/xdg/niri/config.kdl`)
## System Health Check (moonarch-doctor / moondoc)
Diagnose-Script das den Systemzustand gegen moonarch-Defaults prüft:
- Pakete (official.txt + aur.txt installiert? Orphans?)
- System-Services (NetworkManager, bluetooth, greetd, ufw, auto-cpufreq, etc.)
- User-Services (kanshi, stasis, cliphist-text, cliphist-image)
- Config-Dateien (SHA256-Vergleich deployed vs. moonarch-Default)
- Helper-Scripts + Symlinks (moonup, moondoc)
- System-Config (UFW, Pacman/Paru Repos, Default Shell)
- Verzeichnisse + Permissions
Diagnostic script that checks the system state against moonarch defaults:
- Packages (official.txt + aur.txt installed? Orphans?)
- System services (NetworkManager, bluetooth, greetd, ufw, auto-cpufreq, etc.)
- User services (kanshi, wlsunset, stasis, walker, nautilus, cliphist-text, cliphist-image)
- Config files (SHA256 comparison deployed vs. moonarch default)
- Helper scripts + symlinks (moonup, moondoc)
- System config (UFW, pacman/paru repos, default shell)
- Directories + permissions
## Konventionen
## Fontconfig Defaults
- Paketlisten sind einfache Textdateien, ein Paket pro Zeile, Kommentare mit `#`
- Shell-Scripts müssen POSIX-kompatibel oder explizit bash/zsh sein
- Alle Pfade im archinstall-Config relativ zum Installationsziel
System-wide generic-family defaults via `defaults/etc/fonts/conf.d/65-moonarch-fonts.conf` (owned by moonarch-git):
- `sans-serif` → UbuntuSans Nerd Font, `monospace` → UbuntuSansMono Nerd Font
- Number **65**: loads after `60-latin.conf`, so the moonarch prefs beat the stock default (Noto/DejaVu). `local.conf` (at 51 via `51-local.conf`) loads too early and is overridden by 60-latin — therefore **do not** use it (it is also reserved for local user overrides).
- Aliases need `binding="strong"` — a weak `<prefer>` (fontconfig default) ranks behind the effective generic fallback and does not take effect.
- Only applies to apps that use generic families (e.g. Firefox web fallback). moonarch apps (Waybar, foot, GTK, walker, swaync) set the font explicitly.
## Ich bin Apollo
## Browser Idle-Inhibit (xdg-desktop-portal)
Benannt nach dem Programm, das Menschen zum Mond gebracht hat — passend für ein Projekt namens Moonarch.
So that windowed browser video (Firefox/Waterfox) keeps the screen awake, via `defaults/etc/xdg-desktop-portal/niri-portals.conf` (owned by moonarch-git, higher priority than niri's `/usr/share/xdg-desktop-portal/niri-portals.conf`):
- `xdg-desktop-portal-gtk` reports the `Inhibit` interface as success even though nobody implements it under Niri → Firefox believes the idle-inhibit went through the portal and does not use the native Wayland `idle-inhibit`. Result: no inhibitor, screen sleeps.
- Fix: `org.freedesktop.impl.portal.Inhibit=none` → Firefox falls back to `zwp_idle_inhibit`, which Niri honors. The remaining `[preferred]` lines are taken 1:1 from niri's default (portals.conf is not merged — the highest-priority file applies in full).
- stasis is uninvolved here: `monitor_media` (pactl) does not capture browser audio by design (non-browser players only); browsers go through the inhibit path.
- **Activation:** restart xdg-desktop-portal + the browser — Firefox queries portal support at startup.
### Windowed / Muted Video: wayland-pipewire-idle-inhibit
The portal fix only applies to **fullscreen** video — Firefox/Waterfox send the idle-inhibit only in fullscreen. Windowed video remains unprotected: stasis ignores browser audio (pactl, browser-excluded, no MPRIS backend), Niri gets no inhibitor, the screen sleeps mid-video.
Solution: `wayland-pipewire-idle-inhibit` (AUR, systemd user service; in `packages/aur.txt` + `post-install.sh` USER_SERVICES + moonarch-doctor). Holds a Wayland `zwp_idle_inhibit` inhibitor while PipeWire outputs audio (default threshold 5s). Niri then suppresses idle → stasis stays awake, daemon-independent. Releases automatically when audio stops.
- Catches windowed video **with sound**. Misses muted video (no audio signal) — edge case, accepted.
- **Never** put `waterfox`/a browser in stasis `inhibit_apps`: that inhibits unconditionally as long as the browser process runs → the system never idles. This exact wrong fix caused the "no idle" bug.
## Conventions
- Package lists are plain text files, one package per line, comments with `#`
- Shell scripts must be POSIX-compatible or explicitly bash/zsh
- All paths in the archinstall config are relative to the install target
+83 -9
View File
@@ -1,5 +1,79 @@
# Decisions
## 2026-06-08 Fontconfig generic-family defaults via owned conf.d (65)
- **Who**: Dominik, ClaudeCode
- **Why**: Waybar rendered in Hack instead of Ubuntu after the Nerd Fonts "Ubuntu"→"UbuntuSans" rename (Canonical rebrand). Waybar's style.css referenced the now-dead family "Ubuntu Nerd Font"; fontconfig token-matched it to "Hack Nerd Font". An unowned hand-written `/etc/fonts/local.conf` additionally pinned sans-serif/monospace to Hack — a stale relic.
- **Tradeoffs**: `local.conf` (loaded at 51 via `51-local.conf`) is structurally too early — `60-latin.conf` prepends Noto/DejaVu afterwards and wins, so its sans-serif pin never took effect. local.conf is also reserved for local admin overrides, and packaging it as owned would collide with pre-existing unowned files on pacman update. A conf.d file at 65 loads after 60-latin (convention: 6069 = generic→family) and wins cleanly without conflict.
- **How**: New owned `defaults/etc/fonts/conf.d/65-moonarch-fonts.conf` maps sans-serif→UbuntuSans Nerd Font, monospace→UbuntuSansMono Nerd Font; installed by moonarch-git PKGBUILD. Stale `/etc/fonts/local.conf` removed. Waybar `style.css` font-family corrected "Ubuntu Nerd Font"→"UbuntuSans Nerd Font" (the bar wants the explicit proportional font, not a generic). The aliases need `binding="strong"` — verified that a weak `<prefer>` (fontconfig's default for `<alias>`) ranks behind the system's effective generic fallback and does not take effect; strong is required for it to apply.
## 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.install``pre_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.sh``wlsunset` 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.rune``ignore_remote_media true``false`, 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.
@@ -31,56 +105,56 @@
- **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, Ragnar
- **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, Ragnar
- **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**: Ragnar, Dom
- **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, Ragnar
- **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, Ragnar
- **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, Ragnar
- **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, Ragnar
- **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, Ragnar
- **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, Ragnar
- **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.
+3 -32
View File
@@ -16,7 +16,7 @@ desktop that can be rebuilt from scratch in minutes.
| **Greeter** | [greetd](https://sr.ht/~kennylevinsen/greetd/) + [moongreet](https://gitea.moonarch.de/nevaforget/moongreet) | Minimal, Wayland-native login. moongreet provides GTK4 UI with fingerprint support, running inside its own Niri instance. |
| **Lock Screen** | [moonlock](https://gitea.moonarch.de/nevaforget/moonlock) | ext-session-lock-v1 protocol — compositor guarantees lock on crash. PAM + fprintd, GPU blur, multi-monitor. |
| **Power Menu** | [moonset](https://gitea.moonarch.de/nevaforget/moonset) | GTK4 Layer Shell overlay above Waybar. Lock, logout, hibernate, reboot, shutdown with confirmation. |
| **Idle Manager** | [stasis](https://aur.archlinux.org/packages/stasis) | Separate AC/battery power plans. Brightness dimming, DPMS, lock (via moonlock), and suspend on configurable timeouts. |
| **Idle Manager** | [stasis](https://aur.archlinux.org/packages/stasis) + [wayland-pipewire-idle-inhibit](https://github.com/rafaelrc7/wayland-pipewire-idle-inhibit) | Separate AC/battery power plans. Brightness dimming, DPMS, lock (via moonlock), and suspend on configurable timeouts. The companion inhibitor holds a Wayland idle-inhibitor while audio plays — keeps the screen awake during windowed browser video, which stasis' pactl detection skips by design. |
| **Bar** | [Waybar](https://github.com/Alexays/Waybar) | Wayland-native, highly customizable. Niri workspace/window modules via community plugins. |
| **Launcher** | [Walker](https://github.com/abenz1267/walker) + [Elephant](https://github.com/abenz1267/elephant) | Wayland-native GTK4 launcher with built-in providers for apps, clipboard, bluetooth, audio, files, and calculator. Dmenu mode for custom scripts (VPN, CPU governor). |
| **Terminal** | [Foot](https://codeberg.org/dnkl/foot) | Fast, minimal Wayland-native terminal. Server mode for instant window spawning. |
@@ -54,35 +54,6 @@ The archinstall config clones this repo to `/opt/moonarch` via custom-commands.
post-install.sh handles the remaining ~95 packages, all configs, themes, services,
and user setup.
### Transform (Existing Arch+Wayland System)
Already running Arch with a Wayland compositor (Sway, Hyprland, GNOME Wayland, etc.)?
Transform converts your system to Moonarch without reinstalling.
**Prerequisites:** Active Wayland session, git installed.
```bash
# Clone the repo
sudo git clone https://gitea.moonarch.de/nevaforget/moonarch.git /opt/moonarch
# Preview what will happen (no changes made)
/opt/moonarch/scripts/transform.sh --dry-run
# Run the transform
/opt/moonarch/scripts/transform.sh
# Reboot (do NOT log out — your previous DM is already disabled)
sudo reboot
```
The script will:
1. Show a pre-flight summary (package diff, config changes, detected conflicts)
2. Back up your `~/.config/`, `~/.zshrc`, and `/etc/xdg/` to timestamped tar archives
3. Disable conflicting display managers (SDDM, GDM, LightDM, etc.)
4. Remove conflicting packages (e.g. PulseAudio → PipeWire)
5. Install all Moonarch packages and configs (**hard overwrite** of most user configs; kanshi display profiles are preserved)
6. Enable greetd, firewall, and system services
### Update
```bash
@@ -114,9 +85,8 @@ packages/
aur.txt AUR packages (~20), one per line
scripts/
lib.sh Shared helpers sourced by all scripts
lib.sh Shared helpers sourced by post-install.sh
post-install.sh Main automation (packages, configs, themes, services)
transform.sh Convert existing Arch+Wayland system to Moonarch
moonarch-update Interactive updater (deployed to /usr/bin/ as moonup)
moonarch-doctor System health checker (deployed to /usr/bin/ as moondoc)
@@ -236,6 +206,7 @@ are part of the system and updated via `paru -Syu`.
| kanshi | Dynamic display configuration (auto-switch output profiles on hotplug) |
| nautilus | File manager preload (faster first launch) |
| stasis | Idle manager (dimming, DPMS, lock, suspend on AC/battery plans) |
| wayland-pipewire-idle-inhibit | Holds a Wayland idle-inhibitor while audio plays (keeps screen awake for windowed browser video that stasis skips) |
| walker | Walker application launcher (GTK4 service mode for instant startup) |
## Moonarch Ecosystem
+2 -2
View File
@@ -1,10 +1,10 @@
FROM archlinux:base-devel
RUN pacman -Sy --noconfirm git curl && pacman -Scc --noconfirm
RUN useradd -m builder && echo "builder ALL=(ALL) NOPASSWD: /usr/bin/pacman -Sy *, /usr/bin/pacman -S --needed *" >> /etc/sudoers
RUN useradd -m builder && echo "builder ALL=(ALL) NOPASSWD: /usr/bin/pacman -S --needed *" >> /etc/sudoers
ADD https://gitea.com/gitea/act_runner/releases/download/v0.3.1/act_runner-0.3.1-linux-amd64 /usr/local/bin/act_runner
RUN echo "a05b2103a7cc5617197da214eaa06a1055362f21f9f475eb7fbacb8344d86cf8 /usr/local/bin/act_runner" | sha256sum -c - \
&& chmod +x /usr/local/bin/act_runner
COPY --from=gitea/act_runner:latest /usr/local/bin/run.sh /usr/local/bin/run.sh
COPY --from=gitea/act_runner:0.3.1 /usr/local/bin/run.sh /usr/local/bin/run.sh
RUN mkdir -p /data && chown builder:builder /data
USER builder
ENV HOME=/home/builder
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/bash
# ABOUTME: Privileged helper invoked via pkexec from moonarch-batsaver-toggle.
# ABOUTME: Validates the threshold value and writes it to sysfs and the state file.
set -eu
VAL="${1:-}"
case "$VAL" in
""|*[!0-9]*)
echo "Invalid argument: '$VAL' (expected integer 1-100)" >&2
exit 1
;;
esac
if [ "$VAL" -lt 1 ] || [ "$VAL" -gt 100 ]; then
echo "Out of range: $VAL (expected 1-100)" >&2
exit 1
fi
THRESHOLD_FILE="/sys/class/power_supply/BAT0/charge_control_end_threshold"
STATE_DIR="/var/lib/moonarch"
STATE_FILE="$STATE_DIR/batsaver-threshold"
[ -f "$THRESHOLD_FILE" ] || { echo "No battery threshold support" >&2; exit 1; }
# Skip the kernel write when the value already matches — some Lenovo drivers
# reject same-value writes with EINVAL.
CURRENT=$(cat "$THRESHOLD_FILE")
if [ "$CURRENT" != "$VAL" ]; then
printf %s "$VAL" > "$THRESHOLD_FILE"
fi
mkdir -p "$STATE_DIR"
printf %s "$VAL" > "$STATE_FILE"
+23
View File
@@ -0,0 +1,23 @@
#!/bin/sh
# ABOUTME: Restores the saved battery charge end threshold on boot.
# ABOUTME: Skips silently when the kernel already reports the same value (avoids EINVAL on some Lenovo drivers).
set -eu
STATE_FILE="/var/lib/moonarch/batsaver-threshold"
SYS_FILE="/sys/class/power_supply/BAT0/charge_control_end_threshold"
[ -f "$STATE_FILE" ] || exit 0
[ -f "$SYS_FILE" ] || exit 0
V=$(cat "$STATE_FILE")
case "$V" in
""|*[!0-9]*) exit 0 ;;
esac
[ "$V" -ge 1 ] && [ "$V" -le 100 ] || exit 0
# Some Lenovo drivers reject writing the same value with EINVAL.
C=$(cat "$SYS_FILE")
[ "$C" = "$V" ] && exit 0
printf %s "$V" > "$SYS_FILE"
+2 -9
View File
@@ -1,10 +1,8 @@
#!/usr/bin/bash
# ABOUTME: Toggles battery conservation mode between 80% and 100% charge limit.
# ABOUTME: Writes to sysfs (immediate) and state file (persistence across reboots).
# ABOUTME: Reads sysfs as user, dispatches the privileged write via pkexec.
THRESHOLD_FILE="/sys/class/power_supply/BAT0/charge_control_end_threshold"
STATE_DIR="/var/lib/moonarch"
STATE_FILE="${STATE_DIR}/batsaver-threshold"
CONSERVATION_LIMIT=80
[[ -f "$THRESHOLD_FILE" ]] || exit 1
@@ -18,12 +16,7 @@ else
NEW="$CONSERVATION_LIMIT"
fi
# Apply immediately
echo "$NEW" > "$THRESHOLD_FILE" || exit 1
# Persist for next boot
mkdir -p "$STATE_DIR"
echo "$NEW" > "$STATE_FILE" || exit 1
pkexec /usr/bin/moonarch-batsaver-apply "$NEW" || exit 1
# Signal Waybar to refresh the batsaver module (SIGRTMIN+9)
pkill -RTMIN+9 waybar
+5 -1
View File
@@ -5,7 +5,11 @@
# choose audio sink via rofi
# changes default sink and moves all streams to that sink
sink=$(pactl list sinks short | awk '{print $1, $2}' | walker -d -p "󱡫 Sink Switcher" | awk '{print $1}') &&
sink=$(pactl list sinks short | awk '{print $1, $2}' | walker -d -p "󱡫 Sink Switcher" | awk '{print $1}')
# Walker cancel returns empty — awk masks its non-zero exit. Guard here so we
# don't call `pactl set-default-sink ""` on dismissal.
[[ -n "$sink" ]] || exit 0
pactl set-default-sink "$sink" &&
for input in $(pactl list sink-inputs short | awk '{print $1}'); do
+2 -2
View File
@@ -39,13 +39,13 @@ function extract_connection_name() {
# Requires nm-applet (or another NM secret agent) for interactive auth.
function connect_vpn() {
local connection="$1"
nmcli connection up "$connection"
nmcli connection up id "$connection"
}
# Disconnect a VPN.
function disconnect_vpn() {
local connection="$1"
nmcli connection down "$connection"
nmcli connection down id "$connection"
}
# Toggle the VPN connection based on its current state.
+3 -1
View File
@@ -51,7 +51,9 @@ if [[ -f "$USERCONFIG" ]]; then
if [[ ! -f "$OUTPUT" ]] ||
[[ "$USERCONFIG" -nt "$OUTPUT" ]] ||
[[ "$SYSTEM_CONFIG" -nt "$OUTPUT" ]]; then
merge_config
# On merge failure the previous $OUTPUT is stale — remove it so waybar
# falls back to XDG's system config instead of running with stale merged data.
merge_config || rm -f "$OUTPUT"
fi
bootstrap_style
fi
+3 -3
View File
@@ -1,8 +1,8 @@
#!/usr/bin/bash
# ABOUTME: Waybar-Modul das den CPU-Governor als JSON ausgibt.
# ABOUTME: Wird von der Waybar custom/cpugov Config referenziert.
# ABOUTME: Waybar module that outputs the CPU governor as JSON.
# ABOUTME: Referenced by the Waybar custom/cpugov config.
CPU_GOV=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor)
CPU_GOV=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 2>/dev/null)
case $CPU_GOV in
performance)
+2 -2
View File
@@ -1,6 +1,6 @@
#!/usr/bin/bash
# ABOUTME: Waybar-Modul das die GPU-Auslastung als JSON ausgibt.
# ABOUTME: Wird von der Waybar custom/gpu-usage Config referenziert.
# ABOUTME: Waybar module that outputs GPU utilization as JSON.
# ABOUTME: Referenced by the Waybar custom/gpu-usage config.
GPU_STAT=$(cat /sys/class/hwmon/hwmon*/device/gpu_busy_percent 2>/dev/null | head -1 || echo "0")
GPU_STAT="${GPU_STAT:-0}"
@@ -0,0 +1,21 @@
<?xml version="1.0"?>
<!-- ABOUTME: System-wide fontconfig generic-family defaults for Moonarch. -->
<!-- ABOUTME: Loads after 60-latin (number 65) so these prefs win over stock defaults. -->
<!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>
+2
View File
@@ -3,6 +3,8 @@
[appearance]
background = "/usr/share/moonarch/wallpaper.jpg"
cursor-theme = "Sweet-cursors"
cursor-size = 24
[behavior]
# show_user_list = true
+23
View File
@@ -0,0 +1,23 @@
# Disable stock OSC — ModernZ replaces it.
osc=no
# Hide native title bar for a cleaner borderless look (ModernZ draws its own).
title-bar=no
# Cap window size at 80% of screen for oversized videos.
autofit-larger=80%x80%
# --- ModernZ overrides ---
# mpv treats # as a mid-line comment; the full value must be quoted so the
# hex color survives.
script-opts-append="modernz-seekbarfg_color=#b4befe"
script-opts-append="modernz-seek_handle_color=#b4befe"
script-opts-append="modernz-seek_handle_border_color=#b4befe"
script-opts-append="modernz-hover_effect_color=#b4befe"
script-opts-append="modernz-nibble_color=#b4befe"
script-opts-append="modernz-ontop_button=no"
script-opts-append="modernz-window_title_font_size=18"
# Scale OSC down globally
script-opts-append="modernz-scalewindowed=0.75"
script-opts-append="modernz-scalefullscreen=0.75"
@@ -9,7 +9,15 @@ ConditionPathExists=/var/lib/moonarch/batsaver-threshold
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'cat /var/lib/moonarch/batsaver-threshold > /sys/class/power_supply/BAT0/charge_control_end_threshold'
ExecStart=/usr/bin/moonarch-batsaver-restore
NoNewPrivileges=true
ProtectHome=true
PrivateTmp=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictNamespaces=true
RestrictRealtime=true
LockPersonality=true
[Install]
WantedBy=multi-user.target
@@ -12,6 +12,7 @@ Type=simple
# Give kanshi time to configure all outputs before wlsunset captures them
ExecStartPre=/bin/sleep 2
ExecStart=/usr/bin/wlsunset -T 6500 -t 5000 -S 00:00 -s 00:01
ExecStartPost=/usr/bin/pkill -RTMIN+11 waybar
Restart=on-failure
RestartSec=3
@@ -1,4 +0,0 @@
# ABOUTME: udev rule granting wheel group write access to battery charge threshold.
# ABOUTME: Enables unprivileged toggling of conservation mode via moonarch-batsaver-toggle.
SUBSYSTEM=="power_supply", ACTION=="add", ATTR{type}=="Battery", RUN+="/bin/sh -c 'chgrp wheel /sys%p/charge_control_end_threshold 2>/dev/null; chmod g+w /sys%p/charge_control_end_threshold 2>/dev/null'"
@@ -0,0 +1,10 @@
[preferred]
default=gnome;gtk;
org.freedesktop.impl.portal.Access=gtk;
org.freedesktop.impl.portal.Notification=gtk;
org.freedesktop.impl.portal.Secret=gnome-keyring;
# xdg-desktop-portal-gtk reports the Inhibit interface as success even though
# nothing implements it under Niri, which makes Firefox/Waterfox skip the
# native Wayland idle-inhibit. With no backend the browser falls back to
# zwp_idle_inhibit, which Niri honors, so windowed video keeps the screen awake.
org.freedesktop.impl.portal.Inhibit=none;
+14 -27
View File
@@ -1,5 +1,4 @@
# ABOUTME: Moonarch default zsh configuration with Catppuccin-themed prompt.
# ABOUTME: Sources user overrides from ~/.zshrc.d/ and ~/.zshrc.local
# --- History ---
HISTFILE=~/.histfile
@@ -30,26 +29,25 @@ add-zsh-hook preexec _preexec_title
# --- Prompt (Catppuccin Mocha) ---
parse_git_branch() {
local branch=""
branch=$(git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/')
local git_status=$(git status --porcelain 2>/dev/null)
local color=green
if echo "$git_status" | grep -q "^ M"; then
color=yellow
branch="${branch}*"
# Gate on cheap check first — spawning git in every non-repo directory on every
# prompt render costs 20-80ms per prompt. Pattern-match the status output with
# zsh glob matching instead of piping to grep for three subshell-spawning checks.
git rev-parse --git-dir &>/dev/null || return
local branch="" git_status="" color=green flags=""
branch=$(git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD 2>/dev/null)
git_status=$(git status --porcelain 2>/dev/null)
if [[ "$git_status" == *$'\n M '* || "$git_status" == " M "* || "$git_status" == *$'\nM'* ]]; then
color=yellow; flags+="*"
fi
if echo "$git_status" | grep -qE "^ A|^\?\?"; then
color=yellow
branch="${branch}+"
if [[ "$git_status" == *$'\nA '* || "$git_status" == "A "* || "$git_status" == *'??'* ]]; then
color=yellow; flags+="+"
fi
if echo "$git_status" | grep -q "^ D"; then
color=yellow
branch="${branch}-"
if [[ "$git_status" == *$'\n D '* || "$git_status" == " D "* ]]; then
color=yellow; flags+="-"
fi
if [[ -n "$branch" ]]; then
branch=[%F{${color}}${branch}%F{reset}]
echo " [%F{${color}}${branch}${flags}%F{reset}]"
fi
echo " $branch"
}
precmd() {
@@ -141,14 +139,3 @@ export XDG_SESSION_TYPE="wayland"
export EDITOR="nvim"
export SUDO_EDITOR="nvim"
export MOZ_ENABLE_WAYLAND="1"
# --- User override scripts ---
# Drop custom config snippets into ~/.zshrc.d/*.zsh
if [[ -d "$HOME/.zshrc.d" ]]; then
for f in "$HOME/.zshrc.d"/*.zsh(N); do
source "$f"
done
fi
# Single-file user override (for simple additions)
[[ -f "$HOME/.zshrc.local" ]] && source "$HOME/.zshrc.local"
+2
View File
@@ -4,4 +4,6 @@
[Settings]
gtk-theme-name=Colloid-Grey-Dark-Catppuccin
gtk-icon-theme-name=Colloid-Grey-Catppuccin-Dark
gtk-cursor-theme-name=Sweet-cursors
gtk-cursor-theme-size=24
gtk-application-prefer-dark-theme=1
+5
View File
@@ -116,6 +116,11 @@ window-rule {
open-floating true
}
window-rule {
match app-id=r#"^mpv$"#
open-floating true
}
window-rule {
geometry-corner-radius 4
clip-to-geometry true
+8 -2
View File
@@ -5,9 +5,15 @@
@description "Idle management for Moonarch (Niri + moonlock)"
default:
# Media playback inhibits idle (non-browser only, browser uses D-Bus inhibit)
# monitor_media: detect active media via PipeWire/PulseAudio sink-inputs
# (pactl), not MPRIS. Non-browser players (mpv, vlc, ...) are counted and
# inhibit idle. Browser audio is excluded by design; browser idle-inhibit is
# expected via D-Bus (enable_dbus_inhibit), which Waterfox/Firefox only send
# in fullscreen -- so windowed browser video is NOT caught by stasis.
# ignore_remote_media false: also count remote players (Spotify-remote,
# KDEConnect, Chromecast); has no effect on windowed browser video.
monitor_media true
ignore_remote_media true
ignore_remote_media false
# App/process inhibit patterns (apps that don't use D-Bus idle-inhibit)
inhibit_apps [
+2 -2
View File
@@ -217,7 +217,7 @@
},
"exec": "moonarch-waybar-updates",
"interval": 60,
"on-click": "foot moonarch-update"
"on-click": "foot env MOONUP_WAIT=1 moonarch-update"
},
"custom/notification": {
"tooltip": true,
@@ -319,7 +319,7 @@
"custom/gpu-usage": {
"exec": "moonarch-waybar-gpustat",
"return-type": "json",
"restart-interval": 10
"interval": 60
},
"battery": {
"bat": "BAT0",
+1 -1
View File
@@ -3,7 +3,7 @@
* {
border: none;
font-family: "Ubuntu Nerd Font", sans-serif;
font-family: "UbuntuSans Nerd Font", sans-serif;
font-size: 13px;
color: alpha(@theme_text_color, 0.8);
}
+6
View File
@@ -11,6 +11,7 @@ colloid-catppuccin-theme-git
otf-openmoji
# Niri / Wayland Extras
walker-bin
elephant-desktopapplications-bin
elephant-clipboard-bin
elephant-bluetooth-bin
@@ -29,6 +30,11 @@ wl-color-picker
blueberry
waterfox-bin
# Media
mpv-modernz-git
mpv-thumbfast-git
# System & Tools
auto-cpufreq
stasis
wayland-pipewire-idle-inhibit
-4
View File
@@ -91,11 +91,7 @@ viewnior
# Development
git
glab
go
neovim
npm
rustup
# System
fwupd
+1 -6
View File
@@ -1,6 +1,6 @@
#!/bin/bash
# ABOUTME: Shared helper functions and constants for Moonarch scripts.
# ABOUTME: Sourced by post-install.sh, update.sh and transform.sh.
# ABOUTME: Sourced by post-install.sh.
# Path constants — BASH_SOURCE[1] resolves to the calling script, not lib.sh itself.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[1]}")" && pwd)"
@@ -23,11 +23,6 @@ read_packages() {
grep -v '^\s*#' "$1" | grep -v '^\s*$'
}
confirm() {
read -r -p "$1 [y/N] " response
[[ "$response" =~ ^[yY]$ ]]
}
# --- Prerequisite checks ---
check_not_root() {
+28 -45
View File
@@ -109,8 +109,11 @@ section "Packages"
OFFICIAL="/usr/share/moonarch/official.txt"
AUR="/usr/share/moonarch/aur.txt"
# Hoist INSTALLED so the AUR block below can use it even if OFFICIAL is absent —
# otherwise `set -u` aborts the script when $INSTALLED is referenced unset.
INSTALLED=$(pacman -Qq 2>/dev/null)
if [[ -f "$OFFICIAL" ]]; then
INSTALLED=$(pacman -Qq 2>/dev/null)
MISSING_OFFICIAL=()
while IFS= read -r pkg; do
[[ "$pkg" =~ ^[[:space:]]*# ]] && continue
@@ -177,10 +180,19 @@ section "User Services"
if [[ $EUID -eq 0 ]]; then
warn "Running as root — skipping user service checks"
else
for svc in kanshi wlsunset stasis cliphist-text cliphist-image; do
elif pacman -Qq moonarch-git &>/dev/null; then
EXPECTED_SVCS=()
while IFS= read -r svc_file; do
EXPECTED_SVCS+=("$(basename "$svc_file" .service)")
done < <(pacman -Qql moonarch-git | grep -E '^/etc/systemd/user/[^/]+\.service$')
# Services enabled by post-install.sh from other packages
EXPECTED_SVCS+=(stasis wayland-pipewire-idle-inhibit)
for svc in "${EXPECTED_SVCS[@]}"; do
check_user_service "$svc"
done
else
warn "moonarch-git not installed — skipping user service checks"
fi
# --- 4. Config Files ---
@@ -197,46 +209,30 @@ check_config_match "/etc/greetd/config.toml" "$SRC/greetd/config.toml"
check_config_match "/etc/greetd/niri-greeter.kdl" "$SRC/greetd/niri-greeter.kdl"
check_config_match "/etc/moongreet/moongreet.toml" "$SRC/moongreet/moongreet.toml"
if [[ -f /etc/zsh/zshrc.moonarch ]]; then
pass "/etc/zsh/zshrc.moonarch"
else
fail "/etc/zsh/zshrc.moonarch (missing)"
fi
# --- 5. Helper Scripts ---
section "Helper Scripts"
EXPECTED_SCRIPTS=(
moonarch-batsaver-toggle
moonarch-btnote
moonarch-capsnote
moonarch-cpugov
moonarch-doctor
moonarch-nightlight
moonarch-sink-switcher
moonarch-update
moonarch-vpn
moonarch-waybar
moonarch-waybar-batsaver
moonarch-waybar-cpugov
moonarch-waybar-gpustat
moonarch-waybar-hidpp
moonarch-waybar-nightlight
moonarch-waybar-updates
)
if pacman -Qq moonarch-git &>/dev/null; then
EXPECTED_SCRIPTS=()
while IFS= read -r script; do
EXPECTED_SCRIPTS+=("$(basename "$script")")
done < <(pacman -Qql moonarch-git | grep -E '^/usr/bin/moonarch-[^/]+$')
MISSING_SCRIPTS=()
for script in "${EXPECTED_SCRIPTS[@]}"; do
MISSING_SCRIPTS=()
for script in "${EXPECTED_SCRIPTS[@]}"; do
if [[ ! -x "/usr/bin/$script" ]]; then
MISSING_SCRIPTS+=("$script")
fi
done
done
if [[ ${#MISSING_SCRIPTS[@]} -eq 0 ]]; then
if [[ ${#MISSING_SCRIPTS[@]} -eq 0 ]]; then
pass "All ${#EXPECTED_SCRIPTS[@]} helper scripts present"
else
else
fail "Missing scripts: ${MISSING_SCRIPTS[*]}"
fi
else
warn "moonarch-git not installed — skipping helper script checks"
fi
# Symlinks
@@ -270,13 +266,6 @@ else
fail "Pacman [moonarch] repo missing from /etc/pacman.conf"
fi
# Paru PKGBUILD repo
if grep -q '\[moonarch-pkgbuilds\]' /etc/paru.conf 2>/dev/null; then
pass "Paru [moonarch-pkgbuilds] repo configured"
else
fail "Paru [moonarch-pkgbuilds] repo missing from /etc/paru.conf"
fi
# Default shell
USER_SHELL=$(getent passwd "$USER" | cut -d: -f7)
if [[ "$USER_SHELL" == */zsh ]]; then
@@ -301,12 +290,6 @@ else
warn "/var/lib/moonarch/ missing (created on first battery toggle)"
fi
if [[ -d /usr/share/moonarch ]]; then
pass "/usr/share/moonarch/"
else
fail "/usr/share/moonarch/ missing (moonarch-git not installed?)"
fi
# --- Summary ---
echo
+53 -26
View File
@@ -4,6 +4,37 @@
set -euo pipefail
_t() {
# _t "english" "deutsch" — picks by $LANG
case "${LANG%%.*}" in
de_*) printf '%s' "$2" ;;
*) printf '%s' "$1" ;;
esac
}
# Pause on exit when launched from a GUI (Waybar sets MOONUP_WAIT=1).
# Runs via trap so it fires even when `set -e` aborts the script mid-way.
# Reads from /dev/tty because stdin may have been drained/closed by pacman/paru.
_pause_on_exit() {
local rc=$?
if [[ -n "${MOONUP_WAIT:-}" ]]; then
echo
if (( rc != 0 )); then
echo -e "\e[1;31m[Moonarch ERROR]\e[0m $(_t "Exited with error (code $rc)" "Mit Fehler beendet (Code $rc)")" >&2
fi
read -n 1 -s -r -p "$(_t "Press any key to close..." "Beliebige Taste drücken zum Schließen …")" < /dev/tty || true
echo
fi
}
trap _pause_on_exit EXIT
# --- i18n ---
# Reuse pacman's gettext catalog for prompts that match upstream msgids.
# For moonarch-specific strings fall back to _t() inline translation.
export TEXTDOMAIN=pacman
export TEXTDOMAINDIR=/usr/share/locale
YN=$(gettext "[Y/n]")
# --- Helper functions ---
log() {
@@ -19,8 +50,10 @@ read_packages() {
}
confirm() {
read -r -p "$1 [y/N] " response
[[ "$response" =~ ^[yY]$ ]]
# Read from /dev/tty so stdin state (drained by pacman/paru, closed by
# a Waybar launch) doesn't auto-confirm prompts by returning empty input.
read -r -p ":: $1 $YN " response < /dev/tty
[[ -z "$response" || "$response" =~ ^[yYjJ]$ ]]
}
OFFICIAL_PACKAGES="/usr/share/moonarch/official.txt"
@@ -29,85 +62,79 @@ AUR_PACKAGES="/usr/share/moonarch/aur.txt"
# --- Prerequisites ---
if [[ $EUID -eq 0 ]]; then
err "Do NOT run as root. The script uses sudo where needed."
err "$(_t "Do NOT run as root. The script uses sudo where needed." "Nicht als root ausführen. Das Script verwendet sudo wo nötig.")"
exit 1
fi
# --- 1. Update system packages ---
log "=== Update system packages ==="
log "=== $(gettext $'Starting full system upgrade...\n' | tr -d '\n.') ==="
if confirm "Run pacman -Syu?"; then
if confirm "$(_t "Run pacman -Syu?" "pacman -Syu ausführen?")"; then
sudo pacman -Syu
else
log "System update skipped."
log "$(_t "System update skipped." "Systemaktualisierung übersprungen.")"
fi
if command -v paru &>/dev/null; then
if confirm "Update AUR packages (paru -Sua)?"; then
if confirm "$(_t "Update AUR packages (paru -Sua)?" "AUR-Pakete aktualisieren (paru -Sua)?")"; then
paru -Sua
else
log "AUR update skipped."
log "$(_t "AUR update skipped." "AUR-Aktualisierung übersprungen.")"
fi
fi
# --- 2. Reconcile package lists ---
log "=== Reconcile package lists ==="
log "=== $(_t "Reconcile package lists" "Paketlisten abgleichen") ==="
if [[ -f "$OFFICIAL_PACKAGES" ]]; then
MISSING_OFFICIAL=$(comm -23 <(read_packages "$OFFICIAL_PACKAGES" | sort) <(pacman -Qq | sort) || true)
if [[ -n "$MISSING_OFFICIAL" ]]; then
log "Missing official packages:"
log "$(_t "Missing official packages:" "Fehlende offizielle Pakete:")"
echo "$MISSING_OFFICIAL"
if confirm "Install?"; then
if confirm "$(gettext 'Proceed with installation?')"; then
# shellcheck disable=SC2086
sudo pacman -S --needed --noconfirm $MISSING_OFFICIAL
fi
else
log "All official packages installed."
log "$(_t "All official packages installed." "Alle offiziellen Pakete installiert.")"
fi
fi
if [[ -f "$AUR_PACKAGES" ]] && command -v paru &>/dev/null; then
MISSING_AUR=$(comm -23 <(read_packages "$AUR_PACKAGES" | sort) <(pacman -Qq | sort) || true)
if [[ -n "$MISSING_AUR" ]]; then
log "Missing AUR packages:"
log "$(_t "Missing AUR packages:" "Fehlende AUR-Pakete:")"
echo "$MISSING_AUR"
if confirm "Install?"; then
if confirm "$(gettext 'Proceed with installation?')"; then
# shellcheck disable=SC2086
paru -S --needed --noconfirm $MISSING_AUR
fi
else
log "All AUR packages installed."
log "$(_t "All AUR packages installed." "Alle AUR-Pakete installiert.")"
fi
fi
# --- 3. Orphaned packages ---
log "=== Orphaned packages ==="
log "=== $(_t "Orphaned packages" "Verwaiste Pakete") ==="
ORPHANS=$(pacman -Qdtq 2>/dev/null || true)
if [[ -n "$ORPHANS" ]]; then
log "Orphaned packages found:"
log "$(_t "Orphaned packages found:" "Verwaiste Pakete gefunden:")"
echo "$ORPHANS"
if confirm "Remove?"; then
if confirm "$(gettext 'Do you want to remove these packages?')"; then
# shellcheck disable=SC2086
sudo pacman -Rsn --noconfirm $ORPHANS
fi
else
log "No orphaned packages."
log "$(_t "No orphaned packages." "Keine verwaisten Pakete.")"
fi
# --- Done ---
log ""
log "============================================"
log " Moonarch update complete!"
log " $(_t "Moonarch update complete!" "Moonarch-Aktualisierung abgeschlossen!")"
log "============================================"
# Keep terminal open when launched from a GUI (e.g. Waybar on-click)
if [[ -t 0 ]]; then
echo
read -n 1 -s -r -p "Press any key to close..."
fi
+39 -28
View File
@@ -14,8 +14,16 @@ check_pacman
# --- Install paru (AUR Helper) ---
if ! command -v paru &>/dev/null; then
log "Installing paru build dependencies..."
sudo pacman -S --needed --noconfirm base-devel rust
log "Installing paru..."
sudo pacman -S --needed --noconfirm paru
PARU_TMPDIR=$(mktemp -d)
trap 'rm -rf "$PARU_TMPDIR"' EXIT
git clone https://aur.archlinux.org/paru.git "$PARU_TMPDIR/paru"
(cd "$PARU_TMPDIR/paru" && makepkg -si --noconfirm)
rm -rf "$PARU_TMPDIR"
trap - EXIT
else
log "paru already installed."
fi
@@ -60,31 +68,23 @@ fi
rm -f "$KEY_FILE"
trap - EXIT
# --- Set up Moonarch custom paru repo (needed for first install bootstrap) ---
log "Setting up Moonarch paru repo..."
PARU_CONF="/etc/paru.conf"
if ! grep -q 'Mode.*p' "$PARU_CONF" 2>/dev/null; then
sudo sed -i '/^\[options\]/a Mode = arp' "$PARU_CONF"
log " + Mode = arp set in paru.conf."
else
log " ~ Mode already includes pkgbuilds."
fi
if ! grep -q '\[moonarch-pkgbuilds\]' "$PARU_CONF" 2>/dev/null; then
printf '\n[moonarch-pkgbuilds]\nUrl = https://gitea.moonarch.de/nevaforget/moonarch-pkgbuilds.git\n' \
| sudo tee -a "$PARU_CONF" > /dev/null
log " + Moonarch repo added to paru.conf."
else
log " ~ Moonarch repo already in paru.conf."
fi
paru -Syu --pkgbuilds --noconfirm
# --- Install moonarch-git (subsequent updates handled by moonarch.install hook) ---
# --- Install moonarch-git from the Arch registry ---
log "Installing moonarch-git package..."
sudo pacman -Sy --noconfirm
paru -S --needed --noconfirm moonarch-git
# --- Install AUR extras (cannot be hard depends of moonarch-git) ---
if [[ -f "$AUR_PACKAGES" ]]; then
log "Installing AUR packages from $AUR_PACKAGES..."
# shellcheck disable=SC2046
paru -S --needed --noconfirm $(read_packages "$AUR_PACKAGES")
else
err "AUR package list not found: $AUR_PACKAGES"
exit 1
fi
# --- User-level GTK4 symlinks for libadwaita apps ---
THEME_NAME="Colloid-Grey-Dark-Catppuccin"
@@ -114,9 +114,18 @@ gsettings set org.gnome.desktop.interface font-name 'UbuntuSans Nerd Font 11'
if [[ ! -f "$HOME/.zshrc" ]]; then
log "No ~/.zshrc found — sourcing Moonarch defaults."
mkdir -p "$HOME/.zshrc.d"
echo "# Load Moonarch defaults, add custom overrides in ~/.zshrc.d/ or below" > "$HOME/.zshrc"
echo "source /etc/zsh/zshrc.moonarch" >> "$HOME/.zshrc"
echo "source /etc/zsh/zshrc.moonarch" > "$HOME/.zshrc"
fi
# --- Seed Stasis user config ---
#
# Stasis reads ~/.config/stasis/stasis.rune (or /etc/stasis/stasis.rune as
# fallback) but never /etc/xdg/. Without a user config it writes its own
# upstream default on first start. Seed Moonarch's template so the bootstrap
# sees an existing file and skips. Never overwrite an existing user config.
if [[ ! -f "$HOME/.config/stasis/stasis.rune" && -f /etc/xdg/stasis/stasis.rune ]]; then
log "Seeding Moonarch stasis config to user home."
install -Dm644 /etc/xdg/stasis/stasis.rune "$HOME/.config/stasis/stasis.rune"
fi
# --- Enable systemd user services ---
@@ -124,11 +133,14 @@ fi
log "Enabling systemd user services..."
USER_SERVICES=(
"kanshi"
"wlsunset"
"stasis"
"wayland-pipewire-idle-inhibit"
"cliphist-text"
"cliphist-image"
)
# wlsunset deliberately excluded: nightlight is a user-toggle (off by default).
# Enabling it system-wide would create a global-scope WantedBy symlink that
# overrides any user-scope `systemctl --user disable`.
for service in "${USER_SERVICES[@]}"; do
if systemctl --user cat "${service}.service" &>/dev/null; then
@@ -199,6 +211,5 @@ log ""
log "Next steps:"
log " 1. Reboot"
log " 2. Place wallpapers in ~/Pictures/Wallpaper/"
log " 3. rustup default stable"
log " 4. User overrides in ~/.config/ or ~/.zshrc.d/"
log " 3. User overrides in ~/.config/"
log ""
-422
View File
@@ -1,422 +0,0 @@
#!/bin/bash
# ABOUTME: Transforms an existing Arch+Wayland system into a Moonarch system.
# ABOUTME: Backs up configs, installs moonarch-git package, deploys user configs with hard overwrite.
set -euo pipefail
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib.sh"
# --- Parse arguments ---
DRY_RUN=false
if [[ "${1:-}" == "--dry-run" ]]; then
DRY_RUN=true
fi
# ============================================================
# Phase 1: Prerequisites & Detection
# ============================================================
check_not_root
check_pacman
# Require active Wayland session
if [[ "${XDG_SESSION_TYPE:-}" != "wayland" ]] && [[ -z "${WAYLAND_DISPLAY:-}" ]]; then
err "No active Wayland session detected. Transform requires Wayland."
exit 1
fi
# Detect conflicting display managers
CONFLICTING_DMS=()
for dm in sddm gdm lightdm ly lemurs; do
if systemctl is-enabled "${dm}.service" &>/dev/null; then
CONFLICTING_DMS+=("$dm")
fi
done
# Detect conflicting packages
CONFLICTING_PKGS=()
if pacman -Qq pulseaudio &>/dev/null && ! pacman -Qq pipewire-pulse &>/dev/null; then
CONFLICTING_PKGS+=("pulseaudio")
fi
# ============================================================
# Phase 2: Pre-flight Summary
# ============================================================
echo ""
if $DRY_RUN; then
log "============================================"
log " Moonarch Transform — Dry Run"
log "============================================"
else
log "============================================"
log " Moonarch Transform — Pre-flight Summary"
log "============================================"
fi
echo ""
log "Wayland session: detected"
if [[ ${#CONFLICTING_DMS[@]} -gt 0 ]]; then
log "Display managers: ${CONFLICTING_DMS[*]} (will be disabled)"
else
log "Display managers: none conflicting"
fi
if [[ ${#CONFLICTING_PKGS[@]} -gt 0 ]]; then
log "Package conflicts: ${CONFLICTING_PKGS[*]} (will be removed)"
else
log "Package conflicts: none"
fi
echo ""
log "Actions:"
log " 1. (Optional) Backup ~/.config/, ~/.zshrc, /etc/xdg/"
log " 2. Install moonarch-git package (pulls in all dependencies)"
log " 3. Disable conflicting DMs, enable greetd"
log " 4. Overwrite ALL user configs (~/.config/)"
log " 5. Configure GTK themes, firewall, services"
echo ""
# Show package diff
MISSING_OFFICIAL=$(comm -23 <(read_packages "$OFFICIAL_PACKAGES" | sort) <(pacman -Qqe | sort) 2>/dev/null || true)
MISSING_AUR=""
if [[ -f "$AUR_PACKAGES" ]]; then
MISSING_AUR=$(comm -23 <(read_packages "$AUR_PACKAGES" | sort) <(pacman -Qqe | sort) 2>/dev/null || true)
fi
if [[ -n "$MISSING_OFFICIAL" ]] || [[ -n "$MISSING_AUR" ]]; then
log "Packages to install:"
if [[ -n "$MISSING_OFFICIAL" ]]; then
OFFICIAL_COUNT=$(echo "$MISSING_OFFICIAL" | wc -l)
log " Official ($OFFICIAL_COUNT):"
echo "$MISSING_OFFICIAL" | sed 's/^/ /'
fi
if [[ -n "$MISSING_AUR" ]]; then
AUR_COUNT=$(echo "$MISSING_AUR" | wc -l)
log " AUR ($AUR_COUNT):"
echo "$MISSING_AUR" | sed 's/^/ /'
fi
else
log "Packages: all already installed"
fi
# Show config diff summary
CHANGED_XDG=0
CHANGED_BIN=0
for src_dir in "$DEFAULTS_DIR/xdg/"*/; do
app_name="$(basename "$src_dir")"
dest_dir="$HOME/.config/$app_name"
if [[ -d "$dest_dir" ]]; then
if ! diff -rq "$src_dir" "$dest_dir" &>/dev/null 2>&1; then
((CHANGED_XDG++)) || true
fi
else
((CHANGED_XDG++)) || true
fi
done
for bin in "$DEFAULTS_DIR/bin/moonarch-"*; do
name=$(basename "$bin")
if ! cmp -s "$bin" "/usr/bin/$name" 2>/dev/null; then
((CHANGED_BIN++)) || true
fi
done
log "Config changes: $CHANGED_XDG XDG app(s), $CHANGED_BIN helper script(s)"
echo ""
err "This will REPLACE your current desktop configuration."
echo ""
if $DRY_RUN; then
log "Dry run complete — no changes were made."
exit 0
fi
if ! confirm "Proceed?"; then
log "Transform cancelled."
exit 0
fi
# ============================================================
# Phase 3: Backup (optional)
# ============================================================
BACKUP_FILE=""
SYSTEM_BACKUP=""
if confirm "Create backup of current configs before overwriting?"; then
BACKUP_FILE="$HOME/moonarch-backup-$(date +%Y%m%d-%H%M%S).tar.gz"
log "Creating backup: $BACKUP_FILE"
# Build list of paths that actually exist
BACKUP_PATHS=()
[[ -d "$HOME/.config" ]] && BACKUP_PATHS+=(".config")
[[ -f "$HOME/.zshrc" ]] && BACKUP_PATHS+=(".zshrc")
[[ -d "$HOME/.zshrc.d" ]] && BACKUP_PATHS+=(".zshrc.d")
if [[ ${#BACKUP_PATHS[@]} -gt 0 ]]; then
tar czf "$BACKUP_FILE" -C "$HOME" "${BACKUP_PATHS[@]}"
log " + User configs backed up."
fi
# Backup system XDG separately (needs sudo)
if [[ -d /etc/xdg ]]; then
SYSTEM_BACKUP="$HOME/moonarch-backup-system-$(date +%Y%m%d-%H%M%S).tar.gz"
sudo tar czf "$SYSTEM_BACKUP" -C / etc/xdg
sudo chown "$USER:$USER" "$SYSTEM_BACKUP"
log " + System configs backed up: $SYSTEM_BACKUP"
fi
log "Backup complete: $(du -h "$BACKUP_FILE" | cut -f1)"
else
log "Skipping backup."
fi
# ============================================================
# Phase 4: Disable Conflicting Display Managers
# ============================================================
if [[ ${#CONFLICTING_DMS[@]} -gt 0 ]]; then
log "Disabling conflicting display managers..."
for dm in "${CONFLICTING_DMS[@]}"; do
sudo systemctl disable "$dm"
log " - $dm disabled"
done
fi
# ============================================================
# Phase 5: Remove Conflicting Packages
# ============================================================
if pacman -Qq pulseaudio &>/dev/null; then
log "Removing PulseAudio (replaced by PipeWire)..."
sudo pacman -Rdd --noconfirm pulseaudio pulseaudio-alsa pulseaudio-bluetooth 2>/dev/null || true
fi
# ============================================================
# Phase 6: Install moonarch-git Package
# ============================================================
# Install paru if not present
if ! command -v paru &>/dev/null; then
log "Installing paru..."
sudo pacman -S --needed --noconfirm paru
else
log "paru already installed."
fi
# Moonarch package registry
log "Setting up Moonarch package registry..."
if ! grep -q '\[moonarch\]' /etc/pacman.conf 2>/dev/null; then
sudo tee -a /etc/pacman.conf > /dev/null <<'EOCONF'
[moonarch]
SigLevel = Required DatabaseOptional
Server = https://gitea.moonarch.de/api/packages/nevaforget/arch/$repo/$arch
EOCONF
log " + Moonarch repo added to pacman.conf."
else
log " ~ Moonarch repo already in pacman.conf."
fi
log "Importing Moonarch registry signing key..."
EXPECTED_FINGERPRINT="9B02C596A4652C40CA768E75B90C8B82EA30A131"
KEY_FILE=$(mktemp)
trap 'rm -f "$KEY_FILE"' EXIT
curl -sf https://gitea.moonarch.de/api/packages/nevaforget/arch/repository.key -o "$KEY_FILE"
if [[ ! -s "$KEY_FILE" ]]; then
err "Failed to download registry key (empty response)."
exit 1
fi
KEY_FPR=$(gpg --show-keys --with-colons "$KEY_FILE" 2>/dev/null | awk -F: '/^fpr/{print $10; exit}')
if [[ "$KEY_FPR" != "$EXPECTED_FINGERPRINT" ]]; then
err "Registry key fingerprint mismatch! Expected $EXPECTED_FINGERPRINT, got ${KEY_FPR:-<empty>}"
exit 1
fi
KEY_ID=$(gpg --show-keys --with-colons "$KEY_FILE" 2>/dev/null | awk -F: '/^pub/{print $5}')
if [[ -n "$KEY_ID" ]] && ! sudo pacman-key --list-keys "$KEY_ID" &>/dev/null; then
sudo pacman-key --add "$KEY_FILE"
sudo pacman-key --lsign-key "$KEY_ID"
log " + Registry key $KEY_ID imported and locally signed."
else
log " ~ Registry key already imported."
fi
rm -f "$KEY_FILE"
trap - EXIT
# Install/update moonarch-git (paru repo config is set up by moonarch.install hook)
paru -Syu --pkgbuilds --noconfirm
log "Installing moonarch-git package..."
paru -S --needed --noconfirm moonarch-git
# Install packages from package lists
if [[ -n "$MISSING_OFFICIAL" ]]; then
log "Installing official packages..."
echo "$MISSING_OFFICIAL" | paru -S --needed --noconfirm -
fi
if [[ -n "$MISSING_AUR" ]]; then
log "Installing AUR packages..."
echo "$MISSING_AUR" | paru -S --needed --noconfirm -
fi
# ============================================================
# Phase 7: User-Level Configuration
# ============================================================
# User-level GTK4 symlinks for libadwaita apps
THEME_NAME="Colloid-Grey-Dark-Catppuccin"
THEME_GTK4="/usr/share/themes/$THEME_NAME/gtk-4.0"
if [[ -d "$THEME_GTK4" ]]; then
log "Creating user-level GTK4 symlinks for $THEME_NAME..."
USER_GTK4="$HOME/.config/gtk-4.0"
mkdir -p "$USER_GTK4"
ln -sf "$THEME_GTK4/gtk-dark.css" "$USER_GTK4/gtk.css"
ln -sf "$THEME_GTK4/gtk-dark.css" "$USER_GTK4/gtk-dark.css"
rm -f "$USER_GTK4/assets"
ln -s "$THEME_GTK4/assets" "$USER_GTK4/assets"
else
err "GTK4 theme not found: $THEME_GTK4 — libadwaita apps will use fallback theme."
fi
# gsettings
log "Setting gsettings for GTK theme..."
gsettings set org.gnome.desktop.interface gtk-theme "$THEME_NAME"
gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark'
gsettings set org.gnome.desktop.interface icon-theme 'Colloid-Grey-Catppuccin-Dark'
gsettings set org.gnome.desktop.interface font-name 'UbuntuSans Nerd Font 11'
# ============================================================
# Phase 8: Deploy User Configs (Hard Overwrite)
# ============================================================
# Replace user-level XDG configs from /etc/xdg/ (deployed by moonarch-git)
log "Deploying XDG configs to ~/.config/ (overwrite)..."
for src_dir in /etc/xdg/*/; do
app_name="$(basename "$src_dir")"
# Only overwrite apps that moonarch manages
[[ -d "$DEFAULTS_DIR/xdg/$app_name" ]] || continue
# gtk-4.0 is handled separately with Colloid-Grey-Dark-Catppuccin theme symlinks
[[ "$app_name" == "gtk-4.0" ]] && continue
# kanshi profiles are user-specific (display setup) — only seed, never overwrite
[[ "$app_name" == "kanshi" ]] && [[ -d "$HOME/.config/kanshi" ]] && continue
# niri user config includes system config — never overwrite (handled below)
[[ "$app_name" == "niri" ]] && continue
dest_dir="$HOME/.config/$app_name"
rm -rf "$dest_dir" 2>/dev/null || sudo rm -rf "$dest_dir"
cp -r --no-preserve=ownership "$src_dir" "$dest_dir"
log " + $app_name/"
done
# Niri: seed user config with include of system config (preserves user overrides)
niri_user_config="$HOME/.config/niri/config.kdl"
if [[ ! -f "$niri_user_config" ]]; then
mkdir -p "$HOME/.config/niri"
cat > "$niri_user_config" << 'NIRI_EOF'
// ABOUTME: Moonarch user niri config — includes system defaults.
// ABOUTME: Add personal overrides below the include statement.
include "/etc/xdg/niri/config.kdl"
NIRI_EOF
log " + niri/ (seeded with include)"
else
log " ~ niri/ (user config exists, skipped)"
fi
# Overwrite configs owned by other packages with moonarch versions
log "Deploying moonarch config overrides..."
cp /usr/share/moonarch/walker-config.toml "$HOME/.config/walker/config.toml"
log " + walker/config.toml"
# Zsh: always create a fresh .zshrc that sources Moonarch defaults
log "Creating ~/.zshrc with Moonarch defaults..."
mkdir -p "$HOME/.zshrc.d"
echo "# Load Moonarch defaults, add custom overrides in ~/.zshrc.d/ or below" > "$HOME/.zshrc"
echo "source /etc/zsh/zshrc.moonarch" >> "$HOME/.zshrc"
# ============================================================
# Phase 9: Services & Finalization
# ============================================================
# Enable systemd user services
log "Enabling systemd user services..."
USER_SERVICES=(
"kanshi"
"wlsunset"
"stasis"
"cliphist-text"
"cliphist-image"
)
for service in "${USER_SERVICES[@]}"; do
if systemctl --user cat "${service}.service" &>/dev/null; then
systemctl --user enable "$service"
log " + $service (user)"
else
log " ~ $service (user) not found, skipped."
fi
done
log "Enabling services..."
SERVICES=(
"NetworkManager"
"bluetooth"
"greetd"
"systemd-timesyncd"
"ufw"
"auto-cpufreq"
)
for service in "${SERVICES[@]}"; do
if systemctl list-unit-files "${service}.service" &>/dev/null; then
sudo systemctl enable "$service"
log " + $service"
else
log " ~ $service not found, skipped."
fi
done
# Set shell to zsh
if [[ "$SHELL" != */zsh ]]; then
log "Setting default shell to zsh..."
chsh -s "$(which zsh)"
fi
# Firewall
log "Configuring UFW..."
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw --force enable
# Directories
mkdir -p "$HOME/Pictures/Screenshots"
mkdir -p "$HOME/Pictures/Wallpaper"
# ============================================================
# Phase 10: Done
# ============================================================
echo ""
log "============================================"
log " Moonarch transform complete!"
log "============================================"
echo ""
if [[ -n "$BACKUP_FILE" ]]; then
log "Your previous config is backed up at:"
log " $BACKUP_FILE"
if [[ -n "$SYSTEM_BACKUP" ]]; then
log " $SYSTEM_BACKUP"
fi
fi
echo ""
log "Next steps:"
log " 1. Reboot (greetd replaces your previous display manager)"
log " 2. Place wallpapers in ~/Pictures/Wallpaper/"
log " 3. rustup default stable"
log " 4. User overrides in ~/.config/ or ~/.zshrc.d/"
echo ""
err "Do NOT log out — your previous DM is disabled. Reboot instead."
echo ""
-14
View File
@@ -1,14 +0,0 @@
#!/bin/bash
# ABOUTME: Legacy updater — replaced by moonarch-update from the moonarch-git package.
# ABOUTME: Prints deprecation notice and forwards if package version is available.
echo ""
echo -e "\e[1;33m[Moonarch]\e[0m moonarch-update is now provided by the moonarch-git package."
echo -e "\e[1;33m[Moonarch]\e[0m Install with: paru -S moonarch-git"
echo -e "\e[1;33m[Moonarch]\e[0m Then run: moonarch-update"
echo ""
if command -v moonarch-update &>/dev/null && [[ "$(which moonarch-update)" == "/usr/bin/moonarch-update" ]]; then
echo -e "\e[1;34m[Moonarch]\e[0m Package version detected. Forwarding..."
exec moonarch-update
fi