Compare commits

..

24 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
34 changed files with 453 additions and 690 deletions
+20 -1
View File
@@ -7,6 +7,15 @@ on:
push: push:
branches: branches:
- main - 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: jobs:
update-pkgver: update-pkgver:
@@ -21,6 +30,8 @@ jobs:
echo "$PKGVER" > /tmp/pkgver echo "$PKGVER" > /tmp/pkgver
- name: Update PKGBUILD - name: Update PKGBUILD
env:
PKGBUILD_TOKEN: ${{ secrets.PKGBUILD_TOKEN }}
run: | run: |
PKGVER=$(cat /tmp/pkgver) PKGVER=$(cat /tmp/pkgver)
git clone https://gitea.moonarch.de/nevaforget/moonarch-pkgbuilds.git pkgbuilds git clone https://gitea.moonarch.de/nevaforget/moonarch-pkgbuilds.git pkgbuilds
@@ -39,4 +50,12 @@ jobs:
git config user.email "gitea@moonarch.de" git config user.email "gitea@moonarch.de"
git add moonarch-git/PKGBUILD git add moonarch-git/PKGBUILD
git commit -m "chore(moonarch-git): bump pkgver to $PKGVER" 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 # 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) - `config/` — archinstall configuration (incl. custom-commands that clone the repo to /opt/moonarch, root-owned)
- `scripts/`Post-Install- und Helper-Scripts - `scripts/`post-install and helper scripts
- `packages/`Paketlisten (offiziell + AUR), getrennt gepflegt - `packages/`package lists (official + AUR), maintained separately
- `defaults/` — XDG-Configs, Shell-Config, Helper-Binaries, systemd Services, udev-Regeln, greetd/moongreet-Config, Wallpaper - `defaults/` — XDG configs, shell config, helper binaries, systemd services, udev rules, greetd/moongreet config, wallpaper
## Battery Conservation Mode ## Battery Conservation Mode
Laptops mit `charge_control_end_threshold`-Support (ThinkPad, Framework, etc.) erhalten einen Waybar-Toggle: Laptops with `charge_control_end_threshold` support (ThinkPad, Framework, etc.) get a Waybar toggle:
- Klick auf das Battery-Modul schaltet zwischen 80% und 100% Ladegrenze um - Clicking the battery module toggles the charge limit between 80% and 100%
- Bei aktiver Conservation erscheint ein ♥-Icon neben der Battery-Anzeige - When conservation is active, a ♥ icon appears next to the battery indicator
- Zustand wird in `/var/lib/moonarch/batsaver-threshold` persistiert und beim Boot via systemd-Service wiederhergestellt - State is persisted in `/var/lib/moonarch/batsaver-threshold` and restored on boot via a systemd service (`moonarch-batsaver-restore`)
- udev-Regel gibt Gruppe `wheel` Schreibzugriff auf den Threshold (kein sudo nötig) - 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)
- Auf Desktops ohne Battery-Support versteckt sich das Feature komplett - 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: Waybar toggle for wlsunset (Wayland-native blue light filter), persistent state via systemd:
- `wlsunset.service` (systemd User-Service) mit `After=kanshi.service` — startet erst wenn alle Outputs konfiguriert sind - `wlsunset.service` (systemd user service) with `After=kanshi.service` — starts only once all outputs are configured
- Klick auf das Nightlight-Modul in `group/brightness` toggled wlsunset an/aus (`enable --now` / `disable --now`) - **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.
- Zustand überlebt Reboots (enabled/disabled bleibt bestehen) - Clicking the nightlight module in `group/brightness` toggles wlsunset on/off (`enable --now` / `disable --now`)
- Aktiver Zustand zeigt 󰌵 in Catppuccin Yellow, inaktiv 󰌶 in Standard-Textfarbe - State survives reboots (user-scope symlink in `~/.config/systemd/user/...wants/`)
- Signal SIGRTMIN+11 für sofortiges Waybar-Refresh - Active state shows 󰌵 in Catppuccin Yellow, inactive 󰌶 in the default text color
- Scripts: `moonarch-nightlight` (Toggle), `moonarch-waybar-nightlight` (Status-JSON) - 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 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`): 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 der userconfig erweitern `modules-left`/`modules-center`/`modules-right` Arrays - `prepend`/`append` keys in the userconfig extend the `modules-left`/`modules-center`/`modules-right` arrays
- Alle anderen Top-Level-Keys werden als Modul-Definitionen per Object-Merge eingefügt - All other top-level keys are inserted as module definitions via object merge
- Merge wird nur bei Änderungen ausgeführt (Timestamp-Vergleich) - The merge runs only on changes (timestamp comparison)
- Bei Fehler: `notify-send` + `logger`, Waybar startet mit System-Config - On error: `notify-send` + `logger`, Waybar starts with the system config
- Generiert `~/.config/waybar/style.css` mit `@import` der System-Styles falls nicht vorhanden - Generates `~/.config/waybar/style.css` with an `@import` of the system styles if not present
- Benötigt `jq` (in PKGBUILD als Dependency) - Requires `jq` (declared as a dependency in the PKGBUILD)
- System-Config muss valides JSON sein (kein JSONC) - 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) ## System Health Check (moonarch-doctor / moondoc)
Diagnose-Script das den Systemzustand gegen moonarch-Defaults prüft: Diagnostic script that checks the system state against moonarch defaults:
- Pakete (official.txt + aur.txt installiert? Orphans?) - Packages (official.txt + aur.txt installed? Orphans?)
- System-Services (NetworkManager, bluetooth, greetd, ufw, auto-cpufreq, etc.) - System services (NetworkManager, bluetooth, greetd, ufw, auto-cpufreq, etc.)
- User-Services (kanshi, stasis, cliphist-text, cliphist-image) - User services (kanshi, wlsunset, stasis, walker, nautilus, cliphist-text, cliphist-image)
- Config-Dateien (SHA256-Vergleich deployed vs. moonarch-Default) - Config files (SHA256 comparison deployed vs. moonarch default)
- Helper-Scripts + Symlinks (moonup, moondoc) - Helper scripts + symlinks (moonup, moondoc)
- System-Config (UFW, Pacman/Paru Repos, Default Shell) - System config (UFW, pacman/paru repos, default shell)
- Verzeichnisse + Permissions - Directories + permissions
## Konventionen ## Fontconfig Defaults
- Paketlisten sind einfache Textdateien, ein Paket pro Zeile, Kommentare mit `#` System-wide generic-family defaults via `defaults/etc/fonts/conf.d/65-moonarch-fonts.conf` (owned by moonarch-git):
- Shell-Scripts müssen POSIX-kompatibel oder explizit bash/zsh sein - `sans-serif` → UbuntuSans Nerd Font, `monospace` → UbuntuSansMono Nerd Font
- Alle Pfade im archinstall-Config relativ zum Installationsziel - 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
+77 -9
View File
@@ -1,5 +1,73 @@
# Decisions # 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 ## 2026-04-19 moonup i18n: reuse pacman gettext catalog + inline fallback
- **Who**: Dominik, ClaudeCode - **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. - **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.
@@ -37,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. - **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 ## 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. - **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. - **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. - **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 ## 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. - **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. - **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. - **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 ## 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. - **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. - **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. - **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 ## 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 - **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 - **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. - **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 ## 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 - **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 - **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. - **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 ## 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 - **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) - **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` - **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 ## 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. - **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. - **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/`. - **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 ## 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). - **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. - **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. - **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 ## 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. - **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. - **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. - **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. | | **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. | | **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. | | **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. | | **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). | | **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. | | **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, post-install.sh handles the remaining ~95 packages, all configs, themes, services,
and user setup. 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 ### Update
```bash ```bash
@@ -114,9 +85,8 @@ packages/
aur.txt AUR packages (~20), one per line aur.txt AUR packages (~20), one per line
scripts/ 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) 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-update Interactive updater (deployed to /usr/bin/ as moonup)
moonarch-doctor System health checker (deployed to /usr/bin/ as moondoc) 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) | | kanshi | Dynamic display configuration (auto-switch output profiles on hotplug) |
| nautilus | File manager preload (faster first launch) | | nautilus | File manager preload (faster first launch) |
| stasis | Idle manager (dimming, DPMS, lock, suspend on AC/battery plans) | | 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) | | walker | Walker application launcher (GTK4 service mode for instant startup) |
## Moonarch Ecosystem ## Moonarch Ecosystem
+2 -2
View File
@@ -1,10 +1,10 @@
FROM archlinux:base-devel FROM archlinux:base-devel
RUN pacman -Sy --noconfirm git curl && pacman -Scc --noconfirm 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 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 - \ RUN echo "a05b2103a7cc5617197da214eaa06a1055362f21f9f475eb7fbacb8344d86cf8 /usr/local/bin/act_runner" | sha256sum -c - \
&& chmod +x /usr/local/bin/act_runner && 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 RUN mkdir -p /data && chown builder:builder /data
USER builder USER builder
ENV HOME=/home/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 #!/usr/bin/bash
# ABOUTME: Toggles battery conservation mode between 80% and 100% charge limit. # 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" 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 CONSERVATION_LIMIT=80
[[ -f "$THRESHOLD_FILE" ]] || exit 1 [[ -f "$THRESHOLD_FILE" ]] || exit 1
@@ -18,12 +16,7 @@ else
NEW="$CONSERVATION_LIMIT" NEW="$CONSERVATION_LIMIT"
fi fi
# Apply immediately pkexec /usr/bin/moonarch-batsaver-apply "$NEW" || exit 1
echo "$NEW" > "$THRESHOLD_FILE" || exit 1
# Persist for next boot
mkdir -p "$STATE_DIR"
echo "$NEW" > "$STATE_FILE" || exit 1
# Signal Waybar to refresh the batsaver module (SIGRTMIN+9) # Signal Waybar to refresh the batsaver module (SIGRTMIN+9)
pkill -RTMIN+9 waybar pkill -RTMIN+9 waybar
+5 -1
View File
@@ -5,7 +5,11 @@
# choose audio sink via rofi # choose audio sink via rofi
# changes default sink and moves all streams to that sink # 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" && pactl set-default-sink "$sink" &&
for input in $(pactl list sink-inputs short | awk '{print $1}'); do 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. # Requires nm-applet (or another NM secret agent) for interactive auth.
function connect_vpn() { function connect_vpn() {
local connection="$1" local connection="$1"
nmcli connection up "$connection" nmcli connection up id "$connection"
} }
# Disconnect a VPN. # Disconnect a VPN.
function disconnect_vpn() { function disconnect_vpn() {
local connection="$1" local connection="$1"
nmcli connection down "$connection" nmcli connection down id "$connection"
} }
# Toggle the VPN connection based on its current state. # Toggle the VPN connection based on its current state.
+3 -1
View File
@@ -51,7 +51,9 @@ if [[ -f "$USERCONFIG" ]]; then
if [[ ! -f "$OUTPUT" ]] || if [[ ! -f "$OUTPUT" ]] ||
[[ "$USERCONFIG" -nt "$OUTPUT" ]] || [[ "$USERCONFIG" -nt "$OUTPUT" ]] ||
[[ "$SYSTEM_CONFIG" -nt "$OUTPUT" ]]; then [[ "$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 fi
bootstrap_style bootstrap_style
fi fi
+3 -3
View File
@@ -1,8 +1,8 @@
#!/usr/bin/bash #!/usr/bin/bash
# ABOUTME: Waybar-Modul das den CPU-Governor als JSON ausgibt. # ABOUTME: Waybar module that outputs the CPU governor as JSON.
# ABOUTME: Wird von der Waybar custom/cpugov Config referenziert. # 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 case $CPU_GOV in
performance) performance)
+2 -2
View File
@@ -1,6 +1,6 @@
#!/usr/bin/bash #!/usr/bin/bash
# ABOUTME: Waybar-Modul das die GPU-Auslastung als JSON ausgibt. # ABOUTME: Waybar module that outputs GPU utilization as JSON.
# ABOUTME: Wird von der Waybar custom/gpu-usage Config referenziert. # 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=$(cat /sys/class/hwmon/hwmon*/device/gpu_busy_percent 2>/dev/null | head -1 || echo "0")
GPU_STAT="${GPU_STAT:-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] [appearance]
background = "/usr/share/moonarch/wallpaper.jpg" background = "/usr/share/moonarch/wallpaper.jpg"
cursor-theme = "Sweet-cursors"
cursor-size = 24
[behavior] [behavior]
# show_user_list = true # 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"
@@ -1,12 +0,0 @@
// ABOUTME: Allow the greeter user to reboot and power off without authentication.
// ABOUTME: Required because greetd's greeter session is inactive in logind.
polkit.addRule(function(action, subject) {
if (subject.user === "greeter" &&
(action.id === "org.freedesktop.login1.reboot" ||
action.id === "org.freedesktop.login1.reboot-multiple-sessions" ||
action.id === "org.freedesktop.login1.power-off" ||
action.id === "org.freedesktop.login1.power-off-multiple-sessions")) {
return polkit.Result.YES;
}
});
@@ -9,7 +9,15 @@ ConditionPathExists=/var/lib/moonarch/batsaver-threshold
[Service] [Service]
Type=oneshot 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] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
@@ -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: Moonarch default zsh configuration with Catppuccin-themed prompt.
# ABOUTME: Sources user overrides from ~/.zshrc.d/ and ~/.zshrc.local
# --- History --- # --- History ---
HISTFILE=~/.histfile HISTFILE=~/.histfile
@@ -30,26 +29,25 @@ add-zsh-hook preexec _preexec_title
# --- Prompt (Catppuccin Mocha) --- # --- Prompt (Catppuccin Mocha) ---
parse_git_branch() { parse_git_branch() {
local branch="" # Gate on cheap check first — spawning git in every non-repo directory on every
branch=$(git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/') # prompt render costs 20-80ms per prompt. Pattern-match the status output with
local git_status=$(git status --porcelain 2>/dev/null) # zsh glob matching instead of piping to grep for three subshell-spawning checks.
local color=green git rev-parse --git-dir &>/dev/null || return
if echo "$git_status" | grep -q "^ M"; then local branch="" git_status="" color=green flags=""
color=yellow branch=$(git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD 2>/dev/null)
branch="${branch}*" git_status=$(git status --porcelain 2>/dev/null)
if [[ "$git_status" == *$'\n M '* || "$git_status" == " M "* || "$git_status" == *$'\nM'* ]]; then
color=yellow; flags+="*"
fi fi
if echo "$git_status" | grep -qE "^ A|^\?\?"; then if [[ "$git_status" == *$'\nA '* || "$git_status" == "A "* || "$git_status" == *'??'* ]]; then
color=yellow color=yellow; flags+="+"
branch="${branch}+"
fi fi
if echo "$git_status" | grep -q "^ D"; then if [[ "$git_status" == *$'\n D '* || "$git_status" == " D "* ]]; then
color=yellow color=yellow; flags+="-"
branch="${branch}-"
fi fi
if [[ -n "$branch" ]]; then if [[ -n "$branch" ]]; then
branch=[%F{${color}}${branch}%F{reset}] echo " [%F{${color}}${branch}${flags}%F{reset}]"
fi fi
echo " $branch"
} }
precmd() { precmd() {
@@ -141,14 +139,3 @@ export XDG_SESSION_TYPE="wayland"
export EDITOR="nvim" export EDITOR="nvim"
export SUDO_EDITOR="nvim" export SUDO_EDITOR="nvim"
export MOZ_ENABLE_WAYLAND="1" 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] [Settings]
gtk-theme-name=Colloid-Grey-Dark-Catppuccin gtk-theme-name=Colloid-Grey-Dark-Catppuccin
gtk-icon-theme-name=Colloid-Grey-Catppuccin-Dark 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 gtk-application-prefer-dark-theme=1
+5
View File
@@ -116,6 +116,11 @@ window-rule {
open-floating true open-floating true
} }
window-rule {
match app-id=r#"^mpv$"#
open-floating true
}
window-rule { window-rule {
geometry-corner-radius 4 geometry-corner-radius 4
clip-to-geometry true clip-to-geometry true
+8 -2
View File
@@ -5,9 +5,15 @@
@description "Idle management for Moonarch (Niri + moonlock)" @description "Idle management for Moonarch (Niri + moonlock)"
default: 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 monitor_media true
ignore_remote_media true ignore_remote_media false
# App/process inhibit patterns (apps that don't use D-Bus idle-inhibit) # App/process inhibit patterns (apps that don't use D-Bus idle-inhibit)
inhibit_apps [ inhibit_apps [
+2 -2
View File
@@ -217,7 +217,7 @@
}, },
"exec": "moonarch-waybar-updates", "exec": "moonarch-waybar-updates",
"interval": 60, "interval": 60,
"on-click": "foot moonarch-update" "on-click": "foot env MOONUP_WAIT=1 moonarch-update"
}, },
"custom/notification": { "custom/notification": {
"tooltip": true, "tooltip": true,
@@ -319,7 +319,7 @@
"custom/gpu-usage": { "custom/gpu-usage": {
"exec": "moonarch-waybar-gpustat", "exec": "moonarch-waybar-gpustat",
"return-type": "json", "return-type": "json",
"restart-interval": 10 "interval": 60
}, },
"battery": { "battery": {
"bat": "BAT0", "bat": "BAT0",
+1 -1
View File
@@ -3,7 +3,7 @@
* { * {
border: none; border: none;
font-family: "Ubuntu Nerd Font", sans-serif; font-family: "UbuntuSans Nerd Font", sans-serif;
font-size: 13px; font-size: 13px;
color: alpha(@theme_text_color, 0.8); color: alpha(@theme_text_color, 0.8);
} }
+6
View File
@@ -11,6 +11,7 @@ colloid-catppuccin-theme-git
otf-openmoji otf-openmoji
# Niri / Wayland Extras # Niri / Wayland Extras
walker-bin
elephant-desktopapplications-bin elephant-desktopapplications-bin
elephant-clipboard-bin elephant-clipboard-bin
elephant-bluetooth-bin elephant-bluetooth-bin
@@ -29,6 +30,11 @@ wl-color-picker
blueberry blueberry
waterfox-bin waterfox-bin
# Media
mpv-modernz-git
mpv-thumbfast-git
# System & Tools # System & Tools
auto-cpufreq auto-cpufreq
stasis stasis
wayland-pipewire-idle-inhibit
-4
View File
@@ -91,11 +91,7 @@ viewnior
# Development # Development
git git
glab
go
neovim neovim
npm
rustup
# System # System
fwupd fwupd
+1 -6
View File
@@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# ABOUTME: Shared helper functions and constants for Moonarch scripts. # 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. # Path constants — BASH_SOURCE[1] resolves to the calling script, not lib.sh itself.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[1]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[1]}")" && pwd)"
@@ -23,11 +23,6 @@ read_packages() {
grep -v '^\s*#' "$1" | grep -v '^\s*$' grep -v '^\s*#' "$1" | grep -v '^\s*$'
} }
confirm() {
read -r -p "$1 [y/N] " response
[[ "$response" =~ ^[yY]$ ]]
}
# --- Prerequisite checks --- # --- Prerequisite checks ---
check_not_root() { check_not_root() {
+23 -40
View File
@@ -109,8 +109,11 @@ section "Packages"
OFFICIAL="/usr/share/moonarch/official.txt" OFFICIAL="/usr/share/moonarch/official.txt"
AUR="/usr/share/moonarch/aur.txt" AUR="/usr/share/moonarch/aur.txt"
if [[ -f "$OFFICIAL" ]]; then # 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) INSTALLED=$(pacman -Qq 2>/dev/null)
if [[ -f "$OFFICIAL" ]]; then
MISSING_OFFICIAL=() MISSING_OFFICIAL=()
while IFS= read -r pkg; do while IFS= read -r pkg; do
[[ "$pkg" =~ ^[[:space:]]*# ]] && continue [[ "$pkg" =~ ^[[:space:]]*# ]] && continue
@@ -177,10 +180,19 @@ section "User Services"
if [[ $EUID -eq 0 ]]; then if [[ $EUID -eq 0 ]]; then
warn "Running as root — skipping user service checks" warn "Running as root — skipping user service checks"
else elif pacman -Qq moonarch-git &>/dev/null; then
for svc in kanshi wlsunset stasis cliphist-text cliphist-image; do 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" check_user_service "$svc"
done done
else
warn "moonarch-git not installed — skipping user service checks"
fi fi
# --- 4. Config Files --- # --- 4. Config Files ---
@@ -197,34 +209,15 @@ 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/greetd/niri-greeter.kdl" "$SRC/greetd/niri-greeter.kdl"
check_config_match "/etc/moongreet/moongreet.toml" "$SRC/moongreet/moongreet.toml" 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 --- # --- 5. Helper Scripts ---
section "Helper Scripts" section "Helper Scripts"
EXPECTED_SCRIPTS=( if pacman -Qq moonarch-git &>/dev/null; then
moonarch-batsaver-toggle EXPECTED_SCRIPTS=()
moonarch-btnote while IFS= read -r script; do
moonarch-capsnote EXPECTED_SCRIPTS+=("$(basename "$script")")
moonarch-cpugov done < <(pacman -Qql moonarch-git | grep -E '^/usr/bin/moonarch-[^/]+$')
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
)
MISSING_SCRIPTS=() MISSING_SCRIPTS=()
for script in "${EXPECTED_SCRIPTS[@]}"; do for script in "${EXPECTED_SCRIPTS[@]}"; do
@@ -238,6 +231,9 @@ if [[ ${#MISSING_SCRIPTS[@]} -eq 0 ]]; then
else else
fail "Missing scripts: ${MISSING_SCRIPTS[*]}" fail "Missing scripts: ${MISSING_SCRIPTS[*]}"
fi fi
else
warn "moonarch-git not installed — skipping helper script checks"
fi
# Symlinks # Symlinks
for link in moonup moondoc; do for link in moonup moondoc; do
@@ -270,13 +266,6 @@ else
fail "Pacman [moonarch] repo missing from /etc/pacman.conf" fail "Pacman [moonarch] repo missing from /etc/pacman.conf"
fi 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 # Default shell
USER_SHELL=$(getent passwd "$USER" | cut -d: -f7) USER_SHELL=$(getent passwd "$USER" | cut -d: -f7)
if [[ "$USER_SHELL" == */zsh ]]; then if [[ "$USER_SHELL" == */zsh ]]; then
@@ -301,12 +290,6 @@ else
warn "/var/lib/moonarch/ missing (created on first battery toggle)" warn "/var/lib/moonarch/ missing (created on first battery toggle)"
fi fi
if [[ -d /usr/share/moonarch ]]; then
pass "/usr/share/moonarch/"
else
fail "/usr/share/moonarch/ missing (moonarch-git not installed?)"
fi
# --- Summary --- # --- Summary ---
echo echo
+26 -14
View File
@@ -4,13 +4,6 @@
set -euo pipefail set -euo pipefail
# --- 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]")
_t() { _t() {
# _t "english" "deutsch" — picks by $LANG # _t "english" "deutsch" — picks by $LANG
case "${LANG%%.*}" in case "${LANG%%.*}" in
@@ -19,6 +12,29 @@ _t() {
esac 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 --- # --- Helper functions ---
log() { log() {
@@ -34,7 +50,9 @@ read_packages() {
} }
confirm() { confirm() {
read -r -p ":: $1 $YN " response # 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]$ ]] [[ -z "$response" || "$response" =~ ^[yYjJ]$ ]]
} }
@@ -120,9 +138,3 @@ log ""
log "============================================" log "============================================"
log " $(_t "Moonarch update complete!" "Moonarch-Aktualisierung abgeschlossen!")" log " $(_t "Moonarch update complete!" "Moonarch-Aktualisierung abgeschlossen!")"
log "============================================" 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 "$(_t "Press any key to close..." "Beliebige Taste drücken zum Schließen …")"
fi
+39 -28
View File
@@ -14,8 +14,16 @@ check_pacman
# --- Install paru (AUR Helper) --- # --- Install paru (AUR Helper) ---
if ! command -v paru &>/dev/null; then if ! command -v paru &>/dev/null; then
log "Installing paru build dependencies..."
sudo pacman -S --needed --noconfirm base-devel rust
log "Installing paru..." 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 else
log "paru already installed." log "paru already installed."
fi fi
@@ -60,31 +68,23 @@ fi
rm -f "$KEY_FILE" rm -f "$KEY_FILE"
trap - EXIT trap - EXIT
# --- Set up Moonarch custom paru repo (needed for first install bootstrap) --- # --- Install moonarch-git from the Arch registry ---
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) ---
log "Installing moonarch-git package..." log "Installing moonarch-git package..."
sudo pacman -Sy --noconfirm
paru -S --needed --noconfirm moonarch-git 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 --- # --- User-level GTK4 symlinks for libadwaita apps ---
THEME_NAME="Colloid-Grey-Dark-Catppuccin" 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 if [[ ! -f "$HOME/.zshrc" ]]; then
log "No ~/.zshrc found — sourcing Moonarch defaults." log "No ~/.zshrc found — sourcing Moonarch defaults."
mkdir -p "$HOME/.zshrc.d" echo "source /etc/zsh/zshrc.moonarch" > "$HOME/.zshrc"
echo "# Load Moonarch defaults, add custom overrides in ~/.zshrc.d/ or below" > "$HOME/.zshrc" fi
echo "source /etc/zsh/zshrc.moonarch" >> "$HOME/.zshrc"
# --- 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 fi
# --- Enable systemd user services --- # --- Enable systemd user services ---
@@ -124,11 +133,14 @@ fi
log "Enabling systemd user services..." log "Enabling systemd user services..."
USER_SERVICES=( USER_SERVICES=(
"kanshi" "kanshi"
"wlsunset"
"stasis" "stasis"
"wayland-pipewire-idle-inhibit"
"cliphist-text" "cliphist-text"
"cliphist-image" "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 for service in "${USER_SERVICES[@]}"; do
if systemctl --user cat "${service}.service" &>/dev/null; then if systemctl --user cat "${service}.service" &>/dev/null; then
@@ -199,6 +211,5 @@ log ""
log "Next steps:" log "Next steps:"
log " 1. Reboot" log " 1. Reboot"
log " 2. Place wallpapers in ~/Pictures/Wallpaper/" log " 2. Place wallpapers in ~/Pictures/Wallpaper/"
log " 3. rustup default stable" log " 3. User overrides in ~/.config/"
log " 4. User overrides in ~/.config/ or ~/.zshrc.d/"
log "" 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