Compare commits

..

78 Commits

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

- waybar/style.css: "Ubuntu Nerd Font" -> "UbuntuSans Nerd Font"
- conf.d/65-moonarch-fonts.conf: sans-serif/monospace, binding="strong" (weak prefer ranks behind the generic fallback)
- document fontconfig defaults in CLAUDE.md and DECISIONS.md
2026-06-08 13:39:25 +02:00
nevaforget dc47d1a6ec fix(nightlight): default OFF, drop wlsunset from auto-enable list
Coordinated fix with moonarch-pkgbuilds: post-install.sh enabled
wlsunset by default, while the PKGBUILD shipped a global-scope
WantedBy symlink. Together that made the toggle's user-scope disable
a no-op — filter persisted across reboots regardless of user intent.

Removing wlsunset from USER_SERVICES makes "off" the install default;
the toggle now works in user scope only, where disable can take effect.
2026-05-04 14:15:58 +02:00
nevaforget 952776c4f9 batsaver: switch to pkexec helper, drop broken udev permission hack
Update PKGBUILD version / update-pkgver (push) Successful in 2s
The wheel-write-via-udev approach for charge_control_end_threshold has
been broken since 2026-04-08: the audit-remediation commit added
ACTION=="add" to the rule, but the threshold attribute doesn't exist
yet at the add event on Lenovo, so chmod fails silently and permissions
are never set. moonarch-batsaver-toggle has been returning Permission
denied since.

Replace the udev-rule approach with a pkexec helper:

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

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

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

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

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

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

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

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

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

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

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

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

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

confirm() switches to pacman-style: :: prefix, default Y, accepts
y/Y/j/J. No PKGBUILD change — gettext ships with base.
2026-04-19 15:47:28 +02:00
nevaforget ee85b87d3a fix: signal waybar after wlsunset starts to update nightlight icon
Waybar starts before wlsunset (due to ExecStartPre sleep) and only
checks the service status once. ExecStartPost sends SIGRTMIN+11 so
waybar refreshes the nightlight module after wlsunset is ready.
2026-04-16 10:36:05 +02:00
nevaforget fcac91b540 fix: add polkit rule to allow greeter user to reboot and shutdown
The greetd greeter session is inactive in logind, so polkit defaults
require admin authentication for power actions. This rule grants the
greeter user permission for reboot and power-off without authentication.
2026-04-15 14:23:49 +02:00
nevaforget 324dda0548 fix: remove fixed notification popup height to prevent scrollbar
Update PKGBUILD version / update-pkgver (push) Successful in 3s
notification-window-height was set to 150px, causing long notifications
to be clipped with a scrollbar. Set to -1 (compositor-managed) so popups
adapt to content height.
2026-04-15 14:08:53 +02:00
nevaforget 0433f08f08 feat: manage wlsunset via systemd user service
Update PKGBUILD version / update-pkgver (push) Successful in 4s
Move nightlight from niri spawn-at-startup to a systemd user service
with After=kanshi.service to ensure all outputs are configured before
wlsunset starts. Toggle now uses enable/disable --now for persistent
state across reboots.
2026-04-14 17:42:24 +02:00
nevaforget 9bc753e092 fix: use niri include instead of copying system config to user dir
Update PKGBUILD version / update-pkgver (push) Successful in 2s
transform.sh was hard-copying /etc/xdg/niri/config.kdl to ~/.config/niri/,
causing the user config to go stale after system updates. Now seeds a minimal
user config with `include "/etc/xdg/niri/config.kdl"` so system defaults
always stay current and users can add overrides below the include.
2026-04-11 21:03:42 +02:00
nevaforget a360d12bde fix: reduce swaync notification size and cap popup height
Update PKGBUILD version / update-pkgver (push) Successful in 3s
Smaller font (14px → 12px), smaller icon (64px → 36px via CSS variable),
and notification-window-height limit (150px) to prevent oversized popups
from long notification bodies.
2026-04-10 16:15:48 +02:00
nevaforget 97e4b81930 docs: add moonarch-doctor and shorthand aliases to README and CLAUDE.md
Update PKGBUILD version / update-pkgver (push) Failing after 3s
2026-04-10 08:21:38 +02:00
nevaforget b8753bf84f fix: remove docker from defaults, fix cliphist-image ordering cycle
Docker is a dev dependency, not a desktop environment default. Remove
from package list, archinstall config, services and README.

Fix systemd ordering cycle that prevented cliphist-image from starting:
cliphist-text had After=graphical-session.target which combined with
PartOf= and cliphist-image's After=cliphist-text created a cycle.
2026-04-10 08:18:55 +02:00
nevaforget a55c7ea9d1 feat: add moonarch-doctor system health checker
Diagnostic script that verifies services, configs, packages and paths
against the expected moonarch system state. Reports pass/fail/warn with
colored output and summary. Deployed as moonarch-doctor (alias: moondoc).
2026-04-10 08:18:45 +02:00
nevaforget ab066724f0 fix: keep terminal open after moonarch-update completes
Update PKGBUILD version / update-pkgver (push) Successful in 3s
Add "press any key" prompt when running interactively (stdin is a TTY).
Remove dead wrapper in defaults/bin/ that was overwritten by PKGBUILD.
2026-04-10 07:33:46 +02:00
nevaforget 2363e76b4a feat: add moonarch-waybar config merger wrapper
Update PKGBUILD version / update-pkgver (push) Successful in 4s
Waybar's include directive cannot merge arrays, making per-machine
module customization impossible without duplicating the entire config.

moonarch-waybar merges an optional ~/.config/waybar/userconfig with
the system config, supporting prepend/append on module arrays and
object merge for module definitions. Generates user style.css with
@import of system styles on first run.

System waybar config converted from JSONC to valid JSON for jq
compatibility. Niri startup and hotkey updated to use the wrapper.
2026-04-09 17:04:24 +02:00
nevaforget 4dd8aae2f0 fix: change drawer transition direction for sound and brightness groups
Update PKGBUILD version / update-pkgver (push) Successful in 2s
Open drawers to the right instead of left, matching their position
on the right side of the bar.
2026-04-09 12:15:59 +02:00
nevaforget 47ae8d5d51 docs: add wlsunset nightlight to README
Update PKGBUILD version / update-pkgver (push) Successful in 2s
Add nightlight scripts to project structure, wlsunset to startup
sequence diagram, and Night Light entry to component choice table.
2026-04-09 12:10:05 +02:00
nevaforget 4c609135e9 feat: add wlsunset nightlight toggle with Waybar module
Update PKGBUILD version / update-pkgver (push) Successful in 3s
Add blue light filter support via wlsunset, toggleable through a Waybar
module in the brightness group. Nightlight icon is the primary element,
backlight slider expands on click.

