Compare commits

..

13 Commits

Author SHA1 Message Date
nevaforget 18357384e6 Add Moonarch package registry setup with signed packages
Update PKGBUILD version / update-pkgver (push) Successful in 2s
Import Gitea Arch registry key dynamically and configure pacman
with SigLevel = Required DatabaseOptional. Key ID is extracted
from the downloaded key file to avoid hardcoding.
2026-04-01 18:07:07 +02:00
nevaforget 4d4b2221aa ci: test with updated registry token
Update PKGBUILD version / update-pkgver (push) Successful in 1s
2026-04-01 17:58:35 +02:00
nevaforget 792f6b9931 ci: trigger build with debug logging
Update PKGBUILD version / update-pkgver (push) Successful in 2s
2026-04-01 17:55:58 +02:00
nevaforget 6dd98ed25d ci: trigger pkgver bump
Update PKGBUILD version / update-pkgver (push) Successful in 2s
2026-04-01 17:49:30 +02:00
nevaforget 24df4b3994 Add custom Arch-based act_runner image
Update PKGBUILD version / update-pkgver (push) Successful in 1s
Runner based on archlinux:base-devel with git, curl, makepkg.
Runs as non-root builder user so makepkg works natively without
permission workarounds. Registration data stored in /data volume.
2026-04-01 17:45:39 +02:00
nevaforget b5e9bce9a1 ci: trigger pkgver bump
Update PKGBUILD version / update-pkgver (push) Successful in 1s
2026-04-01 16:55:07 +02:00
nevaforget e55bab4816 ci: trigger build-and-publish test
Update PKGBUILD version / update-pkgver (push) Successful in 2s
2026-04-01 16:44:25 +02:00
nevaforget aba7ffede1 Add custom Arch-based act_runner image, revert workflow workaround
Update PKGBUILD version / update-pkgver (push) Successful in 2s
The runner image is now built on archlinux:base-devel with git,
curl, makepkg and a non-root builder user baked in. This removes
the need for per-workflow pacman installs and enables host mode.
2026-04-01 16:42:53 +02:00
nevaforget e8eb383eac Fix CI: install git in Arch container for update-pkgver
Update PKGBUILD version / update-pkgver (push) Failing after 5s
The runner now uses docker mode with archlinux:base-devel which
does not include git by default.
2026-04-01 16:00:31 +02:00
nevaforget 92aa2f9190 ci: test with info endpoint enabled
Update PKGBUILD version / update-pkgver (push) Failing after 2s
2026-04-01 15:59:32 +02:00
nevaforget 5d1a7b61e4 ci: test container networking
Update PKGBUILD version / update-pkgver (push) Failing after 1s
2026-04-01 15:57:44 +02:00
nevaforget 0a709cedb6 ci: test arch container build
Update PKGBUILD version / update-pkgver (push) Failing after 28s
2026-04-01 15:55:06 +02:00
nevaforget 45165204a6 ci: trigger pkgver bump
Update PKGBUILD version / update-pkgver (push) Successful in 2s
2026-04-01 15:44:41 +02:00
67 changed files with 1185 additions and 1467 deletions
+3 -22
View File
@@ -7,15 +7,6 @@ on:
push:
branches:
- main
paths:
# Only files that the moonarch-git PKGBUILD actually packages.
# README.md, DECISIONS.md, scripts/post-install.sh, scripts/lib.sh,
# CI workflow edits, etc. don't change the built package and must
# not trigger a rebuild.
- 'defaults/**'
- 'packages/**'
- 'scripts/moonarch-update'
- 'scripts/moonarch-doctor'
jobs:
update-pkgver:
@@ -23,18 +14,16 @@ jobs:
steps:
- name: Checkout source repo
run: |
git clone --bare https://gitea.moonarch.de/nevaforget/moonarch.git source.git
git clone --bare http://gitea:3000/nevaforget/moonarch.git source.git
cd source.git
PKGVER=$(printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)")
echo "New pkgver: $PKGVER"
echo "$PKGVER" > /tmp/pkgver
- name: Update PKGBUILD
env:
PKGBUILD_TOKEN: ${{ secrets.PKGBUILD_TOKEN }}
run: |
PKGVER=$(cat /tmp/pkgver)
git clone https://gitea.moonarch.de/nevaforget/moonarch-pkgbuilds.git pkgbuilds
git clone http://gitea:3000/nevaforget/moonarch-pkgbuilds.git pkgbuilds
cd pkgbuilds
OLD_VER=$(grep '^pkgver=' moonarch-git/PKGBUILD | cut -d= -f2)
@@ -50,12 +39,4 @@ jobs:
git config user.email "gitea@moonarch.de"
git add moonarch-git/PKGBUILD
git commit -m "chore(moonarch-git): bump pkgver to $PKGVER"
# 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
git -c http.extraHeader="Authorization: token ${{ secrets.PKGBUILD_TOKEN }}" push
+12 -86
View File
@@ -1,94 +1,20 @@
# Moonarch
Reproducible Arch Linux setup based on archinstall + post-install automation.
Reproduzierbares Arch-Linux-Setup basierend auf archinstall + Post-Install-Automatisierung.
## Project Structure
## Projektstruktur
- `config/` — archinstall configuration (incl. custom-commands that clone the repo to /opt/moonarch, root-owned)
- `scripts/`post-install and helper scripts
- `packages/`package lists (official + AUR), maintained separately
- `defaults/` — XDG configs, shell config, helper binaries, systemd services, udev rules, greetd/moongreet config, wallpaper
- `config/` — archinstall-Konfiguration (inkl. custom-commands die das Repo nach /opt/moonarch klonen, root-owned)
- `scripts/`Post-Install- und Helper-Scripts
- `packages/`Paketlisten (offiziell + AUR), getrennt gepflegt
- `defaults/` — XDG-Configs, Shell-Config, Helper-Binaries, greetd/moongreet-Config, Wallpaper
## Battery Conservation Mode
## Konventionen
Laptops with `charge_control_end_threshold` support (ThinkPad, Framework, etc.) get a Waybar toggle:
- Clicking the battery module toggles the charge limit between 80% and 100%
- When conservation is active, a ♥ icon appears next to the battery indicator
- State is persisted in `/var/lib/moonarch/batsaver-threshold` and restored on boot via a systemd service (`moonarch-batsaver-restore`)
- Toggle flow: `moonarch-batsaver-toggle` (user script) reads sysfs, decides 80↔100, calls `pkexec /usr/bin/moonarch-batsaver-apply $NEW` for the privileged sysfs+state write step. Standard pkexec prompt (password once per session cache)
- On desktops without battery support the feature is hidden entirely
- Paketlisten sind einfache Textdateien, ein Paket pro Zeile, Kommentare mit `#`
- Shell-Scripts müssen POSIX-kompatibel oder explizit bash/zsh sein
- Alle Pfade im archinstall-Config relativ zum Installationsziel
## Nightlight (Blue Light Filter)
## Ich bin Apollo
Waybar toggle for wlsunset (Wayland-native blue light filter), persistent state via systemd:
- `wlsunset.service` (systemd user service) with `After=kanshi.service` — starts only once all outputs are configured
- **Default OFF** — fresh installs start without the filter. The PKGBUILD deliberately creates NO symlink for `wlsunset` in `/etc/systemd/user/graphical-session.target.wants/`, and post-install.sh does not enable the service.
- Clicking the nightlight module in `group/brightness` toggles wlsunset on/off (`enable --now` / `disable --now`)
- State survives reboots (user-scope symlink in `~/.config/systemd/user/...wants/`)
- Active state shows 󰌵 in Catppuccin Yellow, inactive 󰌶 in the default text color
- Signal SIGRTMIN+11 for immediate Waybar refresh
- Scripts: `moonarch-nightlight` (toggle), `moonarch-waybar-nightlight` (status JSON)
- **Important**: Never create a global-scope symlink at `/etc/systemd/user/...wants/wlsunset.service` — it overrides any user `disable` and makes the filter effectively impossible to turn off.
## Waybar Config Merger (moonarch-waybar)
Waybar is started via `moonarch-waybar` (not directly). The wrapper merges an optional user config (`~/.config/waybar/userconfig`) with the system config (`/etc/xdg/waybar/config`):
- `prepend`/`append` keys in the userconfig extend the `modules-left`/`modules-center`/`modules-right` arrays
- All other top-level keys are inserted as module definitions via object merge
- The merge runs only on changes (timestamp comparison)
- On error: `notify-send` + `logger`, Waybar starts with the system config
- Generates `~/.config/waybar/style.css` with an `@import` of the system styles if not present
- Requires `jq` (declared as a dependency in the PKGBUILD)
- The system config must be valid JSON (no JSONC)
## mpv + ModernZ OSC
The video player is `mpv` with [ModernZ](https://github.com/Samillion/ModernZ) as the OSC, thumbnails via thumbfast:
- `mpv-modernz-git` provides `modernz.lua` + font + default config to `/etc/mpv/`
- `mpv-thumbfast-git` provides `thumbfast.lua` to `/etc/mpv/scripts/` (auto-detected by ModernZ)
- `defaults/etc/mpv/mpv.conf` is installed directly to `/etc/mpv/mpv.conf` by moonarch-git (owned)
- Stock OSC + title bar disabled, `autofit-larger=80%x80%` caps oversized windows
- ModernZ overrides via `script-opts-append` in mpv.conf: orange accent → Catppuccin Lavender (`#b4befe`), OSC scale 0.75, `window_title_font_size=18`, `ontop_button=no`
- **Important**: mpv treats `#` as a mid-line comment; hex colors must be quoted: `script-opts-append="modernz-seekbarfg_color=#b4befe"` (not `\#`, which only escapes and swallows the rest)
- Niri opens mpv floating (`window-rule` in `defaults/xdg/niri/config.kdl`)
## System Health Check (moonarch-doctor / moondoc)
Diagnostic script that checks the system state against moonarch defaults:
- Packages (official.txt + aur.txt installed? Orphans?)
- System services (NetworkManager, bluetooth, greetd, ufw, auto-cpufreq, etc.)
- User services (kanshi, wlsunset, stasis, walker, nautilus, cliphist-text, cliphist-image)
- Config files (SHA256 comparison deployed vs. moonarch default)
- Helper scripts + symlinks (moonup, moondoc)
- System config (UFW, pacman/paru repos, default shell)
- Directories + permissions
## Fontconfig Defaults
System-wide generic-family defaults via `defaults/etc/fonts/conf.d/65-moonarch-fonts.conf` (owned by moonarch-git):
- `sans-serif` → UbuntuSans Nerd Font, `monospace` → UbuntuSansMono Nerd Font
- Number **65**: loads after `60-latin.conf`, so the moonarch prefs beat the stock default (Noto/DejaVu). `local.conf` (at 51 via `51-local.conf`) loads too early and is overridden by 60-latin — therefore **do not** use it (it is also reserved for local user overrides).
- Aliases need `binding="strong"` — a weak `<prefer>` (fontconfig default) ranks behind the effective generic fallback and does not take effect.
- Only applies to apps that use generic families (e.g. Firefox web fallback). moonarch apps (Waybar, foot, GTK, walker, swaync) set the font explicitly.
## Browser Idle-Inhibit (xdg-desktop-portal)
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
Benannt nach dem Programm, das Menschen zum Mond gebracht hat — passend für ein Projekt namens Moonarch.
+8 -118
View File
@@ -1,160 +1,50 @@
# Decisions
## 2026-06-08 Fontconfig generic-family defaults via owned conf.d (65)
- **Who**: Dominik, ClaudeCode
- **Why**: Waybar rendered in Hack instead of Ubuntu after the Nerd Fonts "Ubuntu"→"UbuntuSans" rename (Canonical rebrand). Waybar's style.css referenced the now-dead family "Ubuntu Nerd Font"; fontconfig token-matched it to "Hack Nerd Font". An unowned hand-written `/etc/fonts/local.conf` additionally pinned sans-serif/monospace to Hack — a stale relic.
- **Tradeoffs**: `local.conf` (loaded at 51 via `51-local.conf`) is structurally too early — `60-latin.conf` prepends Noto/DejaVu afterwards and wins, so its sans-serif pin never took effect. local.conf is also reserved for local admin overrides, and packaging it as owned would collide with pre-existing unowned files on pacman update. A conf.d file at 65 loads after 60-latin (convention: 6069 = generic→family) and wins cleanly without conflict.
- **How**: New owned `defaults/etc/fonts/conf.d/65-moonarch-fonts.conf` maps sans-serif→UbuntuSans Nerd Font, monospace→UbuntuSansMono Nerd Font; installed by moonarch-git PKGBUILD. Stale `/etc/fonts/local.conf` removed. Waybar `style.css` font-family corrected "Ubuntu Nerd Font"→"UbuntuSans Nerd Font" (the bar wants the explicit proportional font, not a generic). The aliases need `binding="strong"` — verified that a weak `<prefer>` (fontconfig's default for `<alias>`) ranks behind the system's effective generic fallback and does not take effect; strong is required for it to apply.
## 2026-05-04 Nightlight default OFF, no global enablement
- **Who**: Dominik, ClaudeCode
- **Why**: Filter survived reboots even after the user toggled it off. Root cause: `moonarch-pkgbuilds/moonarch-git/PKGBUILD` looped over every user service in `defaults/etc/systemd/user/*.service` and dropped a WantedBy symlink into `/etc/systemd/user/graphical-session.target.wants/`. That path is global scope. `moonarch-nightlight` runs `systemctl --user disable wlsunset`, which can only remove user-scope symlinks under `~/.config/`. Systemd's own warning during disable spelled it out: "The following unit files have been enabled in global scope. This means they will still be started automatically after a successful disablement in user scope." Verified empirically — `is-enabled` stayed `enabled`, root symlink untouched. Additionally, `scripts/post-install.sh` enabled `wlsunset` by default in its `USER_SERVICES` array, so even without the global symlink the filter would default ON.
- **Tradeoffs**: Three options weighed. (1) Default OFF + user-scope toggle — minimal change, fresh installs start without filter, toggle creates `~/.config/.../wants/` symlink that user-disable can actually remove. (2) Default ON + user-scope toggle — same plumbing, post-install enables in user scope; filter on by default but disable persists. (3) Status-file gate inside the unit — service stays enabled, ExecStartPre checks a file and exits when off. Picked (1): no behavioral default imposed on fresh installs, no extra plumbing, the toggle stays the single source of truth. Could have moved enablement to a per-user `systemctl --user --global enable` from the .install hook, but that fights the "this is a UI toggle" framing.
- **How**: `moonarch-pkgbuilds/moonarch-git/PKGBUILD` — symlink loop now skips a `skip_enable` list (currently `wlsunset.service`); skipped services are still installed under `/etc/systemd/user/` but not wanted by `graphical-session.target` at the global level. `moonarch-pkgbuilds/moonarch-git/moonarch.install``pre_upgrade()` deletes any pre-existing `/etc/systemd/user/graphical-session.target.wants/wlsunset.service` to clean up systems that received the old packaging. `moonarch/scripts/post-install.sh``wlsunset` removed from `USER_SERVICES`; comment explains why. `moonarch/CLAUDE.md` — Nightlight section reflects "Default OFF" and the global-scope-symlink hazard.
## 2026-05-04 Battery threshold permissions: udev rule → pkexec helper
- **Who**: Dominik, ClaudeCode
- **Why**: The wheel-write-via-udev approach for `/sys/class/power_supply/BAT0/charge_control_end_threshold` had been broken since 2026-04-08 (commit `ac2b210`, "audit remediation Q-W3"). That commit added `ACTION=="add"` to `90-moonarch-battery.rules` to "avoid firing on every battery event" — but that filter is precisely what the rule needs not to have. On Lenovo, the threshold attribute does not exist yet at the `add` event (the driver creates it slightly later); the rule fires, `chmod` fails silently because `2>/dev/null` swallows the error, and permissions are never set. The unfiltered original rule worked by accident: `add` failed silently as well, but a subsequent `change` event on the same device caught the now-existing attribute and set permissions. After the audit commit, change events stopped re-firing the rule and the toggle was permanently broken — `moonarch-batsaver-toggle` returned `Permission denied`. Verified via journalctl + manual chmod: rule fires for hidpp_battery_0 (visible exit-1 errors), no trace for BAT0; manual `chmod g+w` on BAT0's threshold succeeds (sysfs accepts the change), so the permission model itself works — only the rule path failed.
- **Tradeoffs**: Three approaches considered. (A) Restore the original unfiltered rule — fixes the symptom by accident, leaves the failure mode intact (silent fail at add, retry hopefully at change). (B) Switch to `tmpfiles.d` — Arch Wiki explicitly warns this can run before driver modules load, undefined for sysfs. (C) pkexec helper with polkit-rule — standard pattern (Battery-Health-Charging GNOME extension uses exactly this). Picked C with default Standard-pkexec prompt rather than no-password polkit rule: minor UX cost (password once per pkauth session, ≈5min cache), eliminates the entire sysfs-permission problem class, no privilege-escalation surface from a misvalidated helper. The wheel-can-write-sysfs design was a moonarch-specific deviation from common Linux practice — bringing it in line with the standard root-orientiert helper pattern.
- **How**: `defaults/bin/moonarch-batsaver-apply` (new): privileged helper invoked via pkexec; strictly validates argument (digits only, range 1-100), writes sysfs (idempotent — skips kernel write when value already matches to avoid Lenovo EINVAL on same-value writes), writes state file. `defaults/bin/moonarch-batsaver-toggle` (rewritten): user-side reads current threshold, picks 80↔100, dispatches `pkexec /usr/bin/moonarch-batsaver-apply $NEW`, then signals waybar. `defaults/etc/udev/rules.d/90-moonarch-battery.rules` deleted (and the now-empty `defaults/etc/udev/` parent removed). PKGBUILD: udev install line removed. `moonarch-doctor`: removed the udev-effectiveness check (no longer relevant). `moonarch-batsaver.service` and `moonarch-batsaver-restore` (also new in this commit, extracted from the old inline ExecStart for readability) keep root-owned boot-time restore — no permission concerns there. `CLAUDE.md` Battery-Conservation-Mode section updated to describe the new flow.
## 2026-05-04 Cleanup: remove invented zsh override layer, harden moondoc
- **Who**: Dominik, ClaudeCode
- **Why**: Audit revealed two classes of cruft introduced by earlier ClaudeCode sessions without explicit decision or DECISIONS.md entry. (1) An invented user-override mechanism (`~/.zshrc.d/*.zsh` snippet loop, `~/.zshrc.local` fallback) was wired into `defaults/shell/zshrc` and `scripts/post-install.sh`. Not a zsh convention, not documented, redundant to the user's own `~/.zshrc`. `post-install.sh` created the `~/.zshrc.d` directory unconditionally on every fresh install — leaving an empty directory in every user's home. (2) `moonarch-doctor` had only existence checks (`/etc/zsh/zshrc.moonarch` exists?, `/usr/share/moonarch/` exists?) which are redundant with the package check, and hardcoded service/script lists that drift silently when moonarch-git's payload changes. The udev rule for `charge_control_end_threshold` (battery conservation) had no effectiveness check at all — a broken rule would not show up.
- **Tradeoffs**: Could have left the invented override layer alone (no active harm) but it muddies `defaults/shell/zshrc` and produces empty directories on every fresh install. Could have kept the existence checks (cosmetic noise, no harm) but they create false positives — doctor reports "pass" while the actual mechanism may be broken. Kept the 7 hardcoded `check_config_match` entries for `/etc/xdg/foot/`, `/etc/greetd/`, `/etc/moongreet/` etc. — the source-to-destination mapping is not 1:1 (foot → `/etc/xdg/foot/`, greetd → `/etc/greetd/`, moongreet → `/etc/moongreet/`), so dynamic discovery would need a manifest. Acceptable hardcoding for now.
- **How**: `defaults/shell/zshrc``~/.zshrc.d/*.zsh` source loop and `~/.zshrc.local` fallback removed; second ABOUTME line that referenced them removed. `scripts/post-install.sh` — Zsh-block now writes `~/.zshrc` with only `source /etc/zsh/zshrc.moonarch` (no mkdir, no `~/.zshrc.d` reference); stale "rustup default stable" hint and "User overrides in `~/.zshrc.d/`" hint removed from next-steps. `scripts/lib.sh` — dead `confirm()` (orphaned since transform.sh deletion 2026-04-21) removed. `scripts/moonarch-doctor` — user-services and helper-scripts lists now derived from `pacman -Qql moonarch-git` plus an explicit list of post-install-enabled externals (currently `stasis`); useless existence checks for `/etc/zsh/zshrc.moonarch` and `/usr/share/moonarch/` removed; new udev-effectiveness check for `charge_control_end_threshold` (group=wheel + group-writable). `defaults/bin/moonarch-waybar-cpugov`, `moonarch-waybar-gpustat` — German ABOUTME comments translated to English for consistency.
## 2026-04-24 Stasis: flip `ignore_remote_media` to false for browser video
- **Who**: Dominik, ClaudeCode
- **Why**: Idle was firing on a second machine even while a video was playing in the browser. Original config carried the comment "browser uses D-Bus inhibit" and set `ignore_remote_media true`, deliberately excluding browser MPRIS on the assumption that Firefox/Chromium would keep the session alive via `org.freedesktop.ScreenSaver.Inhibit`. Verified against browser behavior: both browsers only raise that inhibit during **fullscreen** video playback (Firefox also requires `dom.screenwakelock.enabled`, default off on Linux). Windowed playback — the common case, YouTube in a tab — sends no inhibit, so stasis saw zero inhibitors and zero media players and ran the full idle plan to suspend. Upstream example config lists `r"firefox.*"` in `inhibit_apps` for exactly this reason; Moonarch removed it without a working substitute.
- **Tradeoffs**: Three options. (A) Put `firefox`/`chromium` back into `inhibit_apps` — works but inhibits whenever the browser *process* is running, even with zero tabs playing; wrong shape. (B) Tell users to flip `dom.screenwakelock.enabled` per browser — pushes per-user config, fragile across browsers. (C) Let browser MPRIS count as a media player by setting `ignore_remote_media false` — stasis already tracks playback state, so it only inhibits during actual playback. Picked C. Cost: any MPRIS source stasis classifies as "remote" (e.g. a Chromecast bridge) now also inhibits; acceptable — that is usually what a user wants anyway, and the inhibit releases the moment playback stops.
- **How**: `defaults/xdg/stasis/stasis.rune``ignore_remote_media true``false`, comment rewritten to document the fullscreen-only D-Bus behavior. PKGBUILD deploys to `/etc/xdg/stasis/stasis.rune`; existing users still need the 2026-04-22 seed mechanism (or a manual merge) to pick it up in `~/.config/stasis/stasis.rune`.
## 2026-04-22 moonarch-doctor housekeeping: drop stale check, add missing services
- **Who**: Dominik, ClaudeCode
- **Why**: Noticed while running `moondoc` on a healthy system that it reported `Paru [moonarch-pkgbuilds] repo missing from /etc/paru.conf` — a false failure. The paru PKGBUILD-repo mechanism was retired on 2026-04-20 in favor of the registry-only flow, and the `moonarch-git` install hook strips the legacy paru.conf section on upgrade; the doctor script was not updated in lockstep. Audit of the rest of the script surfaced two related gaps: the user-services loop skipped `walker.service` and `nautilus.service`, even though the PKGBUILD ships both in `/etc/systemd/user/` and enables them via `graphical-session.target.wants` symlinks. A silently missing walker or nautilus would not show up in diagnostics.
- **How**: Removed the `[moonarch-pkgbuilds]` check from `scripts/moonarch-doctor`. Added `walker` and `nautilus` to the user-service loop. Updated `CLAUDE.md` user-services listing to match (also filled in the missing `wlsunset`). The `[moonarch]` pacman-repo check stays — that is the path that matters now.
## 2026-04-22 Seed Stasis user config from post-install.sh
- **Who**: Dominik, ClaudeCode
- **Why**: Moonarch shipped `defaults/xdg/stasis/stasis.rune` (deployed to `/etc/xdg/stasis/stasis.rune` by the PKGBUILD) on the assumption that stasis honors the XDG system config hierarchy. It does not. Verified against upstream source (v1.1.0, `src/config/mod.rs:30` + `src/config/bootstrap.rs`): stasis only reads `~/.config/stasis/stasis.rune` (primary) or `/etc/stasis/stasis.rune` (fallback, no `xdg/`). On every start with no user config, `ensure_user_config_exists()` writes its own hardcoded default (laptop/desktop template compiled into the binary) to `~/.config/stasis/stasis.rune`. Net effect: Moonarch's Idle-Manager tuning (AC/battery plans, moonlock integration, inhibit apps, niri DPMS commands) was never active on fresh installs — users got the upstream defaults with `swaylock` as locker.
- **Tradeoffs**: Three options were considered. (A) Service drop-in with `--config /etc/stasis/stasis.rune` — cleanest, upgrades propagate via package, no user-home touching; rejected because it takes stasis out of the standard config path users expect when they want to customize, and requires packaging a drop-in unit. (B) Seed once from post-install.sh into `~/.config/stasis/stasis.rune` — chosen: user-owned file, immediately editable, no magic. Cost: package updates to the template never reach users who already ran post-install; they have to merge manually. (C) Recurring systemd user service that keeps seeding — ruled out, too clever, user edits would race with it. Stasis has no config hierarchy / merging, so "system default + user override" cannot be modeled at all.
- **How**: Added a block to `scripts/post-install.sh` before the user-services-enable step: if `~/.config/stasis/stasis.rune` does not exist and `/etc/xdg/stasis/stasis.rune` does, `install -Dm644` copies the template into the user home. Order is correct — moonarch-git is installed earlier in the script (deploys `/etc/xdg/…`), and stasis.service is only *enabled* (not started) later, so the seed is in place before stasis ever runs. The `/etc/xdg/stasis/` payload stays as the canonical template source inside the package.
## 2026-04-21 post-install.sh pulls aur.txt, rust for paru build, rustup out of official
- **Who**: Dominik, ClaudeCode
- **Why**: Three related gaps uncovered while fixing the paru bootstrap: (1) `moonarch-git` cannot depend on AUR packages, so every AUR package in `aur.txt` (walker-bin, elephant-*-bin, awww's theme, waypaper, stasis, …) was silently never installed by post-install.sh — a fresh install would have a working pacman but no launcher, no idle manager, no theming. (2) `makepkg -si` for `paru` (AUR source build) needs `rust` as makedep; neither archinstall nor post-install.sh installed it, so the restored paru bootstrap would have crashed on rust-less systems. (3) `rustup` sat in `official.txt` "for something we don't remember" — turned out to be a leftover; `rust` suffices for the paru build, and rustup is only needed for dev toolchain management which is a per-user concern, not a Moonarch default.
- **Tradeoffs**: Installing `rust` (~350MB) just to bootstrap paru feels heavy. Alternative `paru-bin` avoids the compile step entirely; rejected because Dominik's documented workflow uses `paru` (source) and consistency with that matters more than ~30s of build time. rustup can be re-added to official.txt if the dev workflow grows that demand.
- **How**: (1) post-install.sh now runs `read_packages "$AUR_PACKAGES" | paru -S --needed` after `paru -S moonarch-git`. (2) `sudo pacman -S base-devel rust` before the paru git-clone. (3) `rustup` removed from `official.txt` (remains in PKGBUILD `optdepends` for discoverability).
## 2026-04-21 Restore AUR bootstrap for paru in post-install.sh
- **Who**: Dominik, ClaudeCode
- **Why**: Commit 0726451 (2026-03-29) replaced the working `git clone https://aur.archlinux.org/paru.git && makepkg -si` bootstrap with `sudo pacman -S paru`, on the (wrong) assumption that paru had landed in `[extra]`. Verified against archlinux.org API: paru and paru-bin are AUR-only, not in any official repo. Fresh installs have been silently broken since — the command fails, the installer aborts before the moonarch registry is configured.
- **Tradeoffs**: Bootstrap builds paru from source, which needs `base-devel` (already pulled by archinstall). Alternative `paru-bin` would skip the compile step; chose `paru` to match the upstream recommendation the user follows. Alternative "bundle paru in the Moonarch registry" would be internally consistent but adds a dependency on a running Gitea during install.
- **How**: Replaced the `pacman -S paru` line in `post-install.sh` with the original bootstrap — `mktemp -d`, `git clone`, `makepkg -si --noconfirm`, `rm -rf`. Wrapped in an EXIT trap so the tempdir gets cleaned up even on failure.
## 2026-04-21 Drop transform.sh and legacy update.sh shim
- **Who**: Dominik, ClaudeCode
- **Why**: transform.sh was added 2026-03-29 to onboard users from existing Arch+Wayland systems, but was never actually used — Moonarch is installed fresh via archinstall. Maintaining it meant duplicated paru/repo/key setup, a second entry point with its own backup/pre-flight logic, and a second reason for the `/opt/moonarch` clone. scripts/update.sh was a deprecation shim from before moonarch-update moved into the moonarch-git package; obsolete now that the package ships moonup/moondoc.
- **Tradeoffs**: Existing Arch+Wayland users now have to install paru, add the `[moonarch]` repo + key manually, then `paru -S moonarch-git`. That's three commands instead of one script — acceptable since nobody actually uses that path. If the need returns, transform can be resurrected from git history.
- **How**: Deleted `scripts/transform.sh` and `scripts/update.sh`. `lib.sh` stays (still sourced by post-install.sh). README "Transform" section removed, project-structure listing trimmed. Fresh-install flow via archinstall + post-install.sh is unchanged.
## 2026-04-20 Registry-only install: drop paru --pkgbuilds from setup scripts
- **Who**: Dominik, ClaudeCode
- **Why**: Two parallel paths for finding moonarch packages (Arch registry via `[moonarch]` in pacman.conf, and paru's PKGBUILD-repo via `[moonarch-pkgbuilds]` in paru.conf) caused ambiguity during debugging: `paru -S moonarch-git` was resolving against the registry's stale DB (zombie r99 entry) while the PKGBUILD source would have had r105. Also: the PKGBUILD-repo path triggered a local build on every client, while the registry ships prebuilt binaries. With the registry DB now stable (see `moonarch-pkgbuilds/DECISIONS.md`, same date), the second mechanism is redundant.
- **Tradeoffs**: If the registry is down or broken, clients have no local-build fallback. Acceptable — a broken registry is a server-side bug to fix, not something to mask with a parallel mechanism that complicates diagnostics.
- **How**: `post-install.sh` and `transform.sh` no longer write `Mode = arp` or a `[moonarch-pkgbuilds]` section to `/etc/paru.conf`, and no longer call `paru -Syu --pkgbuilds`. They run `pacman -Sy` + `paru -S moonarch-git` against the registry. The `moonarch-git` install hook now strips the legacy paru.conf entries on upgrade (see moonarch-pkgbuilds repo).
## 2026-04-19 moonup i18n: reuse pacman gettext catalog + inline fallback
- **Who**: Dominik, ClaudeCode
- **Why**: moonup prompts looked foreign next to pacman/paru output (`[y/N]` vs `[J/n]`, English strings on a German system). User wanted consistency with the rest of the pacman UX.
- **Tradeoffs**: Full `.po`/`.mo` build-chain for moonarch was overkill for ~15 strings. gettext-only approach misses most moonarch-specific strings (no matching msgids in pacman catalog). Chose hybrid: `TEXTDOMAIN=pacman` for strings that match upstream msgids (prompts like `Proceed with installation?`, `Do you want to remove these packages?`, `[Y/n]`, `Starting full system upgrade...`), inline `_t()` helper for moonarch-specific strings.
- **How**: `moonarch-update` sets `TEXTDOMAIN=pacman`/`TEXTDOMAINDIR=/usr/share/locale`. `_t "en" "de"` picks by `${LANG%%.*}` matching `de_*`. `confirm()` now uses `::` prefix, default Y, accepts `y/Y/j/J`. No PKGBUILD change — `gettext` is in `base`. pacman msgids with trailing `\n` (e.g. `Starting full system upgrade...\n`) require ANSI-C quoting `$'...\n'` to match.
## 2026-04-08 Battery conservation mode: udev + sysfs + Waybar toggle
- **Who**: Dominik, ClaudeCode
- **Why**: Laptops with `charge_control_end_threshold` support benefit from limiting charge to 80% to extend battery lifespan. Needed a user-friendly toggle without requiring sudo.
- **Tradeoffs**: udev RUN approach for permissions (group wheel gets write access) vs polkit/pkexec (password prompt on every toggle). Chose udev for seamless UX. State persisted in `/var/lib/moonarch/` (system-wide, not per-user) — acceptable since charge threshold is a hardware setting, not a user preference. Fixed 80% threshold instead of configurable — KISS, matches industry standard.
- **How**: udev rule grants wheel group write on `charge_control_end_threshold`. Toggle script writes sysfs + state file. systemd oneshot service restores on boot. Waybar shows ♥ icon when active, hidden when inactive. Click on battery module toggles.
## 2026-04-07 Walker theme: gtk-inherit → moonarch with fixed colors
- **Who**: Dominik, ClaudeCode
- **Why**: gtk-inherit theme relied on GTK4 color inheritance which works but doesn't update live when switching GTK themes (Walker service needs restart). Explicit color definitions make the theme self-contained and predictable.
- **Tradeoffs**: Colors no longer auto-follow GTK theme changes. Acceptable since moonarch uses a fixed Catppuccin Mocha palette anyway.
- **How**: Renamed theme to moonarch, added @define-color with Colloid-Grey-Dark-Catppuccin values, reduced border/shadow weight.
## 2026-04-07 Migrate archinstall config to v4 format
- **Who**: Dominik, ClaudeCode
- **Why**: archinstall v4.1 introduced new canonical key names. Old keys (audio_config, bootloader, custom-commands) are soft-deprecated and auto-mapped, but custom-commands (hyphen) vs custom_commands (underscore) was risky.
- **Tradeoffs**: Config now requires archinstall v4+. Older ISOs with v2/v3 may not parse the new keys.
- **How**: Migrated audio_config into app_config, bootloader into bootloader_config, custom-commands to custom_commands, gfx_driver value updated.
## 2026-04-07 kanshi config: no ABOUTME, no overwrite on transform
- **Who**: Dominik, ClaudeCode
- **Why**: ABOUTME comments in kanshi config broke the profile parser in wdisplays-persistent store.c, preventing config saves. Additionally, transform.sh was overwriting user display profiles on every run.
- **Tradeoffs**: kanshi default template is now empty (no comments). Users get no guidance in the seed file, but wdisplays-persistent provides the GUI for config management.
- **How**: Removed ABOUTME from defaults/xdg/kanshi/config. Added skip logic in transform.sh to preserve existing kanshi user configs.
## 2026-04-07 Move paru repo config into moonarch.install hook
- **Who**: Dominik, ClaudeCode
- **Why**: paru PKGBUILD repo config was only set up by post-install.sh and transform.sh. If paru updated and overwrote /etc/paru.conf, or the config was missing on existing systems, moonarch-git couldn't update itself — bootstrap loop where the fix requires the package that delivers the fix.
- **Tradeoffs**: Config setup is now in two places: moonarch.install (for updates) and post-install.sh (for first install before moonarch-git exists). Acceptable duplication to break the circular dependency.
- **How**: Added paru repo config (Mode=arp + [moonarch-pkgbuilds] section) to moonarch.install post_install/post_upgrade hook. Kept post-install.sh setup for bootstrap. Removed redundant setup from transform.sh.
## 2026-04-02 Rename paru PKGBUILD repo, move config to /etc/paru.conf
- **Who**: Dominik, ClaudeCode
- **Why**: paru PKGBUILD repo and pacman package registry both used `[moonarch]` as section name. When pkgver-bot pushed version bumps, paru tried to resolve PKGBUILD targets (moongreet-git, moonlock-git) against the pacman repo — which only contains moonarch-git — causing "nicht alle benötigten Pakete gefunden" errors.
- **Tradeoffs**: Renaming the PKGBUILD repo section means existing installations need a one-time manual fix. Using `/etc/paru.conf` instead of `~/.config/paru/paru.conf` is consistent with moonarch's system-wide config philosophy.
- **How**: Renamed PKGBUILD repo section from `[moonarch]` to `[moonarch-pkgbuilds]`. Moved paru config (Mode + repo) from user-level `~/.config/paru/paru.conf` to system-wide `/etc/paru.conf`. Updated post-install.sh accordingly.
## 2026-04-01 Replace dunst with swaync as notification daemon
- **Who**: Dominik, ClaudeCode
- **Who**: Dominik, Ragnar
- **Why**: Dunst lacks wp_fractional_scale_v1 support, causing aliased/jagged font rendering on external monitors in mixed-DPI setups (laptop eDP-1 at 2.5x, externals at 1x). Confirmed by testing: removing fractional scaling fixed the issue. swaync uses GTK4 which handles fractional scaling natively.
- **Tradeoffs**: swaync is heavier than dunst (GTK4 + libadwaita dependency). Loses dunstctl CLI (replaced by swaync-client). Gains notification center panel with DnD toggle, grouping, MPRIS widget support. Waybar already had swaync-client integration with exec-if guard.
- **How**: Replaced dunst with swaync in packages/official.txt and archinstall config. Niri spawn-at-startup updated. Waybar dunstctl widget removed (swaync-client widget already present). New swaync config.json and style.css based on catppuccin/swaync upstream theme with Lavender accent instead of Blue.
## 2026-03-31 Audit: shell script quoting fixes, PKGBUILD permissions
- **Who**: ClaudeCode, Dom
- **Who**: Ragnar, Dom
- **Why**: Security audit found command injection risk in moonarch-cpugov (unquoted array expansion with pkexec), word-splitting in moonarch-btnote (upower output from Bluetooth devices), and nmcli argument injection in moonarch-vpn. PKGBUILD for moongreet had world-readable cache dir.
- **Tradeoffs**: `eval` in cpugov is safe because COMMANDS values are hardcoded string literals, not user input. Alternative (function dispatch) would be cleaner but over-engineered for 3 fixed entries. moonarch-btnote switched from for-loop to while+read with process substitution to avoid subshell.
- **How**: (1) `eval "${COMMANDS[$choice]}"` in cpugov. (2) `while IFS= read -r` + process substitution + quoted `$DEVICE_DATA` in btnote. (3) `--` guard before `$connection` in vpn nmcli calls. (4) `install -dm700` for moongreet cache dirs in PKGBUILD. (5) `else err` logging in post-install.sh when USER_DEFAULTS missing.
## 2026-03-29 /opt/moonarch stays root-owned, no chown to user
- **Who**: Dominik, ClaudeCode
- **Who**: Dominik, Ragnar
- **Why**: Multi-user system — chown to UID 1000 locks out other users from moonarch-update
- **Tradeoffs**: sudo required for git operations in update.sh vs. simpler user-owned repo
- **How**: Repo stays at /opt/moonarch owned by root:root. update.sh uses `sudo git` for fetch/pull. All scripts already use sudo for system-level operations, so this is consistent.
## 2026-03-29 Add transform.sh for existing Arch+Wayland systems
- **Who**: Dominik, ClaudeCode
- **Who**: Dominik, Ragnar
- **Why**: Users with existing Arch+Wayland setups should be able to adopt Moonarch without reinstalling
- **Tradeoffs**: Hard overwrite of all configs (user + system) vs. selective/merge approach — chose hard overwrite for simplicity and consistency
- **How**: New transform.sh with pre-flight summary, backup, DM conflict resolution, and --dry-run flag. Shared helpers extracted to lib.sh.
## 2026-03-29 Package moonarch as moonarch-git PKGBUILD
- **Who**: Dominik, ClaudeCode
- **Who**: Dominik, Ragnar
- **Why**: System artifacts (XDG configs, helper scripts, zsh config, wallpaper) should be managed by pacman for clean deployment, versioning, rollback, and deinstallation
- **Tradeoffs**: /etc/xdg/ configs NOT in backup= (moonarch philosophy: system defaults flow through, users override in ~/.config/). /etc/greetd/ and /etc/moongreet/ NOT owned by package (owned by greetd/moongreet-git, overwritten via .install hook). Helper scripts move from /usr/local/bin/ to /usr/bin/ (FHS for package-managed files)
- **How**: moonarch-git PKGBUILD in moonarch-pkgbuilds repo. sweet-cursors-git as separate package. moonarch-update simplified (no git-sync, pacman handles file deployment). Installer scripts (post-install.sh, transform.sh) remain for orchestration, will be refactored in a follow-up to delegate file deployment to `paru -S moonarch-git`
## 2026-03-30 Replace Rofi with Walker as application launcher
- **Who**: Dominik, ClaudeCode
- **Who**: Dominik, Ragnar
- **Why**: Walker is Wayland-native (GTK4 + gtk4-layer-shell), has built-in providers for clipboard, bluetooth, audio (wireplumber), and Niri integration. Reduces custom shell scripts from 8 to 3. Rofi required a Wayland fork (rofi-lbonn-wayland-git) and every applet was a custom bash script.
- **Tradeoffs**: Walker is newer/less battle-tested than Rofi. Requires separate Elephant daemon with per-provider packages. Dmenu mode lacks Rofi's `-a`/`-u` (active/urgent) and `-mesg` flags. Settings menu (moonarch-setmen) dropped entirely — apps are findable via Walker's app search.
- **How**: Walker + Elephant as systemd user services. Native providers replace 5 rofi scripts (launcher, clipboard, bluetooth, volume, sink-switcher). 3 scripts ported to walker dmenu (vpn, cpugov, sink-switcher). Walker theme inherits GTK4 system theme colors (gtk-inherit). Old rofi configs preserved in `legacy/rofi/`.
## 2026-03-30 Use nm-applet as VPN secret agent, add WireGuard support
- **Who**: Dominik, ClaudeCode
- **Who**: Dominik, Ragnar
- **Why**: VPN auth previously spawned a foot terminal for `nmcli --ask`, which was fragile and ugly. WireGuard connections were invisible to the VPN script and Waybar indicator because both only checked for OpenVPN (`tun0` / `vpn` type).
- **Tradeoffs**: nm-applet adds a tray indicator (mitigated with `--indicator` mode which is minimal). Requires nm-applet running at session start. Alternative was gnome-keyring or a custom secret agent — nm-applet is simpler and handles all NM secret types.
- **How**: nm-applet started via niri spawn-at-startup. moonarch-vpn rewritten to support both vpn and wireguard types, uses nm-applet for auth instead of foot terminal, sends notify-send for connect/disconnect results. Waybar VPN module uses `nmcli` active connection check instead of `/proc/sys/net/ipv4/conf/tun0`, plus RTMIN+9 signal for instant updates after toggle.
## 2026-03-30 Standardize GTK theme to Colloid-Grey-Dark-Catppuccin
- **Who**: Dominik, ClaudeCode
- **Who**: Dominik, Ragnar
- **Why**: gsettings had `Colloid-Dark-Catppuccin` while config files had `Colloid-Catppuccin` — inconsistent. Grey accent matches the icon theme (Colloid-Grey-Catppuccin-Dark). Explicit `-Dark` variant is more reliable than depending on `prefer-dark` color-scheme setting.
- **Tradeoffs**: Explicit dark locks out light mode toggle — acceptable since Moonarch is dark-only by design.
- **How**: Updated transform.sh, post-install.sh, gtk-3.0/settings.ini, and gsettings to `Colloid-Grey-Dark-Catppuccin`. GTK4 symlinks updated accordingly.
+56 -46
View File
@@ -16,21 +16,20 @@ desktop that can be rebuilt from scratch in minutes.
| **Greeter** | [greetd](https://sr.ht/~kennylevinsen/greetd/) + [moongreet](https://gitea.moonarch.de/nevaforget/moongreet) | Minimal, Wayland-native login. moongreet provides GTK4 UI with fingerprint support, running inside its own Niri instance. |
| **Lock Screen** | [moonlock](https://gitea.moonarch.de/nevaforget/moonlock) | ext-session-lock-v1 protocol — compositor guarantees lock on crash. PAM + fprintd, GPU blur, multi-monitor. |
| **Power Menu** | [moonset](https://gitea.moonarch.de/nevaforget/moonset) | GTK4 Layer Shell overlay above Waybar. Lock, logout, hibernate, reboot, shutdown with confirmation. |
| **Idle Manager** | [stasis](https://aur.archlinux.org/packages/stasis) + [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. |
| **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. |
| **Bar** | [Waybar](https://github.com/Alexays/Waybar) | Wayland-native, highly customizable. Niri workspace/window modules via community plugins. |
| **Launcher** | [Walker](https://github.com/abenz1267/walker) + [Elephant](https://github.com/abenz1267/elephant) | Wayland-native GTK4 launcher with built-in providers for apps, clipboard, bluetooth, audio, files, and calculator. Dmenu mode for custom scripts (VPN, CPU governor). |
| **Terminal** | [Foot](https://codeberg.org/dnkl/foot) | Fast, minimal Wayland-native terminal. Server mode for instant window spawning. |
| **Shell** | Zsh | Programmable completion, FZF integration, syntax highlighting. |
| **Audio** | PipeWire | Drop-in replacement for PulseAudio/JACK with lower latency. RNNoise input denoising configured out of the box. |
| **Display Management** | [kanshi](https://sr.ht/~emersion/kanshi/) + [wdisplays-persistent](https://github.com/sfs-pra/wdisplays) | kanshi auto-switches output profiles on hotplug. wdisplays-persistent saves changes directly to kanshi config. |
| **Notifications** | [swaync](https://github.com/ErikReider/SwayNotificationCenter) | GTK4 notification daemon with DND toggle, grouping, MPRIS support. Fractional scaling via native GTK4. |
| **Clipboard** | [cliphist](https://github.com/sentriz/cliphist) + Walker | Clipboard history stored in runtime dir, wiped on session start. Walker provides the picker UI. |
| **Display Management** | [kanshi](https://sr.ht/~emersion/kanshi/) + [wdisplays](https://github.com/artizirk/wdisplays) | kanshi auto-switches output profiles on hotplug. wdisplays for manual GUI configuration. |
| **Notifications** | [dunst](https://dunst-project.org/) | Lightweight, scriptable. DND mode exposed as Waybar module. |
| **Clipboard** | Walker clipboard provider | Built-in clipboard history with image support. Managed by Elephant backend. |
| **GTK Theme** | Colloid-Grey-Dark-Catppuccin | Catppuccin Mocha palette, grey accent, explicit dark variant. |
| **Icons** | Colloid-Grey-Catppuccin-Dark | Catppuccin-colored icon set matching the GTK theme. |
| **Cursor** | Sweet-cursors | Visible on dark backgrounds without clashing. |
| **Font** | UbuntuSans Nerd Font | Clean sans-serif with full Nerd Font glyph coverage for bar/terminal icons. |
| **Wallpaper** | [waypaper](https://github.com/anufrievroman/waypaper) + [awww](https://codeberg.org/LGFae/awww) | waypaper provides GUI selection, awww handles animated transitions (60 FPS, 2s crossfade). |
| **Night Light** | [wlsunset](https://sr.ht/~kennylevinsen/wlsunset/) | Wayland-native blue light filter via wlr-gamma-control. Toggleable from Waybar brightness group. |
| **Firewall** | UFW | Simple deny-incoming/allow-outgoing default. Sane baseline without iptables complexity. |
| **AUR Helper** | [paru](https://github.com/Morganamilo/paru) | Rust-based, pacman-compatible. Supports custom PKGBUILD repos for moongreet/moonlock/moonset. |
@@ -54,25 +53,44 @@ The archinstall config clones this repo to `/opt/moonarch` via custom-commands.
post-install.sh handles the remaining ~95 packages, all configs, themes, services,
and user setup.
### Transform (Existing Arch+Wayland System)
Already running Arch with a Wayland compositor (Sway, Hyprland, GNOME Wayland, etc.)?
Transform converts your system to Moonarch without reinstalling.
**Prerequisites:** Active Wayland session, git installed.
```bash
# Clone the repo
sudo git clone https://gitea.moonarch.de/nevaforget/moonarch.git /opt/moonarch
# Preview what will happen (no changes made)
/opt/moonarch/scripts/transform.sh --dry-run
# Run the transform
/opt/moonarch/scripts/transform.sh
# Reboot (do NOT log out — your previous DM is already disabled)
sudo reboot
```
The script will:
1. Show a pre-flight summary (package diff, config changes, detected conflicts)
2. Back up your `~/.config/`, `~/.zshrc`, and `/etc/xdg/` to timestamped tar archives
3. Disable conflicting display managers (SDDM, GDM, LightDM, etc.)
4. Remove conflicting packages (e.g. PulseAudio → PipeWire)
5. Install all Moonarch packages and configs (**hard overwrite** of all user configs)
6. Enable greetd, firewall, and system services
### Update
```bash
moonup # or: moonarch-update
moonarch-update
```
Interactive updater that upgrades system and AUR packages, reconciles package lists
against what's installed, and cleans orphaned packages. Config deployment happens
automatically via the moonarch-git package on `paru -Syu`.
### Health Check
```bash
moondoc # or: moonarch-doctor
```
Diagnostic tool that verifies the system state against moonarch defaults: services,
configs, packages, helper scripts, firewall, shell and directory permissions. Reports
pass/fail/warn per check with a summary at the end.
Interactive updater that syncs the repo, upgrades system/AUR packages, reconciles
package lists against what's installed, deploys changed XDG defaults, and cleans
orphaned packages.
## Project Structure
@@ -85,46 +103,41 @@ packages/
aur.txt AUR packages (~20), one per line
scripts/
lib.sh Shared helpers sourced by post-install.sh
lib.sh Shared helpers sourced by all scripts
post-install.sh Main automation (packages, configs, themes, services)
moonarch-update Interactive updater (deployed to /usr/bin/ as moonup)
moonarch-doctor System health checker (deployed to /usr/bin/ as moondoc)
transform.sh Convert existing Arch+Wayland system to Moonarch
update.sh Legacy wrapper — prints notice, forwards to moonarch-update
defaults/
xdg/ System-wide XDG configs (deployed to /etc/xdg/)
niri/config.kdl Compositor: layout, keybinds, startup apps
waybar/config, style.css Bar: modules, layout, Catppuccin styling
walker/config.toml, themes/ Launcher: Walker config + moonarch theme (Catppuccin Mocha)
walker/config.toml, themes/ Launcher: Walker config + gtk-inherit theme
foot/foot.ini Terminal: font, colors, keybinds
swaync/config.json, style.css Notifications: appearance, behavior, MPRIS
kanshi/config Display profiles (empty seed, user-configured via wdisplays)
dunst/dunstrc Notifications: geometry, colors, behavior
kanshi/config Display profiles (empty, user-configured)
stasis/stasis.rune Idle manager: AC/battery power plans
pipewire/ Audio: RNNoise input denoising
waypaper/config.ini Wallpaper manager: backend, folder, transitions
fastfetch/config.jsonc System info display
gtk-3.0/, gtk-4.0/ GTK theme settings
bin/ Helper scripts (deployed to /usr/bin/)
bin/ Helper scripts (deployed to /usr/local/bin/)
moonarch-sink-switcher Audio output switcher (walker dmenu + pactl)
moonarch-vpn VPN connection manager (walker dmenu + nmcli)
moonarch-cpugov CPU governor switcher (walker dmenu + auto-cpufreq)
moonarch-dnd Dunst Do Not Disturb toggle (Waybar JSON output)
moonarch-btnote Bluetooth device battery monitor (upower + notify-send)
moonarch-capsnote Caps Lock toggle notification
moonarch-waybar-cpugov Waybar module: CPU governor status
moonarch-waybar-gpustat Waybar module: GPU utilization
moonarch-waybar-hidpp Waybar module: Logitech HID++ device battery
moonarch-waybar-batsaver Waybar module: battery conservation mode indicator
moonarch-waybar-updates Waybar module: package update checker (repo + AUR)
moonarch-batsaver-toggle Toggle battery charge limit (80% ↔ 100%)
moonarch-nightlight Toggle wlsunset blue light filter on/off
moonarch-waybar-nightlight Waybar module: nightlight status
shell/zshrc Zsh config: prompt, aliases, FZF, completion
etc/greetd/ greetd daemon + greeter Niri config
etc/moongreet/ moongreet configuration
etc/systemd/user/ Systemd user services (cliphist, kanshi, walker, nautilus)
etc/systemd/system/ System service (battery conservation restore on boot)
etc/udev/rules.d/ udev rules (battery threshold permissions)
user/systemd/user/ Systemd user services (kanshi, walker, elephant)
user/waybar/ Per-user Waybar overrides (only copied if missing)
backgrounds/wallpaper.jpg Default wallpaper (shared by desktop, greeter, lock screen)
```
@@ -138,11 +151,10 @@ greetd ─► niri (greeter instance) ─► moongreet ─► user authenticates
┌──────────┬────────┬────────┬──────────┐
▼ ▼ ▼ ▼ ▼
waybar swaync foot waypaper nm-applet wlsunset
(bar) (notify) (server) (wallpaper) (VPN) (nightlight)
waybar dunst foot waypaper cliphist
(bar) (notify) (server) (wallpaper) (clipboard)
systemd user services (graphical-session.target):
kanshi, cliphist, walker, nautilus
kanshi runs as a systemd user service (graphical-session.target)
```
## Keybinds (Default)
@@ -175,17 +187,17 @@ All system configs live in `/etc/xdg/` and can be overridden per-user in `~/.con
| Component | System Default | User Override |
|-----------|---------------|---------------|
| Niri | `/etc/xdg/niri/config.kdl` | `~/.config/niri/config.kdl` (includes system config) |
| Niri | `/etc/xdg/niri/config.kdl` | `~/.config/niri/config.kdl` |
| Waybar | `/etc/xdg/waybar/` | `~/.config/waybar/` |
| Walker | `/etc/xdg/walker/` | `~/.config/walker/` |
| Foot | `/etc/xdg/foot/foot.ini` | `~/.config/foot/foot.ini` |
| swaync | `/etc/xdg/swaync/` | `~/.config/swaync/` |
| Dunst | `/etc/xdg/dunst/dunstrc` | `~/.config/dunst/dunstrc` |
| kanshi | `/etc/xdg/kanshi/config` | `~/.config/kanshi/config` |
| PipeWire | `/etc/xdg/pipewire/` | `~/.config/pipewire/` |
| Zsh | `/etc/zsh/zshrc.moonarch` | `~/.zshrc` + `~/.zshrc.d/` |
Helper scripts in `/usr/bin/moonarch-*` are not meant to be overridden — they
are part of the system and updated via `paru -Syu`.
Helper scripts in `/usr/local/bin/moonarch-*` are not meant to be overridden — they
are part of the system and updated via `moonarch-update`.
## System Services
@@ -194,6 +206,7 @@ are part of the system and updated via `paru -Syu`.
| greetd | Login greeter (runs moongreet inside Niri) |
| NetworkManager | Network management (WiFi, VPN, Ethernet) |
| bluetooth | Bluetooth stack (bluez) |
| docker | Container runtime |
| systemd-timesyncd | NTP time synchronization |
| ufw | Firewall (deny incoming, allow outgoing) |
| auto-cpufreq | CPU frequency scaling (AC: performance, battery: powersave) |
@@ -202,11 +215,8 @@ are part of the system and updated via `paru -Syu`.
| Service | Purpose |
|---------|---------|
| cliphist | Clipboard history (wipes on session start, stores in runtime dir) |
| kanshi | Dynamic display configuration (auto-switch output profiles on hotplug) |
| nautilus | File manager preload (faster first launch) |
| stasis | Idle manager (dimming, DPMS, lock, suspend on AC/battery plans) |
| wayland-pipewire-idle-inhibit | Holds a Wayland idle-inhibitor while audio plays (keeps screen awake for windowed browser video that stasis skips) |
| elephant | Walker data provider backend (apps, clipboard, bluetooth, audio) |
| walker | Walker application launcher (GTK4 service mode for instant startup) |
## Moonarch Ecosystem
+3 -4
View File
@@ -1,10 +1,9 @@
FROM archlinux:base-devel
RUN pacman -Sy --noconfirm git curl && pacman -Scc --noconfirm
RUN useradd -m builder && echo "builder ALL=(ALL) NOPASSWD: /usr/bin/pacman -S --needed *" >> /etc/sudoers
RUN useradd -m builder && echo "builder ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
ADD https://gitea.com/gitea/act_runner/releases/download/v0.3.1/act_runner-0.3.1-linux-amd64 /usr/local/bin/act_runner
RUN echo "a05b2103a7cc5617197da214eaa06a1055362f21f9f475eb7fbacb8344d86cf8 /usr/local/bin/act_runner" | sha256sum -c - \
&& chmod +x /usr/local/bin/act_runner
COPY --from=gitea/act_runner:0.3.1 /usr/local/bin/run.sh /usr/local/bin/run.sh
RUN chmod +x /usr/local/bin/act_runner
COPY --from=gitea/act_runner:latest /usr/local/bin/run.sh /usr/local/bin/run.sh
RUN mkdir -p /data && chown builder:builder /data
USER builder
ENV HOME=/home/builder
+5 -7
View File
@@ -2,16 +2,11 @@
"__comment": "ABOUTME: archinstall configuration for Moonarch.",
"__comment2": "ABOUTME: Base setup — kernel, disk and filesystem are chosen interactively.",
"app_config": {
"audio_config": {
"audio": "pipewire"
}
},
"bootloader_config": {
"bootloader": "Systemd-boot",
"uki": false
},
"kernels": ["linux-zen"],
@@ -43,6 +38,8 @@
"pipewire-jack",
"pipewire-pulse",
"wireplumber",
"docker",
"docker-compose",
"fwupd",
"ufw",
"greetd",
@@ -55,7 +52,7 @@
],
"profile_config": {
"gfx_driver": "All open-source (default)",
"gfx_driver": "All open-source",
"greeter": "greetd",
"profile": {
"main": "Desktop",
@@ -66,6 +63,7 @@
"services": [
"NetworkManager",
"bluetooth",
"docker",
"greetd",
"systemd-timesyncd",
"ufw"
@@ -73,7 +71,7 @@
"timezone": "Europe/Berlin",
"custom_commands": [
"custom-commands": [
"git clone https://gitea.moonarch.de/nevaforget/moonarch.git /opt/moonarch"
]
}
-35
View File
@@ -1,35 +0,0 @@
#!/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
@@ -1,23 +0,0 @@
#!/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"
-22
View File
@@ -1,22 +0,0 @@
#!/usr/bin/bash
# ABOUTME: Toggles battery conservation mode between 80% and 100% charge limit.
# ABOUTME: Reads sysfs as user, dispatches the privileged write via pkexec.
THRESHOLD_FILE="/sys/class/power_supply/BAT0/charge_control_end_threshold"
CONSERVATION_LIMIT=80
[[ -f "$THRESHOLD_FILE" ]] || exit 1
CURRENT=$(cat "$THRESHOLD_FILE")
[[ "$CURRENT" =~ ^[0-9]+$ ]] || exit 1
if [[ "$CURRENT" -le "$CONSERVATION_LIMIT" ]]; then
NEW=100
else
NEW="$CONSERVATION_LIMIT"
fi
pkexec /usr/bin/moonarch-batsaver-apply "$NEW" || exit 1
# Signal Waybar to refresh the batsaver module (SIGRTMIN+9)
pkill -RTMIN+9 waybar
+3 -2
View File
@@ -8,8 +8,9 @@ ICON="battery-empty"
while IFS= read -r d; do
[ -z "$d" ] && continue
DEVICE_DATA=$(upower -i "$d")
PER_INT=$(echo "$DEVICE_DATA" | grep -oP 'percentage:\s+\K[0-9]+')
DEVICE_NAME=$(echo "$DEVICE_DATA" | grep -oP 'model:\s+\K.+')
PERCENTAGE=$(echo "$DEVICE_DATA" | grep -Po '(?<=(percentage: )).*(?= icon)')
PER_INT=$(echo "${PERCENTAGE//%}")
DEVICE_NAME=$(echo "$DEVICE_DATA" | grep -Po '(?<=(model: )).*(?= serial)')
if [ -n "$DEVICE_NAME" ] && [ -n "$PER_INT" ] && [ "$PER_INT" -lt "$NOTIFY_AT_PERCENTAGE" ]; then
notify-send -t 5000 -e "Low battery $DEVICE_NAME $PER_INT%" -i "$ICON" \
+3 -4
View File
@@ -60,12 +60,11 @@ fi
# check if choice exists
if test "${COMMANDS[$choice]+isset}"
then
${COMMANDS[$choice]}
# Execute the choice — eval required because COMMANDS values contain
# multi-word strings that must be interpreted as full commands.
eval "${COMMANDS[$choice]}"
notify-send -h string:x-canonical-private-synchronous:cpugov -i cpu "CPU Mode" "Set to $choice ${LABELS[$choice]}"
# Signal Waybar to refresh the cpugov module (SIGRTMIN+10)
pkill -RTMIN+10 waybar
else
notify-send -u critical "CPU Governor" "Unknown command: ${choice}"
fi
-11
View File
@@ -1,11 +0,0 @@
#!/bin/bash
# ABOUTME: Toggles wlsunset night light on/off via systemd user service.
# ABOUTME: Persists state across reboots (enable/disable), signals waybar for refresh.
if systemctl --user is-active --quiet wlsunset; then
systemctl --user disable --now wlsunset
else
systemctl --user enable --now wlsunset
fi
pkill -RTMIN+11 waybar
+1 -5
View File
@@ -5,11 +5,7 @@
# choose audio sink via rofi
# changes default sink and moves all streams to that sink
sink=$(pactl list sinks short | awk '{print $1, $2}' | walker -d -p "󱡫 Sink Switcher" | awk '{print $1}')
# 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
sink=$(pactl list sinks short | awk '{print $1, $2}' | walker -d -p "󱡫 Sink Switcher" | awk '{print $1}') &&
pactl set-default-sink "$sink" &&
for input in $(pactl list sink-inputs short | awk '{print $1}'); do
+18 -5
View File
@@ -10,7 +10,7 @@
ACTIVE_PREFIX="󰌘 "
INACTIVE_PREFIX="󰌙 "
for cmd in nmcli walker; do
for cmd in nmcli walker notify-send; do
command -v "$cmd" >/dev/null 2>&1 || {
echo "Error: '$cmd' not found" >&2
exit 1
@@ -35,17 +35,29 @@ function extract_connection_name() {
echo "$result"
}
# Connect a VPN.
# Connect a VPN and notify the result.
# Requires nm-applet (or another NM secret agent) for interactive auth.
function connect_vpn() {
local connection="$1"
nmcli connection up id "$connection"
local feedback
if feedback=$(nmcli connection up -- "$connection" 2>&1); then
notify-send "VPN" "Connected to '$connection'"
else
notify-send -u critical "VPN" "Connection failed: $feedback"
fi
}
# Disconnect a VPN.
# Disconnect a VPN and notify the result.
function disconnect_vpn() {
local connection="$1"
nmcli connection down id "$connection"
local feedback
if feedback=$(nmcli connection down -- "$connection" 2>&1); then
notify-send "VPN" "Disconnected from '$connection'"
else
notify-send -u critical "VPN" "Disconnect failed: $feedback"
fi
}
# Toggle the VPN connection based on its current state.
@@ -77,6 +89,7 @@ function main() {
connections=$(list_vpn_connections)
if [[ -z "$connections" ]]; then
notify-send "VPN" "No VPN connections configured"
exit 0
fi
-61
View File
@@ -1,61 +0,0 @@
#!/bin/bash
# ABOUTME: Wrapper that merges system waybar config with per-machine userconfig.
# ABOUTME: Handles array prepend/append that waybar's native include cannot do.
SYSTEM_CONFIG="/etc/xdg/waybar/config"
SYSTEM_STYLE="/etc/xdg/waybar/style.css"
USER_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/waybar"
USERCONFIG="$USER_DIR/userconfig"
OUTPUT="$USER_DIR/config"
USER_STYLE="$USER_DIR/style.css"
merge_config() {
mkdir -p "$USER_DIR"
if ! jq -s '
.[0] as $sys | .[1] as $user |
(($user.prepend // {}) | to_entries) as $prepends |
(($user.append // {}) | to_entries) as $appends |
$sys |
reduce $prepends[] as $p (.;
.[$p.key] = ($p.value + (.[$p.key] // []))
) |
reduce $appends[] as $a (.;
.[$a.key] = ((.[$a.key] // []) + $a.value)
) |
($user | del(.prepend) | del(.append)) as $extras |
. * $extras
' "$SYSTEM_CONFIG" "$USERCONFIG" > "${OUTPUT}.tmp" 2>&1; then
local err
err=$(cat "${OUTPUT}.tmp")
rm -f "${OUTPUT}.tmp"
logger -t moonarch-waybar "Config merge failed: $err"
notify-send -u critical "moonarch-waybar" "Config merge failed — using system config.\n$err"
return 1
fi
mv "${OUTPUT}.tmp" "$OUTPUT"
}
bootstrap_style() {
if [[ ! -f "$USER_STYLE" ]]; then
mkdir -p "$USER_DIR"
cat > "$USER_STYLE" << 'CSS'
/* Generated by moonarch-waybar — add custom styles below */
@import url("/etc/xdg/waybar/style.css");
CSS
fi
}
if [[ -f "$USERCONFIG" ]]; then
if [[ ! -f "$OUTPUT" ]] ||
[[ "$USERCONFIG" -nt "$OUTPUT" ]] ||
[[ "$SYSTEM_CONFIG" -nt "$OUTPUT" ]]; then
# On merge failure the previous $OUTPUT is stale — remove it so waybar
# falls back to XDG's system config instead of running with stale merged data.
merge_config || rm -f "$OUTPUT"
fi
bootstrap_style
fi
exec waybar "$@"
-19
View File
@@ -1,19 +0,0 @@
#!/usr/bin/bash
# ABOUTME: Waybar module showing battery conservation mode status.
# ABOUTME: Outputs heart icon when active (threshold ≤80), empty when inactive.
THRESHOLD_FILE="/sys/class/power_supply/BAT0/charge_control_end_threshold"
# No battery threshold support → no output → Waybar hides module
[[ -f "$THRESHOLD_FILE" ]] || exit 0
THRESHOLD=$(cat "$THRESHOLD_FILE")
if [[ "$THRESHOLD" -le 80 ]]; then
jq --compact-output -n \
--arg text "♥" \
--arg tooltip "Battery Conservation: ON (limit ${THRESHOLD}%)" \
--arg class "on" \
'{text: $text, tooltip: $tooltip, class: $class}'
fi
# Threshold > 80 → no output → Waybar hides module
+3 -3
View File
@@ -1,8 +1,8 @@
#!/usr/bin/bash
# ABOUTME: Waybar module that outputs the CPU governor as JSON.
# ABOUTME: Referenced by the Waybar custom/cpugov config.
# ABOUTME: Waybar-Modul das den CPU-Governor als JSON ausgibt.
# ABOUTME: Wird von der Waybar custom/cpugov Config referenziert.
CPU_GOV=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 2>/dev/null)
CPU_GOV=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor)
case $CPU_GOV in
performance)
+15 -9
View File
@@ -1,7 +1,9 @@
#!/usr/bin/bash
# ABOUTME: Waybar module that outputs GPU utilization as JSON.
# ABOUTME: Referenced by the Waybar custom/gpu-usage config.
# ABOUTME: Waybar-Modul das die GPU-Auslastung als JSON ausgibt.
# ABOUTME: Wird von der Waybar custom/gpu-usage Config referenziert.
while :
do
GPU_STAT=$(cat /sys/class/hwmon/hwmon*/device/gpu_busy_percent 2>/dev/null | head -1 || echo "0")
GPU_STAT="${GPU_STAT:-0}"
ICON="<span color='#69ff94' size='8pt' rise='1.5pt'>▁</span>"
@@ -24,10 +26,14 @@ elif [ "$GPU_STAT" -lt 100 ]; then
ICON="<span color='#dd532e' size='8pt' rise='1.5pt'>█</span>"
fi
jq --unbuffered --compact-output -n \
--arg text "GPU $ICON" \
--arg alt "GPU $ICON $GPU_STAT%" \
--arg tooltip "GPU $ICON $GPU_STAT%" \
--arg class "gpustat" \
--argjson percentage "$GPU_STAT" \
'{text: $text, alt: $alt, tooltip: $tooltip, class: $class, percentage: $percentage}'
s="text|alt|tooltip|class|percentage
GPU $ICON|GPU $ICON $GPU_STAT%|GPU $ICON $GPU_STAT%|gpustat|$GPU_STAT"
jq --unbuffered --compact-output -Rn '
( input | split("|") ) as $keys |
( inputs | split("|") ) as $vals |
[[$keys, $vals] | transpose[] | {key:.[0],value:.[1]}] | from_entries
' <<<"$s"
sleep 5
done
-9
View File
@@ -1,9 +0,0 @@
#!/bin/bash
# ABOUTME: Outputs JSON status for waybar nightlight module.
# ABOUTME: Checks wlsunset systemd service, shows warm icon when active.
if systemctl --user is-active --quiet wlsunset; then
jq -nc '{text: "󰌵", alt: "on", tooltip: "Nightlight: An (5000K)", class: "on"}'
else
jq -nc '{text: "󰌶", alt: "off", tooltip: "Nightlight: Aus", class: "off"}'
fi
-68
View File
@@ -1,68 +0,0 @@
#!/usr/bin/bash
# ABOUTME: Waybar module that checks for available package updates with caching.
# ABOUTME: Combines pacman repo updates (checkupdates) and AUR updates (paru -Qua).
CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/moonarch"
CACHE_FILE="$CACHE_DIR/waybar-updates"
PACMAN_DB="/var/lib/pacman/local"
MAX_AGE=3600
mkdir -p "$CACHE_DIR"
needs_refresh() {
# No cache yet
[[ ! -f "$CACHE_FILE" ]] && return 0
# Pacman DB changed since last check (update was installed)
[[ "$PACMAN_DB" -nt "$CACHE_FILE" ]] && return 0
# Cache older than MAX_AGE
local age=$(( $(date +%s) - $(stat -c %Y "$CACHE_FILE") ))
[[ "$age" -ge "$MAX_AGE" ]] && return 0
return 1
}
if needs_refresh; then
repo_updates=$(checkupdates 2>/dev/null)
repo_count=0
if [[ -n "$repo_updates" ]]; then
repo_count=$(echo "$repo_updates" | wc -l)
fi
aur_updates=""
aur_count=0
if command -v paru &>/dev/null; then
aur_updates=$(paru -Qua 2>/dev/null)
if [[ -n "$aur_updates" ]]; then
aur_count=$(echo "$aur_updates" | wc -l)
fi
fi
total=$((repo_count + aur_count))
if [[ "$total" -eq 0 ]]; then
echo "" > "$CACHE_FILE"
exit 0
fi
# Build tooltip with package lists
tooltip=""
if [[ -n "$repo_updates" ]]; then
tooltip+="Repo ($repo_count):"$'\n'"$repo_updates"
fi
if [[ -n "$aur_updates" ]]; then
[[ -n "$tooltip" ]] && tooltip+=$'\n\n'
tooltip+="AUR ($aur_count):"$'\n'"$aur_updates"
fi
jq --compact-output -n \
--arg text "$total" \
--arg alt "has-updates" \
--arg tooltip "$tooltip" \
--arg class "has-updates" \
'{text: $text, alt: $alt, tooltip: $tooltip, class: $class}' > "$CACHE_FILE"
fi
# Output cached result (empty cache = no updates = module hidden)
cat "$CACHE_FILE"
@@ -1,21 +0,0 @@
<?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,8 +3,6 @@
[appearance]
background = "/usr/share/moonarch/wallpaper.jpg"
cursor-theme = "Sweet-cursors"
cursor-size = 24
[behavior]
# show_user_list = true
-23
View File
@@ -1,23 +0,0 @@
# 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,23 +0,0 @@
# ABOUTME: Restores battery charge threshold from saved state on boot.
# ABOUTME: Only runs on laptops with threshold support and a saved state file.
[Unit]
Description=Restore battery conservation mode threshold
After=sysinit.target
ConditionPathExists=/sys/class/power_supply/BAT0/charge_control_end_threshold
ConditionPathExists=/var/lib/moonarch/batsaver-threshold
[Service]
Type=oneshot
ExecStart=/usr/bin/moonarch-batsaver-restore
NoNewPrivileges=true
ProtectHome=true
PrivateTmp=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictNamespaces=true
RestrictRealtime=true
LockPersonality=true
[Install]
WantedBy=multi-user.target
@@ -1,16 +0,0 @@
# ABOUTME: systemd user service for image clipboard history via cliphist + wl-paste.
# ABOUTME: Stores image clipboard entries in XDG_RUNTIME_DIR.
[Unit]
Description=Clipboard history manager (image)
PartOf=graphical-session.target
After=cliphist-text.service
[Service]
Type=simple
ExecStart=/usr/bin/wl-paste --type image --watch cliphist -db-path %t/cliphist/db store
Restart=on-failure
RestartSec=3
[Install]
WantedBy=graphical-session.target
@@ -1,16 +0,0 @@
# ABOUTME: systemd user service for text clipboard history via cliphist + wl-paste.
# ABOUTME: Wipes history on start, stores text entries in XDG_RUNTIME_DIR.
[Unit]
Description=Clipboard history manager (text)
PartOf=graphical-session.target
[Service]
Type=simple
ExecStartPre=/bin/sh -c 'mkdir -p $XDG_RUNTIME_DIR/cliphist && /usr/bin/cliphist wipe'
ExecStart=/usr/bin/wl-paste --watch cliphist -db-path %t/cliphist/db store
Restart=on-failure
RestartSec=3
[Install]
WantedBy=graphical-session.target
@@ -1,11 +0,0 @@
[Unit]
Description=Nautilus File Manager (GApplication Service)
After=graphical-session.target
[Service]
Type=simple
ExecStart=nautilus --gapplication-service
Restart=on-failure
[Install]
WantedBy=graphical-session.target
@@ -1,20 +0,0 @@
# ABOUTME: systemd user service for wlsunset night light (blue light filter).
# ABOUTME: Starts after kanshi to ensure all outputs are configured.
[Unit]
Description=Wlsunset night light (blue light filter)
Documentation=man:wlsunset(1)
PartOf=graphical-session.target
After=graphical-session.target kanshi.service
[Service]
Type=simple
# Give kanshi time to configure all outputs before wlsunset captures them
ExecStartPre=/bin/sleep 2
ExecStart=/usr/bin/wlsunset -T 6500 -t 5000 -S 00:00 -s 00:01
ExecStartPost=/usr/bin/pkill -RTMIN+11 waybar
Restart=on-failure
RestartSec=3
[Install]
WantedBy=graphical-session.target
@@ -1,10 +0,0 @@
[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;
+27 -14
View File
@@ -1,4 +1,5 @@
# ABOUTME: Moonarch default zsh configuration with Catppuccin-themed prompt.
# ABOUTME: Sources user overrides from ~/.zshrc.d/ and ~/.zshrc.local
# --- History ---
HISTFILE=~/.histfile
@@ -29,25 +30,26 @@ add-zsh-hook preexec _preexec_title
# --- Prompt (Catppuccin Mocha) ---
parse_git_branch() {
# Gate on cheap check first — spawning git in every non-repo directory on every
# prompt render costs 20-80ms per prompt. Pattern-match the status output with
# zsh glob matching instead of piping to grep for three subshell-spawning checks.
git rev-parse --git-dir &>/dev/null || return
local branch="" git_status="" color=green flags=""
branch=$(git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD 2>/dev/null)
git_status=$(git status --porcelain 2>/dev/null)
if [[ "$git_status" == *$'\n M '* || "$git_status" == " M "* || "$git_status" == *$'\nM'* ]]; then
color=yellow; flags+="*"
local branch=""
branch=$(git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/')
local git_status=$(git status --porcelain 2>/dev/null)
local color=green
if echo "$git_status" | grep -q "^ M"; then
color=yellow
branch="${branch}*"
fi
if [[ "$git_status" == *$'\nA '* || "$git_status" == "A "* || "$git_status" == *'??'* ]]; then
color=yellow; flags+="+"
if echo "$git_status" | grep -qE "^ A|^\?\?"; then
color=yellow
branch="${branch}+"
fi
if [[ "$git_status" == *$'\n D '* || "$git_status" == " D "* ]]; then
color=yellow; flags+="-"
if echo "$git_status" | grep -q "^ D"; then
color=yellow
branch="${branch}-"
fi
if [[ -n "$branch" ]]; then
echo " [%F{${color}}${branch}${flags}%F{reset}]"
branch=[%F{${color}}${branch}%F{reset}]
fi
echo " $branch"
}
precmd() {
@@ -139,3 +141,14 @@ export XDG_SESSION_TYPE="wayland"
export EDITOR="nvim"
export SUDO_EDITOR="nvim"
export MOZ_ENABLE_WAYLAND="1"
# --- User override scripts ---
# Drop custom config snippets into ~/.zshrc.d/*.zsh
if [[ -d "$HOME/.zshrc.d" ]]; then
for f in "$HOME/.zshrc.d"/*.zsh(N); do
source "$f"
done
fi
# Single-file user override (for simple additions)
[[ -f "$HOME/.zshrc.local" ]] && source "$HOME/.zshrc.local"
+33
View File
@@ -0,0 +1,33 @@
{
// ABOUTME: User override for Waybar — extends the system-wide config from /etc/xdg/waybar/.
// ABOUTME: Define custom modules here and add them to modules-left/center/right.
// Load system-wide Moonarch config as base.
// Properties defined here override those from the include.
"include": ["/etc/xdg/waybar/config"]
// Example: extend module bar (must be specified completely as it replaces
// the system-wide one):
//
// "modules-right": [
// "mpris",
// "custom/cpugov",
// "group/net",
// "group/sound",
// "backlight",
// "custom/keyboard",
// "battery",
// "group/indicators"
// ],
// Example: show HID++ device battery (e.g. Logitech keyboard)
// moonarch-waybar-hidpp finds the correct hidpp_battery_* entry dynamically
//
// "custom/keyboard": {
// "exec": "moonarch-waybar-hidpp 'G515 LS TKL'",
// "return-type": "json",
// "interval": 120,
// "format": "{}",
// "exec-on-event": false
// }
}
+18
View File
@@ -0,0 +1,18 @@
/* ABOUTME: User override for Waybar styling — extends the system-wide style.css. */
/* ABOUTME: Define custom styles here, the system-wide base is loaded via @import. */
@import url("/etc/xdg/waybar/style.css");
/* Add custom styles below. */
/* Selectors from the system-wide config can be overridden here. */
/* Example: color Logitech keyboard battery */
/*
#battery-keyboard.warning:not(.charging) {
color: #e6a200;
}
#battery-keyboard.critical:not(.charging) {
color: #cc3436;
}
*/
-4
View File
@@ -2,8 +2,4 @@
# ABOUTME: User overrides go in ~/.config/gtk-4.0/settings.ini
[Settings]
gtk-theme-name=Colloid-Grey-Dark-Catppuccin
gtk-icon-theme-name=Colloid-Grey-Catppuccin-Dark
gtk-cursor-theme-name=Sweet-cursors
gtk-cursor-theme-size=24
gtk-application-prefer-dark-theme=1
+2
View File
@@ -0,0 +1,2 @@
# ABOUTME: kanshi configuration for dynamic display output management.
# ABOUTME: Add profiles here to auto-switch outputs on hotplug events.
+4 -10
View File
@@ -41,7 +41,6 @@ gestures {
layout {
gaps 8
center-focused-column "never"
always-center-single-column
preset-column-widths {
proportion 0.33333
@@ -79,15 +78,15 @@ layout {
// xwayland-satellite is managed automatically since niri 25.08
// kanshi is managed via systemd user service (kanshi.service)
spawn-at-startup "moonarch-waybar"
spawn-at-startup "waybar"
spawn-at-startup "swaync"
spawn-at-startup "/usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1"
spawn-at-startup "nm-applet" "--indicator"
spawn-sh-at-startup "waypaper --restore"
// spawn-sh-at-startup "nemo . &> /dev/null &"
spawn-sh-at-startup "foot --server"
// wlsunset is managed via systemd user service (wlsunset.service)
// Clipboard history managed by cliphist.service (systemd user service)
spawn-sh-at-startup "mkdir -p $XDG_RUNTIME_DIR/cliphist && wl-paste --watch cliphist -db-path $XDG_RUNTIME_DIR/cliphist/db store"
spawn-sh-at-startup "mkdir -p $XDG_RUNTIME_DIR/cliphist && wl-paste --type image --watch cliphist -db-path $XDG_RUNTIME_DIR/cliphist/db store"
hotkey-overlay {
hide-not-bound
@@ -116,11 +115,6 @@ window-rule {
open-floating true
}
window-rule {
match app-id=r#"^mpv$"#
open-floating true
}
window-rule {
geometry-corner-radius 4
clip-to-geometry true
@@ -131,7 +125,7 @@ binds {
Super+C hotkey-overlay-title=null { spawn "walker" "-s" "clipboard"; }
Alt+W { spawn-sh "killall waybar && moonarch-waybar &"; }
Alt+W { spawn-sh "killall waybar && waybar &"; }
Super+E { spawn-sh "xdg-open ~"; }
+2 -8
View File
@@ -5,15 +5,9 @@
@description "Idle management for Moonarch (Niri + moonlock)"
default:
# 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.
# Media playback inhibits idle (non-browser only, browser uses D-Bus inhibit)
monitor_media true
ignore_remote_media false
ignore_remote_media true
# App/process inhibit patterns (apps that don't use D-Bus idle-inhibit)
inhibit_apps [
+2 -2
View File
@@ -5,10 +5,10 @@
"positionY": "top",
"layer": "overlay",
"control-center-layer": "top",
"cssPriority": "user",
"cssPriority": "application",
"notification-window-width": 300,
"notification-window-height": -1,
"notification-icon-size": 64,
"notification-body-image-height": 100,
"notification-body-image-width": 200,
+93 -61
View File
@@ -1,16 +1,33 @@
/* ABOUTME: Moonarch swaync notification styling with Catppuccin Mocha colors. */
/* ABOUTME: Based on catppuccin/swaync v1.0.1 release, accent changed from Blue to Lavender. */
/* ABOUTME: Based on catppuccin/swaync, accent changed from Blue to Lavender. */
/* Catppuccin Mocha palette */
@define-color base #1e1e2e;
@define-color mantle #181825;
@define-color crust #11111b;
@define-color surface0 #313244;
@define-color surface1 #45475a;
@define-color surface2 #585b70;
@define-color text #cdd6f4;
@define-color subtext0 #a6adc8;
@define-color subtext1 #bac2de;
@define-color overlay0 #6c7086;
@define-color lavender #b4befe;
@define-color sapphire #74c7ec;
@define-color red #f38ba8;
@define-color maroon #eba0ac;
@define-color pink #f5c2e7;
@define-color yellow #f9e2af;
* {
all: unset;
font-size: 12px;
font-size: 14px;
font-family: "UbuntuSans Nerd Font";
transition: 200ms;
--notification-icon-size: 36px;
}
trough highlight {
background: #cdd6f4;
background: @text;
}
scale {
@@ -29,24 +46,25 @@ trough slider {
border-radius: 12.6px;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.8);
transition: all 0.2s ease;
background-color: #b4befe;
background-color: @lavender;
}
trough slider:hover {
box-shadow: 0 0 2px rgba(0, 0, 0, 0.8), 0 0 8px #b4befe;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.8), 0 0 8px @lavender;
}
trough {
background-color: #313244;
background-color: @surface0;
}
/* notifications */
/* ── Notifications ── */
.notification-background {
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.8), inset 0 0 0 1px #45475a;
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.8), inset 0 0 0 1px @surface1;
border-radius: 12.6px;
margin: 18px;
background: #181825;
color: #cdd6f4;
background: @mantle;
color: @text;
padding: 0;
}
@@ -56,7 +74,7 @@ trough {
}
.notification-background .notification.critical {
box-shadow: inset 0 0 7px 0 #f38ba8;
box-shadow: inset 0 0 0 2px @red;
}
.notification .notification-content {
@@ -64,70 +82,75 @@ trough {
}
.notification .notification-content overlay {
/* icons */
margin: 4px;
}
.notification-content .summary {
color: #cdd6f4;
color: @text;
}
.notification-content .time {
color: #a6adc8;
color: @subtext0;
}
.notification-content .body {
color: #bac2de;
color: @subtext1;
}
.notification > *:last-child > * {
min-height: 3.4em;
}
/* Close button */
.notification-background .close-button {
margin: 7px;
padding: 2px;
border-radius: 6.3px;
color: #1e1e2e;
background-color: #f38ba8;
border-radius: 50%;
color: @base;
background-color: @red;
}
.notification-background .close-button:hover {
background-color: #eba0ac;
background-color: @maroon;
}
.notification-background .close-button:active {
background-color: #f5c2e7;
background-color: @pink;
}
/* Action buttons */
.notification .notification-action {
border-radius: 7px;
color: #cdd6f4;
box-shadow: inset 0 0 0 1px #45475a;
color: @text;
box-shadow: inset 0 0 0 1px @surface1;
margin: 4px;
padding: 8px;
font-size: 0.2rem; /* controls the button size not text size*/
font-size: 0.2rem;
}
.notification .notification-action {
background-color: #313244;
background-color: @surface0;
}
.notification .notification-action:hover {
background-color: #45475a;
background-color: @surface1;
}
.notification .notification-action:active {
background-color: #585b70;
background-color: @surface2;
}
/* ── Progress bar ── */
.notification.critical progress {
background-color: #f38ba8;
background-color: @red;
}
.notification.low progress,
.notification.normal progress {
background-color: #b4befe;
background-color: @lavender;
}
.notification progress,
@@ -137,18 +160,19 @@ trough {
padding: 3px 0;
}
/* control center */
/* ── Control center ── */
.control-center {
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.8), inset 0 0 0 1px #313244;
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.8), inset 0 0 0 1px @surface0;
border-radius: 12.6px;
background-color: #1e1e2e;
color: #cdd6f4;
background-color: @base;
color: @text;
padding: 14px;
}
.control-center .notification-background {
border-radius: 7px;
box-shadow: inset 0 0 0 1px #45475a;
box-shadow: inset 0 0 0 1px @surface1;
margin: 4px 10px;
}
@@ -161,24 +185,24 @@ trough {
}
.control-center .widget-title > label {
color: #cdd6f4;
color: @text;
font-size: 1.3em;
}
.control-center .widget-title button {
border-radius: 7px;
color: #cdd6f4;
background-color: #313244;
box-shadow: inset 0 0 0 1px #45475a;
color: @text;
background-color: @surface0;
box-shadow: inset 0 0 0 1px @surface1;
padding: 8px;
}
.control-center .widget-title button:hover {
background-color: #45475a;
background-color: @surface1;
}
.control-center .widget-title button:active {
background-color: #585b70;
background-color: @surface2;
}
.control-center .notification-group {
@@ -186,7 +210,7 @@ trough {
}
.control-center .notification-group:focus .notification-background {
background-color: #313244;
background-color: @surface0;
}
scrollbar slider {
@@ -198,7 +222,8 @@ scrollbar trough {
margin: 2px 0;
}
/* dnd */
/* ── DnD toggle ── */
.widget-dnd {
margin-top: 5px;
border-radius: 8px;
@@ -208,28 +233,29 @@ scrollbar trough {
.widget-dnd > switch {
font-size: initial;
border-radius: 8px;
background: #313244;
background: @surface0;
box-shadow: none;
}
.widget-dnd > switch:checked {
background: #b4befe;
background: @lavender;
}
.widget-dnd > switch slider {
background: #45475a;
background: @surface1;
border-radius: 8px;
}
/* mpris */
/* ── MPRIS ── */
.widget-mpris-player {
background: #313244;
background: @surface0;
border-radius: 12.6px;
color: #cdd6f4;
color: @text;
}
.mpris-overlay {
background-color: #313244;
background-color: @surface0;
opacity: 0.9;
padding: 15px 10px;
}
@@ -242,17 +268,17 @@ scrollbar trough {
.widget-mpris-title {
font-size: 1.2rem;
color: #cdd6f4;
color: @text;
}
.widget-mpris-subtitle {
font-size: 1rem;
color: #bac2de;
color: @subtext1;
}
.widget-mpris button {
border-radius: 12.6px;
color: #cdd6f4;
color: @text;
margin: 0 5px;
padding: 2px;
}
@@ -262,34 +288,38 @@ scrollbar trough {
}
.widget-mpris button:hover {
background-color: #313244;
background-color: @surface0;
}
.widget-mpris button:active {
background-color: #45475a;
background-color: @surface1;
}
.widget-mpris button:disabled {
opacity: 0.5;
}
/* ── Menubar / Power ── */
.widget-menubar > box > .menu-button-bar > button > label {
font-size: 3rem;
padding: 0.5rem 2rem;
}
.widget-menubar > box > .menu-button-bar > :last-child {
color: #f38ba8;
color: @red;
}
.power-buttons button:hover,
.powermode-buttons button:hover,
.screenshot-buttons button:hover {
background: #313244;
background: @surface0;
}
/* ── Labels / Buttons Grid ── */
.control-center .widget-label > label {
color: #cdd6f4;
color: @text;
font-size: 2rem;
}
@@ -301,26 +331,28 @@ scrollbar trough {
font-size: 2.5rem;
}
/* ── Volume / Backlight ── */
.widget-volume {
padding: 1rem 0;
}
.widget-volume label {
color: #74c7ec;
color: @sapphire;
padding: 0 1rem;
}
.widget-volume trough highlight {
background: #74c7ec;
background: @sapphire;
}
.widget-backlight trough highlight {
background: #f9e2af;
background: @yellow;
}
.widget-backlight label {
font-size: 1.5rem;
color: #f9e2af;
color: @yellow;
}
.widget-backlight .KB {
+1 -1
View File
@@ -7,7 +7,7 @@ click_to_close = true
as_window = false
single_click_activation = true
selection_wrap = false
theme = "moonarch"
theme = "gtk-inherit"
disable_mouse = false
page_jump_items = 10
hide_quick_activation = false
@@ -1,10 +1,5 @@
@define-color window_bg_color #292c3c;
@define-color window_fg_color #eff1f5;
@define-color accent_bg_color #ccd0da;
@define-color accent_fg_color rgba(17, 17, 27, 0.87);
@define-color error_bg_color #ea999c;
@define-color error_fg_color rgba(17, 17, 27, 0.87);
@define-color theme_fg_color @window_fg_color;
/* ABOUTME: Walker theme that inherits colors from the active GTK4 theme.
ABOUTME: Only overrides layout/spacing colors come from Colloid-Dark-Catppuccin or whatever is active. */
* {
all: unset;
@@ -13,7 +8,7 @@
popover {
background: lighter(@window_bg_color);
border: 1px solid alpha(@accent_bg_color, 0.15);
border: 1px solid darker(@accent_bg_color);
border-radius: 18px;
padding: 10px;
}
@@ -32,12 +27,12 @@ scrollbar {
.box-wrapper {
box-shadow:
0 19px 38px rgba(0, 0, 0, 0.15),
0 15px 12px rgba(0, 0, 0, 0.1);
0 19px 38px rgba(0, 0, 0, 0.3),
0 15px 12px rgba(0, 0, 0, 0.22);
background: @window_bg_color;
padding: 20px;
border-radius: 20px;
border: 1px solid alpha(@accent_bg_color, 0.15);
border: 1px solid darker(@accent_bg_color);
}
.preview-box,
+94 -69
View File
@@ -1,6 +1,6 @@
{
"__aboutme1": "Moonarch default waybar configuration for Niri.",
"__aboutme2": "Deployed to /etc/xdg/waybar/ by moonarch-git package.",
"__aboutme2": "User overrides go in ~/.config/waybar/config",
"layer": "top",
"margin-top": 0,
"spacing": 5,
@@ -17,18 +17,18 @@
"modules-right": [
"tray",
"mpris",
"custom/cpugov",
"bluetooth",
"group/sound",
"group/brightness",
"group/bat",
"backlight",
"battery",
"group/indicators"
],
"group/indicators": {
"orientation": "inherit",
"modules": [
"custom/cpugov",
"gamemode",
"custom/updates",
//"custom/updates",
"idle_inhibitor",
"custom/notification",
"privacy"
@@ -55,28 +55,9 @@
],
"drawer": {
"transition-duration": 500,
"transition-left-to-right": false
"transition-left-to-right": true
}
},
"group/brightness": {
"orientation": "inherit",
"modules": [
"custom/nightlight",
"backlight",
"backlight/slider"
],
"drawer": {
"transition-duration": 500,
"transition-left-to-right": false
}
},
"group/bat": {
"orientation": "inherit",
"modules": [
"battery",
"custom/batsaver"
]
},
"group/sys": {
"orientation": "inherit",
"modules": [
@@ -115,6 +96,7 @@
"on-scroll-down": "shift_down",
"on-click-middle": "alarm-clock-applet"
}
// "on-click": "evolution"
},
"user": {
"format": "{user}",
@@ -190,6 +172,7 @@
"format": "{icon}",
"icon-size": 16,
"tooltip-format": "{title:.100}",
// "sort-by-app-id": true,
"on-click": "activate",
"on-click-middle": "close",
"on-right-middle": "minimize-raise",
@@ -202,7 +185,7 @@
"firefox": "Firefox",
"VSCodium": "Code",
"codium": "Code",
"Alacritty": "Terminal"
"Alacritty": "Terminal",
},
"squash-list": [
"firefox"
@@ -215,9 +198,9 @@
"has-updates": "󱍷",
"updated": "󰂪"
},
"exec": "moonarch-waybar-updates",
"interval": 60,
"on-click": "foot env MOONUP_WAIT=1 moonarch-update"
"exec-if": "which waybar-module-pacman-updates",
"exec": "waybar-module-pacman-updates",
"on-click": "alacritty paru"
},
"custom/notification": {
"tooltip": true,
@@ -240,8 +223,10 @@
"escape": true
},
"cava": {
// "cava_config": "$XDG_CONFIG_HOME/cava/cava.conf",
"framerate": 30,
"autosens": 1,
//"sensitivity": 50,
"bars": 2,
"lower_cutoff_freq": 50,
"higher_cutoff_freq": 10000,
@@ -301,25 +286,16 @@
"<span color='#dd532e' size='8pt' rise='1.5pt'>█</span>"
]
},
"custom/nightlight": {
"exec": "moonarch-waybar-nightlight",
"return-type": "json",
"interval": "once",
"signal": 11,
"on-click": "moonarch-nightlight",
"format": "{}"
},
"custom/cpugov": {
"exec": "moonarch-waybar-cpugov",
"return-type": "json",
"interval": 60,
"signal": 10,
"interval": 5,
"on-click": "moonarch-cpugov"
},
"custom/gpu-usage": {
"exec": "moonarch-waybar-gpustat",
"return-type": "json",
"interval": 60
"restart-interval": 10
},
"battery": {
"bat": "BAT0",
@@ -330,30 +306,18 @@
},
"format": "{capacity}% {icon}",
"format-icons": [
"󰂎",
"󰁺",
"󰁻",
"󰁼",
"󰁽",
"󰁾",
"󰁿",
"󰂀",
"󰂁",
"󰂂",
"󰁹"
"",
"",
"",
"",
""
],
"max-length": 25,
"on-click": "moonarch-batsaver-toggle"
},
"custom/batsaver": {
"exec": "moonarch-waybar-batsaver",
"return-type": "json",
"interval": 30,
"signal": 9
"max-length": 25
},
"bluetooth": {
"format": "󰂯",
"format-disabled": "󰂲",
// "controller": "controller1", // specify the alias of the controller if there are more than 1 on the system
"format": " {status}",
"format-disabled": "󰂲", // an empty format will hide the module
"format-connected": "<small>{num_connections}</small>",
"tooltip-format": "{controller_alias}\t{controller_address}",
"tooltip-format-connected": "{controller_alias}\t{controller_address}\n\n{device_enumerate}\t{device_battery_percentage}%",
@@ -405,10 +369,13 @@
"niri/workspaces": {
"format": "{icon}",
"format-icons": {
// Named workspaces
// (you need to configure them in niri)
"browser": "",
"discord": "󰙯",
"chat": "<b></b>",
// Icons by state
"active": "",
"default": ""
}
@@ -432,56 +399,114 @@
"device": "intel_backlight"
},
"cffi/niri-workspaces-enhanced": {
// Make sure to set the path to the install location on your system
// "module_path": "~/.config/waybar/niri-workspaces-enhanced.so",
"module_path": "/usr/lib/waybar/libwaybar_niri_workspaces_enhanced.so",
// Format string for workspace labels. Available placeholders:
// {index} - Workspace index number
// {name} - Workspace name (might be empty)
// {index-and-name} - Index followed by name if present (e.g., "1 Work")
// {value} - Name if present, otherwise index
// {separator} - ": " when icons are present, "" when empty
// {window-icons} - Formatted icons for windows in workspace
"format": "{window-icons}",
// Apply separate styles to icons depending on current state
"window-icon-format": {
"default": "{icon}",
"urgent": "<span foreground='red'>{icon}</span>",
"focused": "<span foreground='blue'>{icon}</span>"
"focused": "<span foreground='blue'>{icon}</span>",
},
// A mapping from window app_id to icon. Note that this module does
// case-insensitive matching of app_ids, so capitalization doesn't matter.
"window-icons": {
"com.mitchellh.ghostty": "",
"darktable": "",
"foot": "",
"google-chrome": "",
"spotify": "",
"steam": ""
"steam": "",
},
"window-icon-default": "*"
// If no icon is found for a window, the default is used instead
"window-icon-default": "*",
},
"height": 40,
"cffi/niri-windows": {
// path where you placed the .so file
"module_path": "/usr/lib/waybar-niri-windows.so",
// configure the module's behavior
"options": {
// set the module mode
// "graphical" (default): draw a minimap of windows in the current workspace
// "text": draws symbols and a focus indicator for each column (mirrors v1 behavior)
"mode": "graphical",
// ======= graphical mode options =======
// when to show floating windows
// - "always": always show floating window view, even if there are no floating windows
// - "auto" (default): show floating window view if there are floating windows on the current workspace
// - "never": never show floating windows
"show-floating": "auto",
// pick where the floating windows be shown relative to tiled windows
// - "left": show floating windows on the left
// - "right" (default): show floating windows on the right
"floating-position": "right",
// set minimum size of windows, in pixels (default: 1, minimum: 1)
// if this value is too large to fit all windows (e.g. in a column with many windows),
// it will be reduced
"minimum-size": 1,
// set spacing between windows/columns, in pixels (default: 1, minimum: 0)
// if this value is too large, it will be reduced
"spacing": 1,
// set minimum size of windows, in pixels, to draw icons for (default: 0, minimum: 0)
// if unset or 0, icons will only be drawn for tiled windows that are the only one in their column
// if 1+, icons will be drawn for all windows where w >= icon-minimum-size and h >= icon-minimum-size
// icons must be set in the "rules" section below for this to have any effect
"icon-minimum-size": 0,
"column-borders": 0,
"floating-borders": 0,
"on-tile-click": "FocusWindow",
"on-tile-middle-click": "CloseWindow",
"on-tile-right-click": "",
// account for borders when calculating window sizes; see note below (default: 0, minimum: 0)
"column-borders": 0, // border on .column
"floating-borders": 0, // border on .floating
// trigger actions on tile click (see https://yalter.github.io/niri/niri_ipc/enum.Action.html for available actions)
// only actions that take a single window ID are supported
// set to an empty string to disable
"on-tile-click": "FocusWindow", // (default: FocusWindow)
"on-tile-middle-click": "CloseWindow", // (default: CloseWindow)
"on-tile-right-click": "", // (default: none)
// add CSS classes/icons to windows based on their App ID/Title (see `niri msg windows`)
// Go regular expression syntax is supported for app-id and title (see https://pkg.go.dev/regexp/syntax)
// rules are checked in the order they are defined - first match wins and checking stops
// set "continue" to true to also check and apply subsequent rules even if this rule matches
// if multiple rules with icons are applied, the first one will be used
// *icons are not drawn for floating windows by default*; set "icon-minimum-size" to enable (see above)
"rules": [
// .alacritty will be added to all windows with the App ID "Alacritty"
// will be drawn in windows that match
{ "app-id": "Alacritty", "class": "alacritty", "icon": "" },
// .firefox will be added to all windows with the App ID "firefox"
// subsequent rules are also checked and applied for firefox windows
{ "app-id": "firefox", "class": "firefox", "continue": true },
// .youtube-music will be added to all windows that have "YouTube Music" at the end of their title
// will be drawn in windows that match
{ "title": "YouTube Music$", "class": "youtube-music", "icon": "" }
],
// ======= text mode options =======
// customize the symbols used to draw the columns/windows
"symbols": {
"unfocused": "⋅",
"focused": "⊙",
"unfocused-floating": "",
"focused-floating": "⊛",
// text to display when there are no windows on the current workspace
// if this is an empty string (default), the module will be hidden when there are no windows
"empty": ""
}
},
"actions": {
// use niri IPC action names to trigger them (see https://yalter.github.io/niri/niri_ipc/enum.Action.html for available actions)
// any action that has no fields is supported
"on-scroll-up": "FocusColumnLeft",
"on-scroll-down": "FocusColumnRight"
// in graphical mode, don't configure click actions here—they're handled by the module above
}
}
}
+218 -142
View File
@@ -1,23 +1,22 @@
/* ABOUTME: System-wide Waybar styling for Moonarch. */
/* ABOUTME: Uses GTK theme colors via @theme_* variables, Catppuccin Mocha palette. */
* {
border: none;
font-family: "UbuntuSans Nerd Font", sans-serif;
font-family: "Ubuntu Nerd Font", sans-serif;
font-size: 13px;
color: alpha(@theme_text_color, 0.8);
}
window#waybar {
/*background: transparent;*/
background: alpha(@theme_selected_fg_color, 0.2);
}
/* --- Module areas --- */
.modules-left {
/*-----main groups----*/
.modules-right {
margin: 0;
padding-left: 5px;
padding-right: 0;
border-radius: 0;
}
.modules-center {
@@ -25,63 +24,106 @@ window#waybar {
border-radius: 0;
}
.modules-right {
margin: 0;
padding-left: 5px;
padding-right: 5px;
.modules-left {
margin: 0 0 0 5px;
/* background-color:alpha(@theme_selected_bg_color, 0.15);
background: @theme_bg_color */
border-radius: 0;
margin-left: 5px;
}
/* --- Module boxes --- */
.modules-left>widget>*,
.modules-center>widget>*,
.modules-right>widget>* {
padding: 0px 10px;
border-radius: 4px;
margin: 8px 2px;
background-color: alpha(@theme_selected_bg_color, 0.05);
}
/**
## ALL MODULES
**/
#clock,
#battery,
#backlight,
#cpu,
#memory,
#bluetooth,
#temperature,
#pulseaudio,
#tray,
#mode,
#idle_inhibitor,
#workspaces,
#custom-power,
#custom-menu,
#custom-media,
#custom-notification,
#custom-updates,
#custom-pacman,
#user,
#cava,
#custom-gpu-usage,
#custom-cpugov,
#power-profiles-daemon,
#privacy,
#gamemode,
#taskbar,
#window {
border-radius: 0;
margin: 0;
padding: 0;
background-color: transparent;
padding: 0px 10px;
}
/* --- Group children --- */
widget widget>* {
padding: 0 6px;
margin: 0;
background-color: transparent;
border-radius: 0;
}
/* --- Groups --- */
/**
* GROUPS
**/
#sys,
#stats,
#sound,
#brightness,
#indicators {
padding: 0 4px;
#net,
#sound {
margin: 0;
color: alpha(@theme_text_color, 0.5);
}
/**
* SYS
**/
#sys {
padding: 0 0px 0 10px;
}
#custom-power {
margin-left: 0;
margin-right: 0;
padding-left: 0;
}
#user {
padding-left: 0;
font-weight: bold;
}
#custom-weather {
padding-left: 5px;
}
#stats .drawer-child {
padding-left: 10px;
padding-right: 10px;
}
#tray {
padding: 0;
border-radius: 4px;
padding: 0px 8px 0px 8px;
margin: 8px 0px 8px 0px;
}
/* --- Sound --- */
#custom-notification {
padding-right: 0
}
/**
* SOUND
**/
#cava {
background-color: transparent;
padding-left: 0;
}
#pulseaudio-slider {
padding-left: 10px;
@@ -96,6 +138,7 @@ widget widget>* {
border: none;
box-shadow: none;
background-color: @insensitive_bg_color;
border: 1px solid alpha(@theme_selected_bg_color, 0.1);
}
#pulseaudio-slider trough {
@@ -106,95 +149,97 @@ widget widget>* {
}
#pulseaudio-slider highlight {
min-width: 5px;
border-radius: 5px;
background-color: alpha(@theme_selected_bg_color, 0.5);
background-color: #6c7086;
}
#pulseaudio.muted {
color: #cc3436;
#custom-updates {
padding-right: 5px;
padding-left: 5px;
}
/* --- Brightness --- */
#backlight-slider {
padding-left: 10px;
padding-right: 10px;
/**
* MISC
**/
#mpris {
border-radius: 0;
margin: 8px 10px 8px 0px;
padding: 0 10px;
}
#backlight-slider slider {
min-height: 0px;
min-width: 0px;
opacity: 0;
background-image: none;
border: none;
box-shadow: none;
background-color: @insensitive_bg_color;
}
#backlight-slider trough {
min-height: 4px;
min-width: 80px;
border-radius: 5px;
background-color: alpha(@theme_selected_bg_color, 0.3);
}
#backlight-slider highlight {
min-width: 5px;
border-radius: 5px;
background-color: alpha(@theme_selected_bg_color, 0.5);
}
/* --- Battery group (hidden when empty, requires Waybar PR #4941) --- */
#bat.empty,
#bat.empty * {
min-width: 0;
min-height: 0;
/**
* WORKSPACES
**/
/*#workspaces {
padding: 0;
margin: 0;
border: none;
font-size: 0;
opacity: 0;
border-radius: 0
}
/* --- Battery --- */
#battery {
padding: 0;
}
#battery.charging {
color: #2dcc36;
}
#battery.warning:not(.charging) {
color: #e6e600;
}
#battery.critical:not(.charging) {
color: #cc3436;
}
#custom-batsaver {
font-size: 10px;
}
/* --- Workspaces --- */
#workspaces button {
margin: 8px 2px;
padding: 0 2px 0 0;
padding: 0px 10px 0px 8px;
margin: 8px 2px 8px 0px;
outline: none;
border-radius: 4px;
background-color: alpha(@theme_selected_bg_color, 0.05);
}
#workspaces button:last-child {
margin-right: 0;
}
#workspaces button.empty,
#workspaces button.active.empty {
color: inherit;
padding: 0px 0px 0px 8px;
}
#workspaces button.active {
background-color: transparent;
padding: 0px 10px 0px 8px;
}
#workspaces button.visible {
background-color: alpha(@theme_selected_bg_color, 0.1);
}
#workspaces button.urgent {
color: #cc3436;
}
#workspaces button:hover {
background-color: alpha(@theme_selected_bg_color, 0.05);
box-shadow: inherit;
text-shadow: inherit;
}*/
#workspaces {
padding: 0;
border-radius: 0;
}
#workspaces button {
margin: 8px 2px 8px 0px;
padding: 0;
outline: none;
border-radius: 4px;
background-color: alpha(@theme_selected_bg_color, 0.05);
}
#workspaces button:last-child {
margin-right: 0;
}
#workspaces button.empty,
#workspaces button.active.empty {
color: inherit;
}
#workspaces button.active {}
#workspaces button.visible {
background-color: alpha(@theme_selected_bg_color, 0.1);
}
@@ -209,44 +254,24 @@ widget widget>* {
text-shadow: inherit;
}
/* --- Taskbar --- */
#taskbar {
font-size: 8px;
opacity: 1;
/**
## INDICATORS
**/
#pulseaudio.muted {
color: #cc3436;
}
#taskbar.empty {
opacity: 0;
#battery.charging {
color: #2dcc36;
}
#taskbar button {
padding: 3px 4px 0 10px;
border-bottom: 3px solid transparent;
border-radius: 0;
#battery.warning:not(.charging) {
color: #e6e600;
}
#taskbar button.empty {
opacity: 0;
}
#taskbar button:hover {
background-color: alpha(@theme_selected_bg_color, 0.1);
}
#taskbar button.active {
background-color: alpha(@theme_selected_bg_color, 0.1);
border-bottom: 3px solid @theme_selected_bg_color;
padding-bottom: 0px;
opacity: 1;
}
/* --- Misc --- */
#mpris {
border-radius: 0;
margin: 8px 10px 8px 0px;
padding: 0 10px;
#battery.critical:not(.charging) {
color: #cc3436;
}
#temperature.critical {
@@ -257,14 +282,65 @@ widget widget>* {
color: alpha(@theme_selected_bg_color, 0.9);
}
#custom-nightlight.on {
color: #f9e2af;
#indicators {
padding-right: 5px;
padding-left: 5px;
border-radius: 0;
}
/**
* TASKBAR
**/
#taskbar {
font-size: 8px;
margin: 0 0 0 5px;
opacity: 1;
padding: 0;
border-radius: 0;
}
#taskbar.empty {
opacity: 0;
}
#taskbar button {
padding: 3px 10px 3px 10px;
transition: 100ms border ease-in-out;
border-radius: 0;
font-size: 8px;
}
#taskbar button:not(:first-child) {
border-radius: 0px;
}
#taskbar button:first-child {}
#taskbar button:last-child {}
#taskbar button:last-child:first-child {}
#taskbar button.empty {
opacity: 0;
}
#taskbar button:hover {
background-color: alpha(@theme_selected_bg_color, 0.1);
}
#taskbar button.active {
background-color: alpha(@theme_selected_bg_color, 0.05);
opacity: 1;
}
menu {
border-radius: 4px;
}
menuitem {
border-radius: 4px;
}
-7
View File
@@ -11,7 +11,6 @@ colloid-catppuccin-theme-git
otf-openmoji
# Niri / Wayland Extras
walker-bin
elephant-desktopapplications-bin
elephant-clipboard-bin
elephant-bluetooth-bin
@@ -30,11 +29,5 @@ wl-color-picker
blueberry
waterfox-bin
# Media
mpv-modernz-git
mpv-thumbfast-git
# System & Tools
auto-cpufreq
stasis
wayland-pipewire-idle-inhibit
+6 -2
View File
@@ -63,7 +63,6 @@ waybar
swaync
cliphist
wl-clipboard
wlsunset
libnotify
upower
awww
@@ -91,12 +90,17 @@ viewnior
# Development
git
glab
go
neovim
npm
rustup
# System
docker
docker-compose
fwupd
plocate
rebuild-detector
snapper
snap-pac
ufw
+7 -1
View File
@@ -1,6 +1,6 @@
#!/bin/bash
# ABOUTME: Shared helper functions and constants for Moonarch scripts.
# ABOUTME: Sourced by post-install.sh.
# ABOUTME: Sourced by post-install.sh, update.sh and transform.sh.
# Path constants — BASH_SOURCE[1] resolves to the calling script, not lib.sh itself.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[1]}")" && pwd)"
@@ -8,6 +8,7 @@ PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
OFFICIAL_PACKAGES="$PROJECT_DIR/packages/official.txt"
AUR_PACKAGES="$PROJECT_DIR/packages/aur.txt"
DEFAULTS_DIR="$PROJECT_DIR/defaults"
USER_DEFAULTS="/usr/share/moonarch/user-defaults"
# --- Helper functions ---
@@ -23,6 +24,11 @@ read_packages() {
grep -v '^\s*#' "$1" | grep -v '^\s*$'
}
confirm() {
read -r -p "$1 [y/N] " response
[[ "$response" =~ ^[yY]$ ]]
}
# --- Prerequisite checks ---
check_not_root() {
-301
View File
@@ -1,301 +0,0 @@
#!/bin/bash
# ABOUTME: Moonarch system health checker — verifies services, configs, packages and paths.
# ABOUTME: Shipped as /usr/bin/moonarch-doctor (alias: moondoc) by the moonarch-git package.
set -uo pipefail
# --- Output helpers ---
PASS=0
FAIL=0
WARN=0
pass() {
echo -e " \e[1;32m✓\e[0m $*"
((PASS++))
}
fail() {
echo -e " \e[1;31m✗\e[0m $*"
((FAIL++))
}
warn() {
echo -e " \e[1;33m⚠\e[0m $*"
((WARN++))
}
section() {
echo
echo -e "\e[1;34m[$1]\e[0m"
}
# --- Check functions ---
check_system_service() {
local svc="$1"
local type
type=$(systemctl show "$svc" --property=Type 2>/dev/null | cut -d= -f2)
if systemctl is-enabled "$svc" &>/dev/null; then
if systemctl is-active "$svc" &>/dev/null; then
pass "$svc (enabled, active)"
elif [[ "$type" == "oneshot" ]]; then
# Oneshot services are inactive after completion — check if they succeeded
local result
result=$(systemctl show "$svc" --property=Result 2>/dev/null | cut -d= -f2)
if [[ "$result" == "success" ]]; then
pass "$svc (enabled, oneshot completed)"
else
fail "$svc (enabled, oneshot failed: $result)"
fi
else
fail "$svc (enabled, NOT active)"
fi
else
fail "$svc (NOT enabled)"
fi
}
check_user_service() {
local svc="$1"
if systemctl --user is-enabled "$svc" &>/dev/null; then
if systemctl --user is-active "$svc" &>/dev/null; then
pass "$svc (enabled, active)"
else
# User services may be inactive if not in a graphical session
warn "$svc (enabled, not active — may need graphical session)"
fi
else
fail "$svc (NOT enabled)"
fi
}
check_config_match() {
local deployed="$1"
local source="$2"
local label="${deployed}"
if [[ ! -f "$deployed" ]]; then
fail "$label (missing)"
return
fi
if [[ ! -f "$source" ]]; then
warn "$label (exists, but no source to compare at $source)"
return
fi
local hash_deployed hash_source
hash_deployed=$(sha256sum "$deployed" | cut -d' ' -f1)
hash_source=$(sha256sum "$source" | cut -d' ' -f1)
if [[ "$hash_deployed" == "$hash_source" ]]; then
pass "$label"
else
warn "$label (differs from moonarch default)"
fi
}
# --- Header ---
echo -e "\e[1;34m"
echo " Moonarch Doctor"
echo -e "\e[0m"
# --- 1. Packages ---
section "Packages"
OFFICIAL="/usr/share/moonarch/official.txt"
AUR="/usr/share/moonarch/aur.txt"
# Hoist INSTALLED so the AUR block below can use it even if OFFICIAL is absent —
# otherwise `set -u` aborts the script when $INSTALLED is referenced unset.
INSTALLED=$(pacman -Qq 2>/dev/null)
if [[ -f "$OFFICIAL" ]]; then
MISSING_OFFICIAL=()
while IFS= read -r pkg; do
[[ "$pkg" =~ ^[[:space:]]*# ]] && continue
[[ -z "${pkg// }" ]] && continue
if ! echo "$INSTALLED" | grep -qx "$pkg"; then
MISSING_OFFICIAL+=("$pkg")
fi
done < "$OFFICIAL"
if [[ ${#MISSING_OFFICIAL[@]} -eq 0 ]]; then
pass "All official packages installed"
else
fail "Missing official packages: ${MISSING_OFFICIAL[*]}"
fi
else
warn "$OFFICIAL not found (moonarch-git not installed?)"
fi
if [[ -f "$AUR" ]]; then
MISSING_AUR=()
while IFS= read -r pkg; do
[[ "$pkg" =~ ^[[:space:]]*# ]] && continue
[[ -z "${pkg// }" ]] && continue
if ! echo "$INSTALLED" | grep -qx "$pkg"; then
MISSING_AUR+=("$pkg")
fi
done < "$AUR"
if [[ ${#MISSING_AUR[@]} -eq 0 ]]; then
pass "All AUR packages installed"
else
fail "Missing AUR packages: ${MISSING_AUR[*]}"
fi
else
warn "$AUR not found (moonarch-git not installed?)"
fi
ORPHANS=$(pacman -Qdtq 2>/dev/null || true)
if [[ -z "$ORPHANS" ]]; then
pass "No orphaned packages"
else
ORPHAN_COUNT=$(echo "$ORPHANS" | wc -l)
warn "$ORPHAN_COUNT orphaned package(s) (run moonup to clean)"
fi
# --- 2. System Services ---
section "System Services"
for svc in NetworkManager bluetooth greetd systemd-timesyncd ufw auto-cpufreq; do
check_system_service "$svc"
done
# Battery conservation service (laptop only)
if [[ -f /sys/class/power_supply/BAT0/charge_control_end_threshold ]]; then
check_system_service moonarch-batsaver
else
pass "moonarch-batsaver (skipped — no battery threshold support)"
fi
# --- 3. User Services ---
section "User Services"
if [[ $EUID -eq 0 ]]; then
warn "Running as root — skipping user service checks"
elif pacman -Qq moonarch-git &>/dev/null; then
EXPECTED_SVCS=()
while IFS= read -r svc_file; do
EXPECTED_SVCS+=("$(basename "$svc_file" .service)")
done < <(pacman -Qql moonarch-git | grep -E '^/etc/systemd/user/[^/]+\.service$')
# Services enabled by post-install.sh from other packages
EXPECTED_SVCS+=(stasis wayland-pipewire-idle-inhibit)
for svc in "${EXPECTED_SVCS[@]}"; do
check_user_service "$svc"
done
else
warn "moonarch-git not installed — skipping user service checks"
fi
# --- 4. Config Files ---
section "Config Files"
SRC="/usr/share/moonarch"
check_config_match "/etc/xdg/foot/foot.ini" "$SRC/foot/foot.ini"
check_config_match "/etc/xdg/waybar/style.css" "$SRC/waybar/style.css"
check_config_match "/etc/xdg/swaync/config.json" "$SRC/swaync/config.json"
check_config_match "/etc/xdg/swaync/style.css" "$SRC/swaync/style.css"
check_config_match "/etc/greetd/config.toml" "$SRC/greetd/config.toml"
check_config_match "/etc/greetd/niri-greeter.kdl" "$SRC/greetd/niri-greeter.kdl"
check_config_match "/etc/moongreet/moongreet.toml" "$SRC/moongreet/moongreet.toml"
# --- 5. Helper Scripts ---
section "Helper Scripts"
if pacman -Qq moonarch-git &>/dev/null; then
EXPECTED_SCRIPTS=()
while IFS= read -r script; do
EXPECTED_SCRIPTS+=("$(basename "$script")")
done < <(pacman -Qql moonarch-git | grep -E '^/usr/bin/moonarch-[^/]+$')
MISSING_SCRIPTS=()
for script in "${EXPECTED_SCRIPTS[@]}"; do
if [[ ! -x "/usr/bin/$script" ]]; then
MISSING_SCRIPTS+=("$script")
fi
done
if [[ ${#MISSING_SCRIPTS[@]} -eq 0 ]]; then
pass "All ${#EXPECTED_SCRIPTS[@]} helper scripts present"
else
fail "Missing scripts: ${MISSING_SCRIPTS[*]}"
fi
else
warn "moonarch-git not installed — skipping helper script checks"
fi
# Symlinks
for link in moonup moondoc; do
if [[ -L "/usr/bin/$link" ]]; then
pass "$link symlink"
else
fail "$link symlink (missing)"
fi
done
# --- 6. System Config ---
section "System Config"
# UFW (check via systemd, no sudo needed)
if command -v ufw &>/dev/null; then
if systemctl is-active ufw &>/dev/null; then
pass "UFW active"
else
fail "UFW not active"
fi
else
fail "UFW not installed"
fi
# Pacman moonarch repo
if grep -q '^\[moonarch\]' /etc/pacman.conf 2>/dev/null; then
pass "Pacman [moonarch] repo configured"
else
fail "Pacman [moonarch] repo missing from /etc/pacman.conf"
fi
# Default shell
USER_SHELL=$(getent passwd "$USER" | cut -d: -f7)
if [[ "$USER_SHELL" == */zsh ]]; then
pass "Default shell: zsh"
else
warn "Default shell: $USER_SHELL (expected zsh)"
fi
# --- 7. Directories ---
section "Directories"
if [[ -d /var/lib/moonarch ]]; then
DIR_PERMS=$(stat -c '%a' /var/lib/moonarch)
DIR_GROUP=$(stat -c '%G' /var/lib/moonarch)
if [[ "$DIR_GROUP" == "wheel" ]]; then
pass "/var/lib/moonarch/ (group: wheel, mode: $DIR_PERMS)"
else
warn "/var/lib/moonarch/ (group: $DIR_GROUP, expected wheel)"
fi
else
warn "/var/lib/moonarch/ missing (created on first battery toggle)"
fi
# --- Summary ---
echo
TOTAL=$((PASS + FAIL + WARN))
echo -e "\e[1;34m[Summary]\e[0m $TOTAL checks: \e[1;32m$PASS passed\e[0m, \e[1;31m$FAIL failed\e[0m, \e[1;33m$WARN warnings\e[0m"
if [[ $FAIL -gt 0 ]]; then
exit 1
fi
+22 -55
View File
@@ -4,37 +4,6 @@
set -euo pipefail
_t() {
# _t "english" "deutsch" — picks by $LANG
case "${LANG%%.*}" in
de_*) printf '%s' "$2" ;;
*) printf '%s' "$1" ;;
esac
}
# Pause on exit when launched from a GUI (Waybar sets MOONUP_WAIT=1).
# Runs via trap so it fires even when `set -e` aborts the script mid-way.
# Reads from /dev/tty because stdin may have been drained/closed by pacman/paru.
_pause_on_exit() {
local rc=$?
if [[ -n "${MOONUP_WAIT:-}" ]]; then
echo
if (( rc != 0 )); then
echo -e "\e[1;31m[Moonarch ERROR]\e[0m $(_t "Exited with error (code $rc)" "Mit Fehler beendet (Code $rc)")" >&2
fi
read -n 1 -s -r -p "$(_t "Press any key to close..." "Beliebige Taste drücken zum Schließen …")" < /dev/tty || true
echo
fi
}
trap _pause_on_exit EXIT
# --- i18n ---
# Reuse pacman's gettext catalog for prompts that match upstream msgids.
# For moonarch-specific strings fall back to _t() inline translation.
export TEXTDOMAIN=pacman
export TEXTDOMAINDIR=/usr/share/locale
YN=$(gettext "[Y/n]")
# --- Helper functions ---
log() {
@@ -50,10 +19,8 @@ read_packages() {
}
confirm() {
# 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]$ ]]
read -r -p "$1 [y/N] " response
[[ "$response" =~ ^[yY]$ ]]
}
OFFICIAL_PACKAGES="/usr/share/moonarch/official.txt"
@@ -62,79 +29,79 @@ AUR_PACKAGES="/usr/share/moonarch/aur.txt"
# --- Prerequisites ---
if [[ $EUID -eq 0 ]]; then
err "$(_t "Do NOT run as root. The script uses sudo where needed." "Nicht als root ausführen. Das Script verwendet sudo wo nötig.")"
err "Do NOT run as root. The script uses sudo where needed."
exit 1
fi
# --- 1. Update system packages ---
log "=== $(gettext $'Starting full system upgrade...\n' | tr -d '\n.') ==="
log "=== Update system packages ==="
if confirm "$(_t "Run pacman -Syu?" "pacman -Syu ausführen?")"; then
if confirm "Run pacman -Syu?"; then
sudo pacman -Syu
else
log "$(_t "System update skipped." "Systemaktualisierung übersprungen.")"
log "System update skipped."
fi
if command -v paru &>/dev/null; then
if confirm "$(_t "Update AUR packages (paru -Sua)?" "AUR-Pakete aktualisieren (paru -Sua)?")"; then
if confirm "Update AUR packages (paru -Sua)?"; then
paru -Sua
else
log "$(_t "AUR update skipped." "AUR-Aktualisierung übersprungen.")"
log "AUR update skipped."
fi
fi
# --- 2. Reconcile package lists ---
log "=== $(_t "Reconcile package lists" "Paketlisten abgleichen") ==="
log "=== Reconcile package lists ==="
if [[ -f "$OFFICIAL_PACKAGES" ]]; then
MISSING_OFFICIAL=$(comm -23 <(read_packages "$OFFICIAL_PACKAGES" | sort) <(pacman -Qq | sort) || true)
MISSING_OFFICIAL=$(comm -23 <(read_packages "$OFFICIAL_PACKAGES" | sort) <(pacman -Qqe | sort) || true)
if [[ -n "$MISSING_OFFICIAL" ]]; then
log "$(_t "Missing official packages:" "Fehlende offizielle Pakete:")"
log "Missing official packages:"
echo "$MISSING_OFFICIAL"
if confirm "$(gettext 'Proceed with installation?')"; then
if confirm "Install?"; then
# shellcheck disable=SC2086
sudo pacman -S --needed --noconfirm $MISSING_OFFICIAL
fi
else
log "$(_t "All official packages installed." "Alle offiziellen Pakete installiert.")"
log "All official packages installed."
fi
fi
if [[ -f "$AUR_PACKAGES" ]] && command -v paru &>/dev/null; then
MISSING_AUR=$(comm -23 <(read_packages "$AUR_PACKAGES" | sort) <(pacman -Qq | sort) || true)
MISSING_AUR=$(comm -23 <(read_packages "$AUR_PACKAGES" | sort) <(pacman -Qqe | sort) || true)
if [[ -n "$MISSING_AUR" ]]; then
log "$(_t "Missing AUR packages:" "Fehlende AUR-Pakete:")"
log "Missing AUR packages:"
echo "$MISSING_AUR"
if confirm "$(gettext 'Proceed with installation?')"; then
if confirm "Install?"; then
# shellcheck disable=SC2086
paru -S --needed --noconfirm $MISSING_AUR
fi
else
log "$(_t "All AUR packages installed." "Alle AUR-Pakete installiert.")"
log "All AUR packages installed."
fi
fi
# --- 3. Orphaned packages ---
log "=== $(_t "Orphaned packages" "Verwaiste Pakete") ==="
log "=== Orphaned packages ==="
ORPHANS=$(pacman -Qdtq 2>/dev/null || true)
if [[ -n "$ORPHANS" ]]; then
log "$(_t "Orphaned packages found:" "Verwaiste Pakete gefunden:")"
log "Orphaned packages found:"
echo "$ORPHANS"
if confirm "$(gettext 'Do you want to remove these packages?')"; then
if confirm "Remove?"; then
# shellcheck disable=SC2086
sudo pacman -Rsn --noconfirm $ORPHANS
fi
else
log "$(_t "No orphaned packages." "Keine verwaisten Pakete.")"
log "No orphaned packages."
fi
# --- Done ---
log ""
log "============================================"
log " $(_t "Moonarch update complete!" "Moonarch-Aktualisierung abgeschlossen!")"
log " Moonarch update complete!"
log "============================================"
+58 -54
View File
@@ -14,16 +14,8 @@ check_pacman
# --- Install paru (AUR Helper) ---
if ! command -v paru &>/dev/null; then
log "Installing paru build dependencies..."
sudo pacman -S --needed --noconfirm base-devel rust
log "Installing paru..."
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
sudo pacman -S --needed --noconfirm paru
else
log "paru already installed."
fi
@@ -44,19 +36,8 @@ else
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"
@@ -66,25 +47,29 @@ else
log " ~ Registry key already imported."
fi
rm -f "$KEY_FILE"
trap - EXIT
# --- Install moonarch-git from the Arch registry ---
# --- Set up Moonarch custom paru repo ---
log "Setting up Moonarch paru repo..."
PARU_CONF="$HOME/.config/paru/paru.conf"
mkdir -p "$(dirname "$PARU_CONF")"
if ! grep -q '\[moonarch\]' "$PARU_CONF" 2>/dev/null; then
cat >> "$PARU_CONF" <<'EOCONF'
[moonarch]
Url = https://gitea.moonarch.de/nevaforget/moonarch-pkgbuilds.git
EOCONF
log " + Moonarch repo added to paru.conf."
else
log " ~ Moonarch repo already in paru.conf."
fi
paru -Syu --pkgbuilds --noconfirm
# --- Install moonarch-git (pulls in all configs, scripts, themes as dependencies) ---
log "Installing moonarch-git package..."
sudo pacman -Sy --noconfirm
paru -S --needed --noconfirm moonarch-git
# --- Install AUR extras (cannot be hard depends of moonarch-git) ---
if [[ -f "$AUR_PACKAGES" ]]; then
log "Installing AUR packages from $AUR_PACKAGES..."
# shellcheck disable=SC2046
paru -S --needed --noconfirm $(read_packages "$AUR_PACKAGES")
else
err "AUR package list not found: $AUR_PACKAGES"
exit 1
fi
# --- User-level GTK4 symlinks for libadwaita apps ---
THEME_NAME="Colloid-Grey-Dark-Catppuccin"
@@ -110,22 +95,39 @@ 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'
# --- Install user config defaults ---
log "Installing user config defaults to ~/.config/..."
if [[ -d "$USER_DEFAULTS" ]]; then
# Each subdirectory in user-defaults/ corresponds to a ~/.config/ directory.
# Files are only copied if they don't exist yet (no overwriting).
for src_dir in "$USER_DEFAULTS"/*/; do
app_name="$(basename "$src_dir")"
dest_dir="$HOME/.config/$app_name"
mkdir -p "$dest_dir"
find "$src_dir" -type f -print0 | while IFS= read -r -d '' src_file; do
rel_path="${src_file#"$src_dir"}"
dest_file="$dest_dir/$rel_path"
if [[ ! -f "$dest_file" ]]; then
mkdir -p "$(dirname "$dest_file")"
cp "$src_file" "$dest_file"
log " + $app_name/$rel_path"
else
log " ~ $app_name/$rel_path already exists, skipped."
fi
done
done
else
err "USER_DEFAULTS not found: $USER_DEFAULTS — skipping user config defaults."
fi
# --- Zsh user config ---
if [[ ! -f "$HOME/.zshrc" ]]; then
log "No ~/.zshrc found — sourcing Moonarch defaults."
echo "source /etc/zsh/zshrc.moonarch" > "$HOME/.zshrc"
fi
# --- Seed Stasis user config ---
#
# Stasis reads ~/.config/stasis/stasis.rune (or /etc/stasis/stasis.rune as
# fallback) but never /etc/xdg/. Without a user config it writes its own
# upstream default on first start. Seed Moonarch's template so the bootstrap
# sees an existing file and skips. Never overwrite an existing user config.
if [[ ! -f "$HOME/.config/stasis/stasis.rune" && -f /etc/xdg/stasis/stasis.rune ]]; then
log "Seeding Moonarch stasis config to user home."
install -Dm644 /etc/xdg/stasis/stasis.rune "$HOME/.config/stasis/stasis.rune"
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"
fi
# --- Enable systemd user services ---
@@ -133,14 +135,7 @@ fi
log "Enabling systemd user services..."
USER_SERVICES=(
"kanshi"
"stasis"
"wayland-pipewire-idle-inhibit"
"cliphist-text"
"cliphist-image"
)
# wlsunset deliberately excluded: nightlight is a user-toggle (off by default).
# Enabling it system-wide would create a global-scope WantedBy symlink that
# overrides any user-scope `systemctl --user disable`.
for service in "${USER_SERVICES[@]}"; do
if systemctl --user cat "${service}.service" &>/dev/null; then
@@ -157,6 +152,7 @@ log "Enabling services..."
SERVICES=(
"NetworkManager"
"bluetooth"
"docker"
"greetd"
"systemd-timesyncd"
"ufw"
@@ -196,6 +192,13 @@ for entry in /boot/loader/entries/*.conf; do
fi
done
# --- Docker-Gruppe ---
if ! groups | grep -q docker; then
log "Adding user to docker group..."
sudo usermod -aG docker "$USER"
fi
# --- Screenshots directory ---
mkdir -p "$HOME/Pictures/Screenshots"
@@ -211,5 +214,6 @@ log ""
log "Next steps:"
log " 1. Reboot"
log " 2. Place wallpapers in ~/Pictures/Wallpaper/"
log " 3. User overrides in ~/.config/"
log " 3. rustup default stable"
log " 4. User overrides in ~/.config/ or ~/.zshrc.d/"
log ""
+424
View File
@@ -0,0 +1,424 @@
#!/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..."
KEY_FILE=$(mktemp)
curl -sf https://gitea.moonarch.de/api/packages/nevaforget/arch/repository.key -o "$KEY_FILE"
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"
# Moonarch custom paru repo
log "Setting up Moonarch paru repo..."
PARU_CONF="$HOME/.config/paru/paru.conf"
mkdir -p "$(dirname "$PARU_CONF")"
if ! grep -q '\[moonarch\]' "$PARU_CONF" 2>/dev/null; then
cat >> "$PARU_CONF" <<'EOCONF'
[moonarch]
Url = https://gitea.moonarch.de/nevaforget/moonarch-pkgbuilds.git
EOCONF
log " + Moonarch repo added to paru.conf."
else
log " ~ Moonarch repo already in paru.conf."
fi
paru -Sy --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
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
# 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"
# Deploy user defaults (overwrite, no existence check)
log "Deploying user config defaults to ~/.config/ (overwrite)..."
if [[ -d "$USER_DEFAULTS" ]]; then
for src_dir in "$USER_DEFAULTS"/*/; do
app_name="$(basename "$src_dir")"
dest_dir="$HOME/.config/$app_name"
mkdir -p "$dest_dir"
find "$src_dir" -type f -print0 | while IFS= read -r -d '' src_file; do
rel_path="${src_file#"$src_dir"}"
dest_file="$dest_dir/$rel_path"
mkdir -p "$(dirname "$dest_file")"
cp "$src_file" "$dest_file"
log " + $app_name/$rel_path"
done
done
fi
# 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"
)
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"
"docker"
"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
# Docker group
if ! groups | grep -q docker; then
log "Adding user to docker group..."
sudo usermod -aG docker "$USER"
fi
# 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
@@ -0,0 +1,14 @@
#!/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