- Add wlsunset package to official.txt
- Add moonarch-nightlight toggle script (kill/restart wlsunset)
- Add moonarch-waybar-nightlight status script (JSON output)
- Add custom/nightlight module to group/brightness in Waybar config
- Add Catppuccin Yellow highlight for active nightlight state
- Add wlsunset autostart in Niri config (5000K night temperature)
2026-04-09 12:08:35 +02:00
nevaforget f6869c9cea fix: detect implicitly installed packages in moonarch-update
Update PKGBUILD version / update-pkgver (push) Successful in 3s
Use pacman -Qq (all installed) instead of -Qqe (explicit only) so
packages installed as dependencies are not falsely reported as missing.
2026-04-09 11:47:51 +02:00
nevaforget 48b0de071e feat: add brightness slider to Waybar backlight module
Update PKGBUILD version / update-pkgver (push) Successful in 2s
Wrap backlight in a drawer group with backlight/slider, mirroring the
existing sound group pattern. Slider appears on hover/click.
2026-04-09 11:43:36 +02:00
nevaforget b6beabe500 feat: add moonarch-update wrapper for interactive system updates
Update PKGBUILD version / update-pkgver (push) Successful in 2s
Wraps paru in a script that waits for keypress after completion,
preventing foot from closing immediately. Used as Waybar on-click
action for the updates module.
2026-04-09 11:34:11 +02:00
nevaforget 8ddbb23851 feat: add waybar module for package update notifications
Update PKGBUILD version / update-pkgver (push) Successful in 3s
Add moonarch-waybar-updates script that checks for available updates
from both official repos (checkupdates) and AUR (paru -Qua). Uses a
cache mechanism to avoid excessive mirror hits while still detecting
freshly installed updates within 60 seconds.
2026-04-09 10:54:49 +02:00
nevaforget 46ba8365db feat: style taskbar active state with bottom border indicator
Add a visible bottom border to the active taskbar button for clear
focus indication. Reserve border space on all buttons with a transparent
border to prevent icon shifting on state changes.
2026-04-09 06:35:57 +02:00
nevaforget d815e21b1d feat: prepare CSS to hide empty battery group (Waybar PR #4941)
Update PKGBUILD version / update-pkgver (push) Successful in 3s
2026-04-08 17:01:19 +02:00
nevaforget 6d3a7c8d72 feat: enable always-center-single-column in niri layout
Update PKGBUILD version / update-pkgver (push) Successful in 2s
2026-04-08 14:10:26 +02:00
nevaforget ac2b210a1f fix: audit remediation — security, quality and performance fixes
Update PKGBUILD version / update-pkgver (push) Successful in 3s
- CI Dockerfile: verify act_runner SHA256, restrict sudoers to safe
  pacman arguments (S-C1, S-C2)
- cliphist: split into cliphist-text + cliphist-image services with
  Type=simple for proper PID tracking and restart (Q-C3)
- batsaver-toggle: validate sysfs input as numeric, check state file
  write (Q-C2, S-W2)
- udev battery rule: add ACTION=="add" filter to avoid firing on
  every battery event (Q-W3)
- cpugov: replace eval with direct expansion, switch waybar module
  to signal-based updates, send SIGRTMIN+10 after toggle (Q-W1,
  S-W1, P-W4)
- Remove docker group auto-assignment from install scripts (S-I1)
2026-04-08 11:45:56 +02:00
nevaforget e1e80ca414 fix: rename battery group to avoid CSS ID conflict, clean up waybar styles
Update PKGBUILD version / update-pkgver (push) Successful in 3s
group/battery shared its CSS ID (#battery) with the battery module,
causing padding overrides to affect the group box instead of just the
module. Renamed to group/bat. Moved cpugov into indicators group.
Reorganized style.css sections for clarity.
2026-04-08 10:45:34 +02:00
nevaforget 579a948449 feat: add battery conservation mode with Waybar toggle
Update PKGBUILD version / update-pkgver (push) Successful in 2s
Laptops with charge_control_end_threshold support get a click-to-toggle
on the battery module (80% ↔ 100%). A ♥ icon appears when conservation
is active, hidden when inactive. State persists across reboots via
systemd oneshot service. udev rule grants wheel group write access
so no sudo is needed for toggling.
2026-04-08 09:55:46 +02:00
nevaforget 6a258151fa fix: show bluetooth icon instead of status text in Waybar
Update PKGBUILD version / update-pkgver (push) Successful in 2s
The bluetooth module displayed " {status}" (icon + text like
"on"/"off") which was inconsistent with other icon-only modules.
2026-04-07 17:28:15 +02:00
nevaforget 037d311607 refactor: clean up Waybar styles, remove user-defaults mechanism
- Remove defaults/user/ and the user-defaults copy loop from
  post-install.sh and transform.sh — Waybar falls back to
  /etc/xdg/waybar/ via XDG spec, no provisioning needed.
- Remove USER_DEFAULTS constant from lib.sh.
- Clean up style.css: remove dead selectors (#net, #cava,
  #custom-updates, #custom-notification), commented-out blocks,
  empty rules, duplicate properties, and hardcoded hex color.
- Restructure module styling: generic top-level box via
  > widget > *, group children reset via widget widget > *,
  explicit exceptions for workspaces/taskbar/window.
- Normalize section comments and whitespace.
- Update README to remove user/waybar/ from project structure.
2026-04-07 17:27:47 +02:00
nevaforget 2b8e40f37f docs: update README and DECISIONS for Walker moonarch theme
Update PKGBUILD version / update-pkgver (push) Successful in 3s
2026-04-07 13:13:44 +02:00
nevaforget ef8ff7099f feat: rename Walker theme to moonarch, add explicit Catppuccin colors
Rename gtk-inherit theme to moonarch with fixed Colloid-Grey-Dark-
Catppuccin color definitions. Reduces border brightness and shadow
weight for a subtler appearance.

Closes #3
2026-04-07 13:11:06 +02:00
nevaforget 7a2b1ece05 docs: update README and DECISIONS for archinstall v4 and kanshi fixes 2026-04-07 12:24:07 +02:00
nevaforget 62495d8e3d fix: prevent kanshi config from breaking wdisplays-persistent
Update PKGBUILD version / update-pkgver (push) Successful in 3s
Remove ABOUTME comments from kanshi default config — they broke
the profile parser in wdisplays-persistent store.c, preventing
config saves. Also skip kanshi in transform.sh when user profiles
already exist, since display layouts are machine-specific.
2026-04-07 12:23:06 +02:00
nevaforget 76f5602b47 Migrate archinstall config to v4 format
archinstall v4.1 introduced new canonical key names. Update
user_configuration.json to the current schema:
- audio_config → nested under app_config
- bootloader → bootloader_config with explicit uki flag
- gfx_driver value updated to "All open-source (default)"
- custom-commands → custom_commands (underscore)
2026-04-07 11:56:01 +02:00
nevaforget 466ba773dc docs: fix remaining stale references in README and CLAUDE.md
Update PKGBUILD version / update-pkgver (push) Successful in 2s
- scripts/: replace update.sh with moonarch-update, fix description
- Update section: remove stale "syncs repo, deploys XDG" claim
- User services: add stasis to table
- CLAUDE.md: mention systemd user services in defaults/ description
2026-04-07 11:37:44 +02:00
nevaforget 9f994d03fb docs: update README for swaync, cliphist service, /usr/bin paths
Update PKGBUILD version / update-pkgver (push) Successful in 2s
- dunst → swaync throughout
- Clipboard section: cliphist + Walker, wiped on session start
- Helper scripts: /usr/local/bin → /usr/bin (package-managed)
- User services: moved to /etc/systemd/user/, added cliphist/nautilus
- Startup diagram: swaync, nm-applet, systemd services listed
- moonarch-dnd removed (dunst-specific)
2026-04-07 11:34:30 +02:00
nevaforget 80c395fc14 feat: add cliphist service, move user services to /etc/systemd/user/
Update PKGBUILD version / update-pkgver (push) Successful in 2s
- Add cliphist.service that wipes clipboard history on session start
  (crash-safe: cleans up at next boot, not at shutdown)
- Move kanshi, walker, nautilus services from ~/.config/systemd/user/
  to /etc/systemd/user/ (system-level defaults, consistent with
  moonarch's config philosophy)
- Remove cliphist spawn-sh-at-startup from niri config (managed by
  systemd service now)
- Add cliphist to USER_SERVICES in post-install.sh and transform.sh

Closes #1
2026-04-07 11:27:49 +02:00
nevaforget 25baf88a2a fix: move paru repo config into moonarch.install to break bootstrap loop
Update PKGBUILD version / update-pkgver (push) Successful in 3s
The paru PKGBUILD repo config was only set up by post-install.sh and
transform.sh. If the config was missing on an existing system,
moonarch-git couldn't update — the fix required the package that
delivers the fix.

Now moonarch.install sets up the config on every install/upgrade.
post-install.sh keeps it for first-time bootstrap (before moonarch-git
exists). transform.sh no longer manages it.
2026-04-07 11:11:58 +02:00
nevaforget 29550b8a14 fix: audit remediation — 6 fixes across quality, performance, security
Update PKGBUILD version / update-pkgver (push) Successful in 2s
Q-01: Fix broken upower regex in moonarch-btnote (lookaheads never matched)
Q-02: Fix transform.sh paru repo section name ([moonarch] → [moonarch-pkgbuilds]),
      config path (~/.config → /etc), and partial sync (-Sy → -Syu)
Q-03: Add missing stasis package to aur.txt (idle management broken on fresh install)
S-01: Switch CI git clones from HTTP to HTTPS (MITM risk in Docker network)
S-02: Restrict CI builder sudo to /usr/bin/pacman only
P-01: Refactor moonarch-waybar-gpustat — remove while loop, use jq --arg style
2026-04-07 10:50:57 +02:00
nevaforget 27247a4ffb fix: remove duplicate VPN notifications from moonarch-vpn
Update PKGBUILD version / update-pkgver (push) Successful in 2s
nm-applet already sends notifications for VPN state changes.
The script's own notify-send calls caused duplicates on every toggle.

Closes #4
2026-04-07 10:19:41 +02:00
nevaforget fdedc8071f Add nautilus preload service for faster file manager startup
Update PKGBUILD version / update-pkgver (push) Successful in 2s
xdg-open ~ (Super+E) cold-starts Nautilus every time, causing
noticeable delay. A GApplication service keeps Nautilus warm in the
background so subsequent opens are near-instant. Follows the same
pattern as the existing walker.service.

Closes #2
2026-04-07 09:03:18 +02:00
nevaforget 0fba63571c fix: harden GPG key import with fingerprint verification and cleanup
Update PKGBUILD version / update-pkgver (push) Successful in 2s
The registry signing key was imported without verifying its fingerprint,
allowing a MITM or compromised server to inject a rogue key. Now checks
the downloaded key against a pinned fingerprint before import. Also adds
trap EXIT for tempfile cleanup and rejects empty curl responses.
2026-04-06 23:08:01 +02:00
nevaforget 23a14e95d5 fix: enable stasis idle manager in user services
Update PKGBUILD version / update-pkgver (push) Successful in 3s
stasis ships its own systemd user service but was missing from the
USER_SERVICES array in both post-install and transform scripts.
2026-04-06 22:55:33 +02:00
nevaforget 52a49bfcc3 Fix swaync fullscreen background by raising CSS priority to user level
Update PKGBUILD version / update-pkgver (push) Successful in 3s
libadwaita overrides application-level CSS, causing a visible
background behind the control center overlay. Setting cssPriority
to "user" ensures custom styles take precedence.
2026-04-02 12:03:04 +02:00
nevaforget 047ff53091 fix: remove -- from nmcli calls in moonarch-vpn
Update PKGBUILD version / update-pkgver (push) Successful in 2s
nmcli 1.56 treats -- as a connection name instead of end-of-options,
causing "Unknown connection --" errors when toggling VPN connections.
2026-04-02 10:23:55 +02:00
nevaforget 24b81df63c Add rebuild-detector to system packages
Detects AUR packages broken by shared library upgrades (Python, OpenSSL,
etc.) so they can be rebuilt promptly after system updates.
2026-04-02 10:20:04 +02:00
nevaforget 1004a0b986 Fix paru PKGBUILD repo name collision with pacman registry
Update PKGBUILD version / update-pkgver (push) Successful in 2s
Both the pacman package registry and the paru PKGBUILD repo used
[moonarch] as section name, causing paru to fail resolving PKGBUILD
upgrade targets against the wrong repo. Renamed PKGBUILD repo to
[moonarch-pkgbuilds] and moved config from ~/.config/paru/paru.conf
to system-wide /etc/paru.conf.
2026-04-02 08:52:26 +02:00
nevaforget ba4a413097 Move GTK theme from moongreet config to system-wide gtk-4.0 settings
Update PKGBUILD version / update-pkgver (push) Successful in 3s
Remove gtk-theme from moongreet.toml and set gtk-theme-name and
gtk-icon-theme-name in /etc/xdg/gtk-4.0/settings.ini instead.
2026-04-02 08:27:56 +02:00
nevaforget cf9eae1edc Fix swaync theme to use upstream compiled CSS from catppuccin/swaync v1.0.1
Update PKGBUILD version / update-pkgver (push) Successful in 2s
Hand-converted @define-color CSS was not rendering correctly.
Replaced with pre-built release CSS, accent changed from Blue
to Lavender (#b4befe), font set to UbuntuSans Nerd Font.
2026-04-01 19:11:31 +02:00
nevaforget 8faca0ed48 Add gtk-theme to moongreet config for greeter session
The greeter runs as the greeter user, so user-level gsettings
don't apply. Explicit gtk-theme ensures Colloid-Grey-Dark-Catppuccin
is used in the login screen.
2026-04-01 18:50:23 +02:00
nevaforget 7f06c8e501 Add Moonarch package registry setup with signed packages
Update PKGBUILD version / update-pkgver (push) Successful in 1s
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:09:10 +02:00
nevaforget 93ff264824 Add custom Arch-based act_runner image
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 18:09:09 +02:00
nevaforget d4eec1c506 Add custom Arch-based act_runner image, revert workflow workaround
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 18:09:09 +02:00
nevaforget 6b21b6d50c Fix CI: install git in Arch container for update-pkgver
The runner now uses docker mode with archlinux:base-devel which
does not include git by default.
2026-04-01 18:09:09 +02:00
nevaforget 997f7d90a0 Fix swaync notification icon size in waybar
Update PKGBUILD version / update-pkgver (push) Successful in 2s
Remove explicit 16pt Pango span wrapper so the icon inherits
the default waybar font size, matching all other modules.
2026-04-01 15:31:27 +02:00
nevaforget 3673b22009 Add CI workflow to auto-update pkgver in moonarch-pkgbuilds
Update PKGBUILD version / update-pkgver (push) Successful in 2s
Same pattern as moongreet/moonlock/moonset — pushes to main
trigger a pkgver bump so paru detects updates.
2026-04-01 14:59:33 +02:00
67 changed files with 1561 additions and 1172 deletions
+61
View File
@@ -0,0 +1,61 @@
# ABOUTME: Updates pkgver in moonarch-pkgbuilds after a push to main.
# ABOUTME: Ensures paru detects new versions of this package.
name: Update PKGBUILD version
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:
runs-on: moonarch
steps:
- name: Checkout source repo
run: |
git clone --bare https://gitea.moonarch.de/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
cd pkgbuilds
OLD_VER=$(grep '^pkgver=' moonarch-git/PKGBUILD | cut -d= -f2)
if [ "$OLD_VER" = "$PKGVER" ]; then
echo "pkgver already up to date ($PKGVER)"
exit 0
fi
sed -i "s/^pkgver=.*/pkgver=$PKGVER/" moonarch-git/PKGBUILD
echo "Updated pkgver: $OLD_VER → $PKGVER"
git config user.name "pkgver-bot"
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
+86 -12
View File
@@ -1,20 +1,94 @@
# Moonarch
Reproduzierbares Arch-Linux-Setup basierend auf archinstall + Post-Install-Automatisierung.
Reproducible Arch Linux setup based on archinstall + post-install automation.
## Projektstruktur
## Project Structure
- `config/` — archinstall-Konfiguration (inkl. custom-commands die das Repo nach /opt/moonarch klonen, root-owned)
- `scripts/`Post-Install- und Helper-Scripts
- `packages/`Paketlisten (offiziell + AUR), getrennt gepflegt
- `defaults/` — XDG-Configs, Shell-Config, Helper-Binaries, greetd/moongreet-Config, Wallpaper
- `config/` — archinstall configuration (incl. custom-commands that clone the repo to /opt/moonarch, root-owned)
- `scripts/`post-install and helper scripts
- `packages/`package lists (official + AUR), maintained separately
- `defaults/` — XDG configs, shell config, helper binaries, systemd services, udev rules, greetd/moongreet config, wallpaper
## Konventionen
## Battery Conservation Mode
- 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
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
## Ich bin Apollo
## Nightlight (Blue Light Filter)
Benannt nach dem Programm, das Menschen zum Mond gebracht hat — passend für ein Projekt namens Moonarch.
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
+118 -8
View File
@@ -1,50 +1,160 @@
# 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, Ragnar
- **Who**: Dominik, ClaudeCode
- **Why**: Dunst lacks wp_fractional_scale_v1 support, causing aliased/jagged font rendering on external monitors in mixed-DPI setups (laptop eDP-1 at 2.5x, externals at 1x). Confirmed by testing: removing fractional scaling fixed the issue. swaync uses GTK4 which handles fractional scaling natively.
- **Tradeoffs**: swaync is heavier than dunst (GTK4 + libadwaita dependency). Loses dunstctl CLI (replaced by swaync-client). Gains notification center panel with DnD toggle, grouping, MPRIS widget support. Waybar already had swaync-client integration with exec-if guard.
- **How**: Replaced dunst with swaync in packages/official.txt and archinstall config. Niri spawn-at-startup updated. Waybar dunstctl widget removed (swaync-client widget already present). New swaync config.json and style.css based on catppuccin/swaync upstream theme with Lavender accent instead of Blue.
## 2026-03-31 Audit: shell script quoting fixes, PKGBUILD permissions
- **Who**: Ragnar, Dom
- **Who**: ClaudeCode, Dom
- **Why**: Security audit found command injection risk in moonarch-cpugov (unquoted array expansion with pkexec), word-splitting in moonarch-btnote (upower output from Bluetooth devices), and nmcli argument injection in moonarch-vpn. PKGBUILD for moongreet had world-readable cache dir.
- **Tradeoffs**: `eval` in cpugov is safe because COMMANDS values are hardcoded string literals, not user input. Alternative (function dispatch) would be cleaner but over-engineered for 3 fixed entries. moonarch-btnote switched from for-loop to while+read with process substitution to avoid subshell.
- **How**: (1) `eval "${COMMANDS[$choice]}"` in cpugov. (2) `while IFS= read -r` + process substitution + quoted `$DEVICE_DATA` in btnote. (3) `--` guard before `$connection` in vpn nmcli calls. (4) `install -dm700` for moongreet cache dirs in PKGBUILD. (5) `else err` logging in post-install.sh when USER_DEFAULTS missing.
## 2026-03-29 /opt/moonarch stays root-owned, no chown to user
- **Who**: Dominik, Ragnar
- **Who**: Dominik, ClaudeCode
- **Why**: Multi-user system — chown to UID 1000 locks out other users from moonarch-update
- **Tradeoffs**: sudo required for git operations in update.sh vs. simpler user-owned repo
- **How**: Repo stays at /opt/moonarch owned by root:root. update.sh uses `sudo git` for fetch/pull. All scripts already use sudo for system-level operations, so this is consistent.
## 2026-03-29 Add transform.sh for existing Arch+Wayland systems
- **Who**: Dominik, Ragnar
- **Who**: Dominik, ClaudeCode
- **Why**: Users with existing Arch+Wayland setups should be able to adopt Moonarch without reinstalling
- **Tradeoffs**: Hard overwrite of all configs (user + system) vs. selective/merge approach — chose hard overwrite for simplicity and consistency
- **How**: New transform.sh with pre-flight summary, backup, DM conflict resolution, and --dry-run flag. Shared helpers extracted to lib.sh.
## 2026-03-29 Package moonarch as moonarch-git PKGBUILD
- **Who**: Dominik, Ragnar
- **Who**: Dominik, ClaudeCode
- **Why**: System artifacts (XDG configs, helper scripts, zsh config, wallpaper) should be managed by pacman for clean deployment, versioning, rollback, and deinstallation
- **Tradeoffs**: /etc/xdg/ configs NOT in backup= (moonarch philosophy: system defaults flow through, users override in ~/.config/). /etc/greetd/ and /etc/moongreet/ NOT owned by package (owned by greetd/moongreet-git, overwritten via .install hook). Helper scripts move from /usr/local/bin/ to /usr/bin/ (FHS for package-managed files)
- **How**: moonarch-git PKGBUILD in moonarch-pkgbuilds repo. sweet-cursors-git as separate package. moonarch-update simplified (no git-sync, pacman handles file deployment). Installer scripts (post-install.sh, transform.sh) remain for orchestration, will be refactored in a follow-up to delegate file deployment to `paru -S moonarch-git`
## 2026-03-30 Replace Rofi with Walker as application launcher
- **Who**: Dominik, Ragnar
- **Who**: Dominik, ClaudeCode
- **Why**: Walker is Wayland-native (GTK4 + gtk4-layer-shell), has built-in providers for clipboard, bluetooth, audio (wireplumber), and Niri integration. Reduces custom shell scripts from 8 to 3. Rofi required a Wayland fork (rofi-lbonn-wayland-git) and every applet was a custom bash script.
- **Tradeoffs**: Walker is newer/less battle-tested than Rofi. Requires separate Elephant daemon with per-provider packages. Dmenu mode lacks Rofi's `-a`/`-u` (active/urgent) and `-mesg` flags. Settings menu (moonarch-setmen) dropped entirely — apps are findable via Walker's app search.
- **How**: Walker + Elephant as systemd user services. Native providers replace 5 rofi scripts (launcher, clipboard, bluetooth, volume, sink-switcher). 3 scripts ported to walker dmenu (vpn, cpugov, sink-switcher). Walker theme inherits GTK4 system theme colors (gtk-inherit). Old rofi configs preserved in `legacy/rofi/`.
## 2026-03-30 Use nm-applet as VPN secret agent, add WireGuard support
- **Who**: Dominik, Ragnar
- **Who**: Dominik, ClaudeCode
- **Why**: VPN auth previously spawned a foot terminal for `nmcli --ask`, which was fragile and ugly. WireGuard connections were invisible to the VPN script and Waybar indicator because both only checked for OpenVPN (`tun0` / `vpn` type).
- **Tradeoffs**: nm-applet adds a tray indicator (mitigated with `--indicator` mode which is minimal). Requires nm-applet running at session start. Alternative was gnome-keyring or a custom secret agent — nm-applet is simpler and handles all NM secret types.
- **How**: nm-applet started via niri spawn-at-startup. moonarch-vpn rewritten to support both vpn and wireguard types, uses nm-applet for auth instead of foot terminal, sends notify-send for connect/disconnect results. Waybar VPN module uses `nmcli` active connection check instead of `/proc/sys/net/ipv4/conf/tun0`, plus RTMIN+9 signal for instant updates after toggle.
## 2026-03-30 Standardize GTK theme to Colloid-Grey-Dark-Catppuccin
- **Who**: Dominik, Ragnar
- **Who**: Dominik, ClaudeCode
- **Why**: gsettings had `Colloid-Dark-Catppuccin` while config files had `Colloid-Catppuccin` — inconsistent. Grey accent matches the icon theme (Colloid-Grey-Catppuccin-Dark). Explicit `-Dark` variant is more reliable than depending on `prefer-dark` color-scheme setting.
- **Tradeoffs**: Explicit dark locks out light mode toggle — acceptable since Moonarch is dark-only by design.
- **How**: Updated transform.sh, post-install.sh, gtk-3.0/settings.ini, and gsettings to `Colloid-Grey-Dark-Catppuccin`. GTK4 symlinks updated accordingly.
+46 -56
View File
@@ -16,20 +16,21 @@ desktop that can be rebuilt from scratch in minutes.
| **Greeter** | [greetd](https://sr.ht/~kennylevinsen/greetd/) + [moongreet](https://gitea.moonarch.de/nevaforget/moongreet) | Minimal, Wayland-native login. moongreet provides GTK4 UI with fingerprint support, running inside its own Niri instance. |
| **Lock Screen** | [moonlock](https://gitea.moonarch.de/nevaforget/moonlock) | ext-session-lock-v1 protocol — compositor guarantees lock on crash. PAM + fprintd, GPU blur, multi-monitor. |
| **Power Menu** | [moonset](https://gitea.moonarch.de/nevaforget/moonset) | GTK4 Layer Shell overlay above Waybar. Lock, logout, hibernate, reboot, shutdown with confirmation. |
| **Idle Manager** | [stasis](https://aur.archlinux.org/packages/stasis) | Separate AC/battery power plans. Brightness dimming, DPMS, lock (via moonlock), and suspend on configurable timeouts. |
| **Idle Manager** | [stasis](https://aur.archlinux.org/packages/stasis) + [wayland-pipewire-idle-inhibit](https://github.com/rafaelrc7/wayland-pipewire-idle-inhibit) | Separate AC/battery power plans. Brightness dimming, DPMS, lock (via moonlock), and suspend on configurable timeouts. The companion inhibitor holds a Wayland idle-inhibitor while audio plays — keeps the screen awake during windowed browser video, which stasis' pactl detection skips by design. |
| **Bar** | [Waybar](https://github.com/Alexays/Waybar) | Wayland-native, highly customizable. Niri workspace/window modules via community plugins. |
| **Launcher** | [Walker](https://github.com/abenz1267/walker) + [Elephant](https://github.com/abenz1267/elephant) | Wayland-native GTK4 launcher with built-in providers for apps, clipboard, bluetooth, audio, files, and calculator. Dmenu mode for custom scripts (VPN, CPU governor). |
| **Terminal** | [Foot](https://codeberg.org/dnkl/foot) | Fast, minimal Wayland-native terminal. Server mode for instant window spawning. |
| **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](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. |
| **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. |
| **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. |
@@ -53,44 +54,25 @@ 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
moonarch-update
moonup # or: moonarch-update
```
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.
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.
## Project Structure
@@ -103,41 +85,46 @@ packages/
aur.txt AUR packages (~20), one per line
scripts/
lib.sh Shared helpers sourced by all scripts
lib.sh Shared helpers sourced by post-install.sh
post-install.sh Main automation (packages, configs, themes, services)
transform.sh Convert existing Arch+Wayland system to Moonarch
update.sh Legacy wrapper — prints notice, forwards to moonarch-update
moonarch-update Interactive updater (deployed to /usr/bin/ as moonup)
moonarch-doctor System health checker (deployed to /usr/bin/ as moondoc)
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 + gtk-inherit theme
walker/config.toml, themes/ Launcher: Walker config + moonarch theme (Catppuccin Mocha)
foot/foot.ini Terminal: font, colors, keybinds
dunst/dunstrc Notifications: geometry, colors, behavior
kanshi/config Display profiles (empty, user-configured)
swaync/config.json, style.css Notifications: appearance, behavior, MPRIS
kanshi/config Display profiles (empty seed, user-configured via wdisplays)
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/local/bin/)
bin/ Helper scripts (deployed to /usr/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
user/systemd/user/ Systemd user services (kanshi, walker, elephant)
user/waybar/ Per-user Waybar overrides (only copied if missing)
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)
backgrounds/wallpaper.jpg Default wallpaper (shared by desktop, greeter, lock screen)
```
@@ -151,10 +138,11 @@ greetd ─► niri (greeter instance) ─► moongreet ─► user authenticates
┌──────────┬────────┬────────┬──────────┐
▼ ▼ ▼ ▼ ▼
waybar dunst foot waypaper cliphist
(bar) (notify) (server) (wallpaper) (clipboard)
waybar swaync foot waypaper nm-applet wlsunset
(bar) (notify) (server) (wallpaper) (VPN) (nightlight)
kanshi runs as a systemd user service (graphical-session.target)
systemd user services (graphical-session.target):
kanshi, cliphist, walker, nautilus
```
## Keybinds (Default)
@@ -187,17 +175,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` |
| Niri | `/etc/xdg/niri/config.kdl` | `~/.config/niri/config.kdl` (includes system config) |
| Waybar | `/etc/xdg/waybar/` | `~/.config/waybar/` |
| Walker | `/etc/xdg/walker/` | `~/.config/walker/` |
| Foot | `/etc/xdg/foot/foot.ini` | `~/.config/foot/foot.ini` |
| Dunst | `/etc/xdg/dunst/dunstrc` | `~/.config/dunst/dunstrc` |
| swaync | `/etc/xdg/swaync/` | `~/.config/swaync/` |
| 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/local/bin/moonarch-*` are not meant to be overridden — they
are part of the system and updated via `moonarch-update`.
Helper scripts in `/usr/bin/moonarch-*` are not meant to be overridden — they
are part of the system and updated via `paru -Syu`.
## System Services
@@ -206,7 +194,6 @@ are part of the system and updated via `moonarch-update`.
| 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) |
@@ -215,8 +202,11 @@ are part of the system and updated via `moonarch-update`.
| Service | Purpose |
|---------|---------|
| cliphist | Clipboard history (wipes on session start, stores in runtime dir) |
| kanshi | Dynamic display configuration (auto-switch output profiles on hotplug) |
| elephant | Walker data provider backend (apps, clipboard, bluetooth, audio) |
| nautilus | File manager preload (faster first launch) |
| stasis | Idle manager (dimming, DPMS, lock, suspend on AC/battery plans) |
| wayland-pipewire-idle-inhibit | Holds a Wayland idle-inhibitor while audio plays (keeps screen awake for windowed browser video that stasis skips) |
| walker | Walker application launcher (GTK4 service mode for instant startup) |
## Moonarch Ecosystem
+11
View File
@@ -0,0 +1,11 @@
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
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 mkdir -p /data && chown builder:builder /data
USER builder
ENV HOME=/home/builder
ENTRYPOINT ["/usr/local/bin/run.sh"]
+7 -5
View File
@@ -2,11 +2,16 @@
"__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"],
@@ -38,8 +43,6 @@
"pipewire-jack",
"pipewire-pulse",
"wireplumber",
"docker",
"docker-compose",
"fwupd",
"ufw",
"greetd",
@@ -52,7 +55,7 @@
],
"profile_config": {
"gfx_driver": "All open-source",
"gfx_driver": "All open-source (default)",
"greeter": "greetd",
"profile": {
"main": "Desktop",
@@ -63,7 +66,6 @@
"services": [
"NetworkManager",
"bluetooth",
"docker",
"greetd",
"systemd-timesyncd",
"ufw"
@@ -71,7 +73,7 @@
"timezone": "Europe/Berlin",
"custom-commands": [
"custom_commands": [
"git clone https://gitea.moonarch.de/nevaforget/moonarch.git /opt/moonarch"
]
}
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/bash
# ABOUTME: Privileged helper invoked via pkexec from moonarch-batsaver-toggle.
# ABOUTME: Validates the threshold value and writes it to sysfs and the state file.
set -eu
VAL="${1:-}"
case "$VAL" in
""|*[!0-9]*)
echo "Invalid argument: '$VAL' (expected integer 1-100)" >&2
exit 1
;;
esac
if [ "$VAL" -lt 1 ] || [ "$VAL" -gt 100 ]; then
echo "Out of range: $VAL (expected 1-100)" >&2
exit 1
fi
THRESHOLD_FILE="/sys/class/power_supply/BAT0/charge_control_end_threshold"
STATE_DIR="/var/lib/moonarch"
STATE_FILE="$STATE_DIR/batsaver-threshold"
[ -f "$THRESHOLD_FILE" ] || { echo "No battery threshold support" >&2; exit 1; }
# Skip the kernel write when the value already matches — some Lenovo drivers
# reject same-value writes with EINVAL.
CURRENT=$(cat "$THRESHOLD_FILE")
if [ "$CURRENT" != "$VAL" ]; then
printf %s "$VAL" > "$THRESHOLD_FILE"
fi
mkdir -p "$STATE_DIR"
printf %s "$VAL" > "$STATE_FILE"
+23
View File
@@ -0,0 +1,23 @@
#!/bin/sh
# ABOUTME: Restores the saved battery charge end threshold on boot.
# ABOUTME: Skips silently when the kernel already reports the same value (avoids EINVAL on some Lenovo drivers).
set -eu
STATE_FILE="/var/lib/moonarch/batsaver-threshold"
SYS_FILE="/sys/class/power_supply/BAT0/charge_control_end_threshold"
[ -f "$STATE_FILE" ] || exit 0
[ -f "$SYS_FILE" ] || exit 0
V=$(cat "$STATE_FILE")
case "$V" in
""|*[!0-9]*) exit 0 ;;
esac
[ "$V" -ge 1 ] && [ "$V" -le 100 ] || exit 0
# Some Lenovo drivers reject writing the same value with EINVAL.
C=$(cat "$SYS_FILE")
[ "$C" = "$V" ] && exit 0
printf %s "$V" > "$SYS_FILE"
+22
View File
@@ -0,0 +1,22 @@
#!/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
+2 -3
View File
@@ -8,9 +8,8 @@ ICON="battery-empty"
while IFS= read -r d; do
[ -z "$d" ] && continue
DEVICE_DATA=$(upower -i "$d")
PERCENTAGE=$(echo "$DEVICE_DATA" | grep -Po '(?<=(percentage: )).*(?= icon)')
PER_INT=$(echo "${PERCENTAGE//%}")
DEVICE_NAME=$(echo "$DEVICE_DATA" | grep -Po '(?<=(model: )).*(?= serial)')
PER_INT=$(echo "$DEVICE_DATA" | grep -oP 'percentage:\s+\K[0-9]+')
DEVICE_NAME=$(echo "$DEVICE_DATA" | grep -oP 'model:\s+\K.+')
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" \
+4 -3
View File
@@ -60,11 +60,12 @@ fi
# check if choice exists
if test "${COMMANDS[$choice]+isset}"
then
# Execute the choice — eval required because COMMANDS values contain
# multi-word strings that must be interpreted as full commands.
eval "${COMMANDS[$choice]}"
${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
@@ -0,0 +1,11 @@
#!/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
+5 -1
View File
@@ -5,7 +5,11 @@
# choose audio sink via rofi
# changes default sink and moves all streams to that sink
sink=$(pactl list sinks short | awk '{print $1, $2}' | walker -d -p "󱡫 Sink Switcher" | awk '{print $1}') &&
sink=$(pactl list sinks short | awk '{print $1, $2}' | walker -d -p "󱡫 Sink Switcher" | awk '{print $1}')
# Walker cancel returns empty — awk masks its non-zero exit. Guard here so we
# don't call `pactl set-default-sink ""` on dismissal.
[[ -n "$sink" ]] || exit 0
pactl set-default-sink "$sink" &&
for input in $(pactl list sink-inputs short | awk '{print $1}'); do
+5 -18
View File
@@ -10,7 +10,7 @@
ACTIVE_PREFIX="󰌘 "
INACTIVE_PREFIX="󰌙 "
for cmd in nmcli walker notify-send; do
for cmd in nmcli walker; do
command -v "$cmd" >/dev/null 2>&1 || {
echo "Error: '$cmd' not found" >&2
exit 1
@@ -35,29 +35,17 @@ function extract_connection_name() {
echo "$result"
}
# Connect a VPN and notify the result.
# Connect a VPN.
# Requires nm-applet (or another NM secret agent) for interactive auth.
function connect_vpn() {
local connection="$1"
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
nmcli connection up id "$connection"
}
# Disconnect a VPN and notify the result.
# Disconnect a VPN.
function disconnect_vpn() {
local connection="$1"
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
nmcli connection down id "$connection"
}
# Toggle the VPN connection based on its current state.
@@ -89,7 +77,6 @@ function main() {
connections=$(list_vpn_connections)
if [[ -z "$connections" ]]; then
notify-send "VPN" "No VPN connections configured"
exit 0
fi
+61
View File
@@ -0,0 +1,61 @@
#!/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
@@ -0,0 +1,19 @@
#!/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-Modul das den CPU-Governor als JSON ausgibt.
# ABOUTME: Wird von der Waybar custom/cpugov Config referenziert.
# ABOUTME: Waybar module that outputs the CPU governor as JSON.
# ABOUTME: Referenced by the Waybar custom/cpugov config.
CPU_GOV=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor)
CPU_GOV=$(cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor 2>/dev/null)
case $CPU_GOV in
performance)
+9 -15
View File
@@ -1,9 +1,7 @@
#!/usr/bin/bash
# ABOUTME: Waybar-Modul das die GPU-Auslastung als JSON ausgibt.
# ABOUTME: Wird von der Waybar custom/gpu-usage Config referenziert.
# ABOUTME: Waybar module that outputs GPU utilization as JSON.
# ABOUTME: Referenced by the Waybar custom/gpu-usage config.
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>"
@@ -26,14 +24,10 @@ do
ICON="<span color='#dd532e' size='8pt' rise='1.5pt'>█</span>"
fi
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
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}'
+9
View File
@@ -0,0 +1,9 @@
#!/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
@@ -0,0 +1,68 @@
#!/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"
@@ -0,0 +1,21 @@
<?xml version="1.0"?>
<!-- ABOUTME: System-wide fontconfig generic-family defaults for Moonarch. -->
<!-- ABOUTME: Loads after 60-latin (number 65) so these prefs win over stock defaults. -->
<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
<fontconfig>
<description>Moonarch generic-family defaults</description>
<!-- binding="strong" required: the default weak <prefer> ranks behind
stock generic fallbacks, so it would not take effect. -->
<alias binding="strong">
<family>sans-serif</family>
<prefer>
<family>UbuntuSans Nerd Font</family>
</prefer>
</alias>
<alias binding="strong">
<family>monospace</family>
<prefer>
<family>UbuntuSansMono Nerd Font</family>
</prefer>
</alias>
</fontconfig>
+2
View File
@@ -3,6 +3,8 @@
[appearance]
background = "/usr/share/moonarch/wallpaper.jpg"
cursor-theme = "Sweet-cursors"
cursor-size = 24
[behavior]
# show_user_list = true
+23
View File
@@ -0,0 +1,23 @@
# Disable stock OSC — ModernZ replaces it.
osc=no
# Hide native title bar for a cleaner borderless look (ModernZ draws its own).
title-bar=no
# Cap window size at 80% of screen for oversized videos.
autofit-larger=80%x80%
# --- ModernZ overrides ---
# mpv treats # as a mid-line comment; the full value must be quoted so the
# hex color survives.
script-opts-append="modernz-seekbarfg_color=#b4befe"
script-opts-append="modernz-seek_handle_color=#b4befe"
script-opts-append="modernz-seek_handle_border_color=#b4befe"
script-opts-append="modernz-hover_effect_color=#b4befe"
script-opts-append="modernz-nibble_color=#b4befe"
script-opts-append="modernz-ontop_button=no"
script-opts-append="modernz-window_title_font_size=18"
# Scale OSC down globally
script-opts-append="modernz-scalewindowed=0.75"
script-opts-append="modernz-scalefullscreen=0.75"
@@ -0,0 +1,23 @@
# 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
@@ -0,0 +1,16 @@
# 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
@@ -0,0 +1,16 @@
# 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
@@ -0,0 +1,11 @@
[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
@@ -0,0 +1,20 @@
# 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
@@ -0,0 +1,10 @@
[preferred]
default=gnome;gtk;
org.freedesktop.impl.portal.Access=gtk;
org.freedesktop.impl.portal.Notification=gtk;
org.freedesktop.impl.portal.Secret=gnome-keyring;
# xdg-desktop-portal-gtk reports the Inhibit interface as success even though
# nothing implements it under Niri, which makes Firefox/Waterfox skip the
# native Wayland idle-inhibit. With no backend the browser falls back to
# zwp_idle_inhibit, which Niri honors, so windowed video keeps the screen awake.
org.freedesktop.impl.portal.Inhibit=none;
+14 -27
View File
@@ -1,5 +1,4 @@
# ABOUTME: Moonarch default zsh configuration with Catppuccin-themed prompt.
# ABOUTME: Sources user overrides from ~/.zshrc.d/ and ~/.zshrc.local
# --- History ---
HISTFILE=~/.histfile
@@ -30,26 +29,25 @@ add-zsh-hook preexec _preexec_title
# --- Prompt (Catppuccin Mocha) ---
parse_git_branch() {
local branch=""
branch=$(git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/')
local git_status=$(git status --porcelain 2>/dev/null)
local color=green
if echo "$git_status" | grep -q "^ M"; then
color=yellow
branch="${branch}*"
# Gate on cheap check first — spawning git in every non-repo directory on every
# prompt render costs 20-80ms per prompt. Pattern-match the status output with
# zsh glob matching instead of piping to grep for three subshell-spawning checks.
git rev-parse --git-dir &>/dev/null || return
local branch="" git_status="" color=green flags=""
branch=$(git symbolic-ref --short HEAD 2>/dev/null || git rev-parse --short HEAD 2>/dev/null)
git_status=$(git status --porcelain 2>/dev/null)
if [[ "$git_status" == *$'\n M '* || "$git_status" == " M "* || "$git_status" == *$'\nM'* ]]; then
color=yellow; flags+="*"
fi
if echo "$git_status" | grep -qE "^ A|^\?\?"; then
color=yellow
branch="${branch}+"
if [[ "$git_status" == *$'\nA '* || "$git_status" == "A "* || "$git_status" == *'??'* ]]; then
color=yellow; flags+="+"
fi
if echo "$git_status" | grep -q "^ D"; then
color=yellow
branch="${branch}-"
if [[ "$git_status" == *$'\n D '* || "$git_status" == " D "* ]]; then
color=yellow; flags+="-"
fi
if [[ -n "$branch" ]]; then
branch=[%F{${color}}${branch}%F{reset}]
echo " [%F{${color}}${branch}${flags}%F{reset}]"
fi
echo " $branch"
}
precmd() {
@@ -141,14 +139,3 @@ export XDG_SESSION_TYPE="wayland"
export EDITOR="nvim"
export SUDO_EDITOR="nvim"
export MOZ_ENABLE_WAYLAND="1"
# --- User override scripts ---
# Drop custom config snippets into ~/.zshrc.d/*.zsh
if [[ -d "$HOME/.zshrc.d" ]]; then
for f in "$HOME/.zshrc.d"/*.zsh(N); do
source "$f"
done
fi
# Single-file user override (for simple additions)
[[ -f "$HOME/.zshrc.local" ]] && source "$HOME/.zshrc.local"
-33
View File
@@ -1,33 +0,0 @@
{
// 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
@@ -1,18 +0,0 @@
/* 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,4 +2,8 @@
# 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
@@ -1,2 +0,0 @@
# ABOUTME: kanshi configuration for dynamic display output management.
# ABOUTME: Add profiles here to auto-switch outputs on hotplug events.
+10 -4
View File
@@ -41,6 +41,7 @@ gestures {
layout {
gaps 8
center-focused-column "never"
always-center-single-column
preset-column-widths {
proportion 0.33333
@@ -78,15 +79,15 @@ layout {
// xwayland-satellite is managed automatically since niri 25.08
// kanshi is managed via systemd user service (kanshi.service)
spawn-at-startup "waybar"
spawn-at-startup "moonarch-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"
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"
// wlsunset is managed via systemd user service (wlsunset.service)
// Clipboard history managed by cliphist.service (systemd user service)
hotkey-overlay {
hide-not-bound
@@ -115,6 +116,11 @@ window-rule {
open-floating true
}
window-rule {
match app-id=r#"^mpv$"#
open-floating true
}
window-rule {
geometry-corner-radius 4
clip-to-geometry true
@@ -125,7 +131,7 @@ binds {
Super+C hotkey-overlay-title=null { spawn "walker" "-s" "clipboard"; }
Alt+W { spawn-sh "killall waybar && waybar &"; }
Alt+W { spawn-sh "killall waybar && moonarch-waybar &"; }
Super+E { spawn-sh "xdg-open ~"; }
+8 -2
View File
@@ -5,9 +5,15 @@
@description "Idle management for Moonarch (Niri + moonlock)"
default:
# Media playback inhibits idle (non-browser only, browser uses D-Bus inhibit)
# monitor_media: detect active media via PipeWire/PulseAudio sink-inputs
# (pactl), not MPRIS. Non-browser players (mpv, vlc, ...) are counted and
# inhibit idle. Browser audio is excluded by design; browser idle-inhibit is
# expected via D-Bus (enable_dbus_inhibit), which Waterfox/Firefox only send
# in fullscreen -- so windowed browser video is NOT caught by stasis.
# ignore_remote_media false: also count remote players (Spotify-remote,
# KDEConnect, Chromecast); has no effect on windowed browser video.
monitor_media true
ignore_remote_media true
ignore_remote_media false
# App/process inhibit patterns (apps that don't use D-Bus idle-inhibit)
inhibit_apps [
+2 -2
View File
@@ -5,10 +5,10 @@
"positionY": "top",
"layer": "overlay",
"control-center-layer": "top",
"cssPriority": "application",
"cssPriority": "user",
"notification-window-width": 300,
"notification-icon-size": 64,
"notification-window-height": -1,
"notification-body-image-height": 100,
"notification-body-image-width": 200,
+61 -93
View File
@@ -1,33 +1,16 @@
/* ABOUTME: Moonarch swaync notification styling with Catppuccin Mocha colors. */
/* 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;
/* ABOUTME: Based on catppuccin/swaync v1.0.1 release, accent changed from Blue to Lavender. */
* {
all: unset;
font-size: 14px;
font-size: 12px;
font-family: "UbuntuSans Nerd Font";
transition: 200ms;
--notification-icon-size: 36px;
}
trough highlight {
background: @text;
background: #cdd6f4;
}
scale {
@@ -46,25 +29,24 @@ trough slider {
border-radius: 12.6px;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.8);
transition: all 0.2s ease;
background-color: @lavender;
background-color: #b4befe;
}
trough slider:hover {
box-shadow: 0 0 2px rgba(0, 0, 0, 0.8), 0 0 8px @lavender;
box-shadow: 0 0 2px rgba(0, 0, 0, 0.8), 0 0 8px #b4befe;
}
trough {
background-color: @surface0;
background-color: #313244;
}
/* ── Notifications ── */
/* notifications */
.notification-background {
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.8), inset 0 0 0 1px @surface1;
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.8), inset 0 0 0 1px #45475a;
border-radius: 12.6px;
margin: 18px;
background: @mantle;
color: @text;
background: #181825;
color: #cdd6f4;
padding: 0;
}
@@ -74,7 +56,7 @@ trough {
}
.notification-background .notification.critical {
box-shadow: inset 0 0 0 2px @red;
box-shadow: inset 0 0 7px 0 #f38ba8;
}
.notification .notification-content {
@@ -82,75 +64,70 @@ trough {
}
.notification .notification-content overlay {
/* icons */
margin: 4px;
}
.notification-content .summary {
color: @text;
color: #cdd6f4;
}
.notification-content .time {
color: @subtext0;
color: #a6adc8;
}
.notification-content .body {
color: @subtext1;
color: #bac2de;
}
.notification > *:last-child > * {
min-height: 3.4em;
}
/* Close button */
.notification-background .close-button {
margin: 7px;
padding: 2px;
border-radius: 50%;
color: @base;
background-color: @red;
border-radius: 6.3px;
color: #1e1e2e;
background-color: #f38ba8;
}
.notification-background .close-button:hover {
background-color: @maroon;
background-color: #eba0ac;
}
.notification-background .close-button:active {
background-color: @pink;
background-color: #f5c2e7;
}
/* Action buttons */
.notification .notification-action {
border-radius: 7px;
color: @text;
box-shadow: inset 0 0 0 1px @surface1;
color: #cdd6f4;
box-shadow: inset 0 0 0 1px #45475a;
margin: 4px;
padding: 8px;
font-size: 0.2rem;
font-size: 0.2rem; /* controls the button size not text size*/
}
.notification .notification-action {
background-color: @surface0;
background-color: #313244;
}
.notification .notification-action:hover {
background-color: @surface1;
background-color: #45475a;
}
.notification .notification-action:active {
background-color: @surface2;
background-color: #585b70;
}
/* ── Progress bar ── */
.notification.critical progress {
background-color: @red;
background-color: #f38ba8;
}
.notification.low progress,
.notification.normal progress {
background-color: @lavender;
background-color: #b4befe;
}
.notification progress,
@@ -160,19 +137,18 @@ 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 @surface0;
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.8), inset 0 0 0 1px #313244;
border-radius: 12.6px;
background-color: @base;
color: @text;
background-color: #1e1e2e;
color: #cdd6f4;
padding: 14px;
}
.control-center .notification-background {
border-radius: 7px;
box-shadow: inset 0 0 0 1px @surface1;
box-shadow: inset 0 0 0 1px #45475a;
margin: 4px 10px;
}
@@ -185,24 +161,24 @@ trough {
}
.control-center .widget-title > label {
color: @text;
color: #cdd6f4;
font-size: 1.3em;
}
.control-center .widget-title button {
border-radius: 7px;
color: @text;
background-color: @surface0;
box-shadow: inset 0 0 0 1px @surface1;
color: #cdd6f4;
background-color: #313244;
box-shadow: inset 0 0 0 1px #45475a;
padding: 8px;
}
.control-center .widget-title button:hover {
background-color: @surface1;
background-color: #45475a;
}
.control-center .widget-title button:active {
background-color: @surface2;
background-color: #585b70;
}
.control-center .notification-group {
@@ -210,7 +186,7 @@ trough {
}
.control-center .notification-group:focus .notification-background {
background-color: @surface0;
background-color: #313244;
}
scrollbar slider {
@@ -222,8 +198,7 @@ scrollbar trough {
margin: 2px 0;
}
/* ── DnD toggle ── */
/* dnd */
.widget-dnd {
margin-top: 5px;
border-radius: 8px;
@@ -233,29 +208,28 @@ scrollbar trough {
.widget-dnd > switch {
font-size: initial;
border-radius: 8px;
background: @surface0;
background: #313244;
box-shadow: none;
}
.widget-dnd > switch:checked {
background: @lavender;
background: #b4befe;
}
.widget-dnd > switch slider {
background: @surface1;
background: #45475a;
border-radius: 8px;
}
/* ── MPRIS ── */
/* mpris */
.widget-mpris-player {
background: @surface0;
background: #313244;
border-radius: 12.6px;
color: @text;
color: #cdd6f4;
}
.mpris-overlay {
background-color: @surface0;
background-color: #313244;
opacity: 0.9;
padding: 15px 10px;
}
@@ -268,17 +242,17 @@ scrollbar trough {
.widget-mpris-title {
font-size: 1.2rem;
color: @text;
color: #cdd6f4;
}
.widget-mpris-subtitle {
font-size: 1rem;
color: @subtext1;
color: #bac2de;
}
.widget-mpris button {
border-radius: 12.6px;
color: @text;
color: #cdd6f4;
margin: 0 5px;
padding: 2px;
}
@@ -288,38 +262,34 @@ scrollbar trough {
}
.widget-mpris button:hover {
background-color: @surface0;
background-color: #313244;
}
.widget-mpris button:active {
background-color: @surface1;
background-color: #45475a;
}
.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: @red;
color: #f38ba8;
}
.power-buttons button:hover,
.powermode-buttons button:hover,
.screenshot-buttons button:hover {
background: @surface0;
background: #313244;
}
/* ── Labels / Buttons Grid ── */
.control-center .widget-label > label {
color: @text;
color: #cdd6f4;
font-size: 2rem;
}
@@ -331,28 +301,26 @@ scrollbar trough {
font-size: 2.5rem;
}
/* ── Volume / Backlight ── */
.widget-volume {
padding: 1rem 0;
}
.widget-volume label {
color: @sapphire;
color: #74c7ec;
padding: 0 1rem;
}
.widget-volume trough highlight {
background: @sapphire;
background: #74c7ec;
}
.widget-backlight trough highlight {
background: @yellow;
background: #f9e2af;
}
.widget-backlight label {
font-size: 1.5rem;
color: @yellow;
color: #f9e2af;
}
.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 = "gtk-inherit"
theme = "moonarch"
disable_mouse = false
page_jump_items = 10
hide_quick_activation = false
@@ -1,5 +1,10 @@
/* 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. */
@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;
* {
all: unset;
@@ -8,7 +13,7 @@
popover {
background: lighter(@window_bg_color);
border: 1px solid darker(@accent_bg_color);
border: 1px solid alpha(@accent_bg_color, 0.15);
border-radius: 18px;
padding: 10px;
}
@@ -27,12 +32,12 @@ scrollbar {
.box-wrapper {
box-shadow:
0 19px 38px rgba(0, 0, 0, 0.3),
0 15px 12px rgba(0, 0, 0, 0.22);
0 19px 38px rgba(0, 0, 0, 0.15),
0 15px 12px rgba(0, 0, 0, 0.1);
background: @window_bg_color;
padding: 20px;
border-radius: 20px;
border: 1px solid darker(@accent_bg_color);
border: 1px solid alpha(@accent_bg_color, 0.15);
}
.preview-box,
+70 -95
View File
@@ -1,6 +1,6 @@
{
"__aboutme1": "Moonarch default waybar configuration for Niri.",
"__aboutme2": "User overrides go in ~/.config/waybar/config",
"__aboutme2": "Deployed to /etc/xdg/waybar/ by moonarch-git package.",
"layer": "top",
"margin-top": 0,
"spacing": 5,
@@ -17,18 +17,18 @@
"modules-right": [
"tray",
"mpris",
"custom/cpugov",
"bluetooth",
"group/sound",
"backlight",
"battery",
"group/brightness",
"group/bat",
"group/indicators"
],
"group/indicators": {
"orientation": "inherit",
"modules": [
"custom/cpugov",
"gamemode",
//"custom/updates",
"custom/updates",
"idle_inhibitor",
"custom/notification",
"privacy"
@@ -55,9 +55,28 @@
],
"drawer": {
"transition-duration": 500,
"transition-left-to-right": true
"transition-left-to-right": false
}
},
"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": [
@@ -96,7 +115,6 @@
"on-scroll-down": "shift_down",
"on-click-middle": "alarm-clock-applet"
}
// "on-click": "evolution"
},
"user": {
"format": "{user}",
@@ -172,7 +190,6 @@
"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",
@@ -185,7 +202,7 @@
"firefox": "Firefox",
"VSCodium": "Code",
"codium": "Code",
"Alacritty": "Terminal",
"Alacritty": "Terminal"
},
"squash-list": [
"firefox"
@@ -198,13 +215,13 @@
"has-updates": "󱍷",
"updated": "󰂪"
},
"exec-if": "which waybar-module-pacman-updates",
"exec": "waybar-module-pacman-updates",
"on-click": "alacritty paru"
"exec": "moonarch-waybar-updates",
"interval": 60,
"on-click": "foot env MOONUP_WAIT=1 moonarch-update"
},
"custom/notification": {
"tooltip": true,
"format": "<span size='16pt'>{icon}</span>",
"format": "{icon}",
"format-icons": {
"notification": "󱅫",
"none": "󰂜",
@@ -223,10 +240,8 @@
"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,
@@ -286,16 +301,25 @@
"<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": 5,
"interval": 60,
"signal": 10,
"on-click": "moonarch-cpugov"
},
"custom/gpu-usage": {
"exec": "moonarch-waybar-gpustat",
"return-type": "json",
"restart-interval": 10
"interval": 60
},
"battery": {
"bat": "BAT0",
@@ -306,18 +330,30 @@
},
"format": "{capacity}% {icon}",
"format-icons": [
"",
"",
"",
"",
""
"󰂎",
"󰁺",
"󰁻",
"󰁼",
"󰁽",
"󰁾",
"󰁿",
"󰂀",
"󰂁",
"󰂂",
"󰁹"
],
"max-length": 25
"max-length": 25,
"on-click": "moonarch-batsaver-toggle"
},
"custom/batsaver": {
"exec": "moonarch-waybar-batsaver",
"return-type": "json",
"interval": 30,
"signal": 9
},
"bluetooth": {
// "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": "󰂯",
"format-disabled": "󰂲",
"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}%",
@@ -369,13 +405,10 @@
"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": ""
}
@@ -399,114 +432,56 @@
"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": ""
},
// If no icon is found for a window, the default is used instead
"window-icon-default": "*",
"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,
// 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)
"column-borders": 0,
"floating-borders": 0,
"on-tile-click": "FocusWindow",
"on-tile-middle-click": "CloseWindow",
"on-tile-right-click": "",
"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
}
}
}
+168 -244
View File
@@ -1,22 +1,23 @@
/* ABOUTME: System-wide Waybar styling for Moonarch. */
/* ABOUTME: Uses GTK theme colors via @theme_* variables, Catppuccin Mocha palette. */
* {
border: none;
font-family: "Ubuntu Nerd Font", sans-serif;
font-family: "UbuntuSans Nerd Font", sans-serif;
font-size: 13px;
color: alpha(@theme_text_color, 0.8);
}
window#waybar {
/*background: transparent;*/
background: alpha(@theme_selected_fg_color, 0.2);
}
/*-----main groups----*/
.modules-right {
/* --- Module areas --- */
.modules-left {
margin: 0;
padding-left: 5px;
padding-right: 0;
border-radius: 0;
}
.modules-center {
@@ -24,106 +25,63 @@ window#waybar {
border-radius: 0;
}
.modules-left {
margin: 0 0 0 5px;
/* background-color:alpha(@theme_selected_bg_color, 0.15);
background: @theme_bg_color */
.modules-right {
margin: 0;
padding-left: 5px;
padding-right: 5px;
border-radius: 0;
margin-left: 5px;
}
/**
## 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,
/* --- Module boxes --- */
#power-profiles-daemon,
#privacy,
#gamemode,
.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);
}
#workspaces,
#taskbar,
#window {
padding: 0px 10px;
border-radius: 0;
margin: 0;
padding: 0;
background-color: transparent;
}
/**
* GROUPS
**/
/* --- Group children --- */
widget widget>* {
padding: 0 6px;
margin: 0;
background-color: transparent;
border-radius: 0;
}
/* --- Groups --- */
#sys,
#stats,
#net,
#sound {
margin: 0;
#sound,
#brightness,
#indicators {
padding: 0 4px;
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 {
border-radius: 4px;
padding: 0px 8px 0px 8px;
margin: 8px 0px 8px 0px;
padding: 0;
}
#custom-notification {
padding-right: 0
}
/**
* SOUND
**/
#cava {
background-color: transparent;
padding-left: 0;
}
/* --- Sound --- */
#pulseaudio-slider {
padding-left: 10px;
@@ -138,7 +96,6 @@ window#waybar {
border: none;
box-shadow: none;
background-color: @insensitive_bg_color;
border: 1px solid alpha(@theme_selected_bg_color, 0.1);
}
#pulseaudio-slider trough {
@@ -149,119 +106,64 @@ window#waybar {
}
#pulseaudio-slider highlight {
min-width: 5px;
border-radius: 5px;
background-color: #6c7086;
background-color: alpha(@theme_selected_bg_color, 0.5);
}
#custom-updates {
padding-right: 5px;
padding-left: 5px;
}
/**
* MISC
**/
#mpris {
border-radius: 0;
margin: 8px 10px 8px 0px;
padding: 0 10px;
}
/**
* WORKSPACES
**/
/*#workspaces {
padding: 0;
border-radius: 0
}
#workspaces button {
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);
}
#workspaces button.urgent {
color: #cc3436;
}
#workspaces button:hover {
background-color: alpha(@theme_selected_bg_color, 0.05);
box-shadow: inherit;
text-shadow: inherit;
}
/**
## INDICATORS
**/
#pulseaudio.muted {
color: #cc3436;
}
/* --- Brightness --- */
#backlight-slider {
padding-left: 10px;
padding-right: 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;
padding: 0;
margin: 0;
border: none;
font-size: 0;
opacity: 0;
}
/* --- Battery --- */
#battery {
padding: 0;
}
#battery.charging {
color: #2dcc36;
}
@@ -274,6 +176,79 @@ window#waybar {
color: #cc3436;
}
#custom-batsaver {
font-size: 10px;
}
/* --- Workspaces --- */
#workspaces button {
margin: 8px 2px;
padding: 0 2px 0 0;
outline: none;
border-radius: 4px;
background-color: alpha(@theme_selected_bg_color, 0.05);
}
#workspaces button.empty,
#workspaces button.active.empty {
color: inherit;
}
#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;
}
/* --- Taskbar --- */
#taskbar {
font-size: 8px;
opacity: 1;
}
#taskbar.empty {
opacity: 0;
}
#taskbar button {
padding: 3px 4px 0 10px;
border-bottom: 3px solid transparent;
border-radius: 0;
}
#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;
}
#temperature.critical {
color: #cc3436;
}
@@ -282,65 +257,14 @@ window#waybar {
color: alpha(@theme_selected_bg_color, 0.9);
}
#indicators {
padding-right: 5px;
padding-left: 5px;
border-radius: 0;
#custom-nightlight.on {
color: #f9e2af;
}
/**
* 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,6 +11,7 @@ colloid-catppuccin-theme-git
otf-openmoji
# Niri / Wayland Extras
walker-bin
elephant-desktopapplications-bin
elephant-clipboard-bin
elephant-bluetooth-bin
@@ -29,5 +30,11 @@ wl-color-picker
blueberry
waterfox-bin
# Media
mpv-modernz-git
mpv-thumbfast-git
# System & Tools
auto-cpufreq
stasis
wayland-pipewire-idle-inhibit
+2 -6
View File
@@ -63,6 +63,7 @@ waybar
swaync
cliphist
wl-clipboard
wlsunset
libnotify
upower
awww
@@ -90,17 +91,12 @@ viewnior
# Development
git
glab
go
neovim
npm
rustup
# System
docker
docker-compose
fwupd
plocate
rebuild-detector
snapper
snap-pac
ufw
+1 -7
View File
@@ -1,6 +1,6 @@
#!/bin/bash
# ABOUTME: Shared helper functions and constants for Moonarch scripts.
# ABOUTME: Sourced by post-install.sh, update.sh and transform.sh.
# ABOUTME: Sourced by post-install.sh.
# Path constants — BASH_SOURCE[1] resolves to the calling script, not lib.sh itself.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[1]}")" && pwd)"
@@ -8,7 +8,6 @@ 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 ---
@@ -24,11 +23,6 @@ read_packages() {
grep -v '^\s*#' "$1" | grep -v '^\s*$'
}
confirm() {
read -r -p "$1 [y/N] " response
[[ "$response" =~ ^[yY]$ ]]
}
# --- Prerequisite checks ---
check_not_root() {
+301
View File
@@ -0,0 +1,301 @@
#!/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
+55 -22
View File
@@ -4,6 +4,37 @@
set -euo pipefail
_t() {
# _t "english" "deutsch" — picks by $LANG
case "${LANG%%.*}" in
de_*) printf '%s' "$2" ;;
*) printf '%s' "$1" ;;
esac
}
# Pause on exit when launched from a GUI (Waybar sets MOONUP_WAIT=1).
# Runs via trap so it fires even when `set -e` aborts the script mid-way.
# Reads from /dev/tty because stdin may have been drained/closed by pacman/paru.
_pause_on_exit() {
local rc=$?
if [[ -n "${MOONUP_WAIT:-}" ]]; then
echo
if (( rc != 0 )); then
echo -e "\e[1;31m[Moonarch ERROR]\e[0m $(_t "Exited with error (code $rc)" "Mit Fehler beendet (Code $rc)")" >&2
fi
read -n 1 -s -r -p "$(_t "Press any key to close..." "Beliebige Taste drücken zum Schließen …")" < /dev/tty || true
echo
fi
}
trap _pause_on_exit EXIT
# --- i18n ---
# Reuse pacman's gettext catalog for prompts that match upstream msgids.
# For moonarch-specific strings fall back to _t() inline translation.
export TEXTDOMAIN=pacman
export TEXTDOMAINDIR=/usr/share/locale
YN=$(gettext "[Y/n]")
# --- Helper functions ---
log() {
@@ -19,8 +50,10 @@ read_packages() {
}
confirm() {
read -r -p "$1 [y/N] " response
[[ "$response" =~ ^[yY]$ ]]
# Read from /dev/tty so stdin state (drained by pacman/paru, closed by
# a Waybar launch) doesn't auto-confirm prompts by returning empty input.
read -r -p ":: $1 $YN " response < /dev/tty
[[ -z "$response" || "$response" =~ ^[yYjJ]$ ]]
}
OFFICIAL_PACKAGES="/usr/share/moonarch/official.txt"
@@ -29,79 +62,79 @@ AUR_PACKAGES="/usr/share/moonarch/aur.txt"
# --- Prerequisites ---
if [[ $EUID -eq 0 ]]; then
err "Do NOT run as root. The script uses sudo where needed."
err "$(_t "Do NOT run as root. The script uses sudo where needed." "Nicht als root ausführen. Das Script verwendet sudo wo nötig.")"
exit 1
fi
# --- 1. Update system packages ---
log "=== Update system packages ==="
log "=== $(gettext $'Starting full system upgrade...\n' | tr -d '\n.') ==="
if confirm "Run pacman -Syu?"; then
if confirm "$(_t "Run pacman -Syu?" "pacman -Syu ausführen?")"; then
sudo pacman -Syu
else
log "System update skipped."
log "$(_t "System update skipped." "Systemaktualisierung übersprungen.")"
fi
if command -v paru &>/dev/null; then
if confirm "Update AUR packages (paru -Sua)?"; then
if confirm "$(_t "Update AUR packages (paru -Sua)?" "AUR-Pakete aktualisieren (paru -Sua)?")"; then
paru -Sua
else
log "AUR update skipped."
log "$(_t "AUR update skipped." "AUR-Aktualisierung übersprungen.")"
fi
fi
# --- 2. Reconcile package lists ---
log "=== Reconcile package lists ==="
log "=== $(_t "Reconcile package lists" "Paketlisten abgleichen") ==="
if [[ -f "$OFFICIAL_PACKAGES" ]]; then
MISSING_OFFICIAL=$(comm -23 <(read_packages "$OFFICIAL_PACKAGES" | sort) <(pacman -Qqe | sort) || true)
MISSING_OFFICIAL=$(comm -23 <(read_packages "$OFFICIAL_PACKAGES" | sort) <(pacman -Qq | sort) || true)
if [[ -n "$MISSING_OFFICIAL" ]]; then
log "Missing official packages:"
log "$(_t "Missing official packages:" "Fehlende offizielle Pakete:")"
echo "$MISSING_OFFICIAL"
if confirm "Install?"; then
if confirm "$(gettext 'Proceed with installation?')"; then
# shellcheck disable=SC2086
sudo pacman -S --needed --noconfirm $MISSING_OFFICIAL
fi
else
log "All official packages installed."
log "$(_t "All official packages installed." "Alle offiziellen Pakete installiert.")"
fi
fi
if [[ -f "$AUR_PACKAGES" ]] && command -v paru &>/dev/null; then
MISSING_AUR=$(comm -23 <(read_packages "$AUR_PACKAGES" | sort) <(pacman -Qqe | sort) || true)
MISSING_AUR=$(comm -23 <(read_packages "$AUR_PACKAGES" | sort) <(pacman -Qq | sort) || true)
if [[ -n "$MISSING_AUR" ]]; then
log "Missing AUR packages:"
log "$(_t "Missing AUR packages:" "Fehlende AUR-Pakete:")"
echo "$MISSING_AUR"
if confirm "Install?"; then
if confirm "$(gettext 'Proceed with installation?')"; then
# shellcheck disable=SC2086
paru -S --needed --noconfirm $MISSING_AUR
fi
else
log "All AUR packages installed."
log "$(_t "All AUR packages installed." "Alle AUR-Pakete installiert.")"
fi
fi
# --- 3. Orphaned packages ---
log "=== Orphaned packages ==="
log "=== $(_t "Orphaned packages" "Verwaiste Pakete") ==="
ORPHANS=$(pacman -Qdtq 2>/dev/null || true)
if [[ -n "$ORPHANS" ]]; then
log "Orphaned packages found:"
log "$(_t "Orphaned packages found:" "Verwaiste Pakete gefunden:")"
echo "$ORPHANS"
if confirm "Remove?"; then
if confirm "$(gettext 'Do you want to remove these packages?')"; then
# shellcheck disable=SC2086
sudo pacman -Rsn --noconfirm $ORPHANS
fi
else
log "No orphaned packages."
log "$(_t "No orphaned packages." "Keine verwaisten Pakete.")"
fi
# --- Done ---
log ""
log "============================================"
log " Moonarch update complete!"
log " $(_t "Moonarch update complete!" "Moonarch-Aktualisierung abgeschlossen!")"
log "============================================"
+75 -51
View File
@@ -14,34 +14,77 @@ check_pacman
# --- Install paru (AUR Helper) ---
if ! command -v paru &>/dev/null; then
log "Installing paru build dependencies..."
sudo pacman -S --needed --noconfirm base-devel rust
log "Installing paru..."
sudo pacman -S --needed --noconfirm paru
PARU_TMPDIR=$(mktemp -d)
trap 'rm -rf "$PARU_TMPDIR"' EXIT
git clone https://aur.archlinux.org/paru.git "$PARU_TMPDIR/paru"
(cd "$PARU_TMPDIR/paru" && makepkg -si --noconfirm)
rm -rf "$PARU_TMPDIR"
trap - EXIT
else
log "paru already installed."
fi
# --- Set up Moonarch custom paru repo ---
# --- Set up 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'
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
SigLevel = Required DatabaseOptional
Server = https://gitea.moonarch.de/api/packages/nevaforget/arch/$repo/$arch
EOCONF
log " + Moonarch repo added to paru.conf."
log " + Moonarch repo added to pacman.conf."
else
log " ~ Moonarch repo already in paru.conf."
log " ~ Moonarch repo already in pacman.conf."
fi
paru -Syu --pkgbuilds --noconfirm
log "Importing Moonarch registry signing key..."
EXPECTED_FINGERPRINT="9B02C596A4652C40CA768E75B90C8B82EA30A131"
KEY_FILE=$(mktemp)
trap 'rm -f "$KEY_FILE"' EXIT
curl -sf https://gitea.moonarch.de/api/packages/nevaforget/arch/repository.key -o "$KEY_FILE"
if [[ ! -s "$KEY_FILE" ]]; then
err "Failed to download registry key (empty response)."
exit 1
fi
KEY_FPR=$(gpg --show-keys --with-colons "$KEY_FILE" 2>/dev/null | awk -F: '/^fpr/{print $10; exit}')
if [[ "$KEY_FPR" != "$EXPECTED_FINGERPRINT" ]]; then
err "Registry key fingerprint mismatch! Expected $EXPECTED_FINGERPRINT, got ${KEY_FPR:-<empty>}"
exit 1
fi
KEY_ID=$(gpg --show-keys --with-colons "$KEY_FILE" 2>/dev/null | awk -F: '/^pub/{print $5}')
if [[ -n "$KEY_ID" ]] && ! sudo pacman-key --list-keys "$KEY_ID" &>/dev/null; then
sudo pacman-key --add "$KEY_FILE"
sudo pacman-key --lsign-key "$KEY_ID"
log " + Registry key $KEY_ID imported and locally signed."
else
log " ~ Registry key already imported."
fi
rm -f "$KEY_FILE"
trap - EXIT
# --- Install moonarch-git (pulls in all configs, scripts, themes as dependencies) ---
# --- Install moonarch-git from the Arch registry ---
log "Installing moonarch-git package..."
sudo pacman -Sy --noconfirm
paru -S --needed --noconfirm moonarch-git
# --- Install AUR extras (cannot be hard depends of moonarch-git) ---
if [[ -f "$AUR_PACKAGES" ]]; then
log "Installing AUR packages from $AUR_PACKAGES..."
# shellcheck disable=SC2046
paru -S --needed --noconfirm $(read_packages "$AUR_PACKAGES")
else
err "AUR package list not found: $AUR_PACKAGES"
exit 1
fi
# --- User-level GTK4 symlinks for libadwaita apps ---
THEME_NAME="Colloid-Grey-Dark-Catppuccin"
@@ -67,39 +110,22 @@ 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."
mkdir -p "$HOME/.zshrc.d"
echo "# Load Moonarch defaults, add custom overrides in ~/.zshrc.d/ or below" > "$HOME/.zshrc"
echo "source /etc/zsh/zshrc.moonarch" >> "$HOME/.zshrc"
echo "source /etc/zsh/zshrc.moonarch" > "$HOME/.zshrc"
fi
# --- Seed Stasis user config ---
#
# Stasis reads ~/.config/stasis/stasis.rune (or /etc/stasis/stasis.rune as
# fallback) but never /etc/xdg/. Without a user config it writes its own
# upstream default on first start. Seed Moonarch's template so the bootstrap
# sees an existing file and skips. Never overwrite an existing user config.
if [[ ! -f "$HOME/.config/stasis/stasis.rune" && -f /etc/xdg/stasis/stasis.rune ]]; then
log "Seeding Moonarch stasis config to user home."
install -Dm644 /etc/xdg/stasis/stasis.rune "$HOME/.config/stasis/stasis.rune"
fi
# --- Enable systemd user services ---
@@ -107,7 +133,14 @@ 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
@@ -124,7 +157,6 @@ log "Enabling services..."
SERVICES=(
"NetworkManager"
"bluetooth"
"docker"
"greetd"
"systemd-timesyncd"
"ufw"
@@ -164,13 +196,6 @@ 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"
@@ -186,6 +211,5 @@ log ""
log "Next steps:"
log " 1. Reboot"
log " 2. Place wallpapers in ~/Pictures/Wallpaper/"
log " 3. rustup default stable"
log " 4. User overrides in ~/.config/ or ~/.zshrc.d/"
log " 3. User overrides in ~/.config/"
log ""
-397
View File
@@ -1,397 +0,0 @@
#!/bin/bash
# ABOUTME: Transforms an existing Arch+Wayland system into a Moonarch system.
# ABOUTME: Backs up configs, installs moonarch-git package, deploys user configs with hard overwrite.
set -euo pipefail
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib.sh"
# --- Parse arguments ---
DRY_RUN=false
if [[ "${1:-}" == "--dry-run" ]]; then
DRY_RUN=true
fi
# ============================================================
# Phase 1: Prerequisites & Detection
# ============================================================
check_not_root
check_pacman
# Require active Wayland session
if [[ "${XDG_SESSION_TYPE:-}" != "wayland" ]] && [[ -z "${WAYLAND_DISPLAY:-}" ]]; then
err "No active Wayland session detected. Transform requires Wayland."
exit 1
fi
# Detect conflicting display managers
CONFLICTING_DMS=()
for dm in sddm gdm lightdm ly lemurs; do
if systemctl is-enabled "${dm}.service" &>/dev/null; then
CONFLICTING_DMS+=("$dm")
fi
done
# Detect conflicting packages
CONFLICTING_PKGS=()
if pacman -Qq pulseaudio &>/dev/null && ! pacman -Qq pipewire-pulse &>/dev/null; then
CONFLICTING_PKGS+=("pulseaudio")
fi
# ============================================================
# Phase 2: Pre-flight Summary
# ============================================================
echo ""
if $DRY_RUN; then
log "============================================"
log " Moonarch Transform — Dry Run"
log "============================================"
else
log "============================================"
log " Moonarch Transform — Pre-flight Summary"
log "============================================"
fi
echo ""
log "Wayland session: detected"
if [[ ${#CONFLICTING_DMS[@]} -gt 0 ]]; then
log "Display managers: ${CONFLICTING_DMS[*]} (will be disabled)"
else
log "Display managers: none conflicting"
fi
if [[ ${#CONFLICTING_PKGS[@]} -gt 0 ]]; then
log "Package conflicts: ${CONFLICTING_PKGS[*]} (will be removed)"
else
log "Package conflicts: none"
fi
echo ""
log "Actions:"
log " 1. (Optional) Backup ~/.config/, ~/.zshrc, /etc/xdg/"
log " 2. Install moonarch-git package (pulls in all dependencies)"
log " 3. Disable conflicting DMs, enable greetd"
log " 4. Overwrite ALL user configs (~/.config/)"
log " 5. Configure GTK themes, firewall, services"
echo ""
# Show package diff
MISSING_OFFICIAL=$(comm -23 <(read_packages "$OFFICIAL_PACKAGES" | sort) <(pacman -Qqe | sort) 2>/dev/null || true)
MISSING_AUR=""
if [[ -f "$AUR_PACKAGES" ]]; then
MISSING_AUR=$(comm -23 <(read_packages "$AUR_PACKAGES" | sort) <(pacman -Qqe | sort) 2>/dev/null || true)
fi
if [[ -n "$MISSING_OFFICIAL" ]] || [[ -n "$MISSING_AUR" ]]; then
log "Packages to install:"
if [[ -n "$MISSING_OFFICIAL" ]]; then
OFFICIAL_COUNT=$(echo "$MISSING_OFFICIAL" | wc -l)
log " Official ($OFFICIAL_COUNT):"
echo "$MISSING_OFFICIAL" | sed 's/^/ /'
fi
if [[ -n "$MISSING_AUR" ]]; then
AUR_COUNT=$(echo "$MISSING_AUR" | wc -l)
log " AUR ($AUR_COUNT):"
echo "$MISSING_AUR" | sed 's/^/ /'
fi
else
log "Packages: all already installed"
fi
# Show config diff summary
CHANGED_XDG=0
CHANGED_BIN=0
for src_dir in "$DEFAULTS_DIR/xdg/"*/; do
app_name="$(basename "$src_dir")"
dest_dir="$HOME/.config/$app_name"
if [[ -d "$dest_dir" ]]; then
if ! diff -rq "$src_dir" "$dest_dir" &>/dev/null 2>&1; then
((CHANGED_XDG++)) || true
fi
else
((CHANGED_XDG++)) || true
fi
done
for bin in "$DEFAULTS_DIR/bin/moonarch-"*; do
name=$(basename "$bin")
if ! cmp -s "$bin" "/usr/bin/$name" 2>/dev/null; then
((CHANGED_BIN++)) || true
fi
done
log "Config changes: $CHANGED_XDG XDG app(s), $CHANGED_BIN helper script(s)"
echo ""
err "This will REPLACE your current desktop configuration."
echo ""
if $DRY_RUN; then
log "Dry run complete — no changes were made."
exit 0
fi
if ! confirm "Proceed?"; then
log "Transform cancelled."
exit 0
fi
# ============================================================
# Phase 3: Backup (optional)
# ============================================================
BACKUP_FILE=""
SYSTEM_BACKUP=""
if confirm "Create backup of current configs before overwriting?"; then
BACKUP_FILE="$HOME/moonarch-backup-$(date +%Y%m%d-%H%M%S).tar.gz"
log "Creating backup: $BACKUP_FILE"
# Build list of paths that actually exist
BACKUP_PATHS=()
[[ -d "$HOME/.config" ]] && BACKUP_PATHS+=(".config")
[[ -f "$HOME/.zshrc" ]] && BACKUP_PATHS+=(".zshrc")
[[ -d "$HOME/.zshrc.d" ]] && BACKUP_PATHS+=(".zshrc.d")
if [[ ${#BACKUP_PATHS[@]} -gt 0 ]]; then
tar czf "$BACKUP_FILE" -C "$HOME" "${BACKUP_PATHS[@]}"
log " + User configs backed up."
fi
# Backup system XDG separately (needs sudo)
if [[ -d /etc/xdg ]]; then
SYSTEM_BACKUP="$HOME/moonarch-backup-system-$(date +%Y%m%d-%H%M%S).tar.gz"
sudo tar czf "$SYSTEM_BACKUP" -C / etc/xdg
sudo chown "$USER:$USER" "$SYSTEM_BACKUP"
log " + System configs backed up: $SYSTEM_BACKUP"
fi
log "Backup complete: $(du -h "$BACKUP_FILE" | cut -f1)"
else
log "Skipping backup."
fi
# ============================================================
# Phase 4: Disable Conflicting Display Managers
# ============================================================
if [[ ${#CONFLICTING_DMS[@]} -gt 0 ]]; then
log "Disabling conflicting display managers..."
for dm in "${CONFLICTING_DMS[@]}"; do
sudo systemctl disable "$dm"
log " - $dm disabled"
done
fi
# ============================================================
# Phase 5: Remove Conflicting Packages
# ============================================================
if pacman -Qq pulseaudio &>/dev/null; then
log "Removing PulseAudio (replaced by PipeWire)..."
sudo pacman -Rdd --noconfirm pulseaudio pulseaudio-alsa pulseaudio-bluetooth 2>/dev/null || true
fi
# ============================================================
# Phase 6: Install moonarch-git Package
# ============================================================
# Install paru if not present
if ! command -v paru &>/dev/null; then
log "Installing paru..."
sudo pacman -S --needed --noconfirm paru
else
log "paru already installed."
fi
# Moonarch 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
@@ -1,14 +0,0 @@
#!/bin/bash
# ABOUTME: Legacy updater — replaced by moonarch-update from the moonarch-git package.
# ABOUTME: Prints deprecation notice and forwards if package version is available.
echo ""
echo -e "\e[1;33m[Moonarch]\e[0m moonarch-update is now provided by the moonarch-git package."
echo -e "\e[1;33m[Moonarch]\e[0m Install with: paru -S moonarch-git"
echo -e "\e[1;33m[Moonarch]\e[0m Then run: moonarch-update"
echo ""
if command -v moonarch-update &>/dev/null && [[ "$(which moonarch-update)" == "/usr/bin/moonarch-update" ]]; then
echo -e "\e[1;34m[Moonarch]\e[0m Package version detected. Forwarding..."
exec moonarch-update
fi