Compare commits
23 Commits
42801548b7
...
v1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82659566ac | ||
|
|
994025f0c8 | ||
|
|
b0b2a4d732 | ||
|
|
964a5c98b7 | ||
|
|
c45fb80b79 | ||
|
|
0c668c2eb1 | ||
|
|
1f4bd3d5fa | ||
|
|
017bb1aa41 | ||
|
|
b856bb93e8 | ||
|
|
e0ee5b9e7e | ||
|
|
f68b245d92 | ||
|
|
fb748c217c | ||
|
|
8d3911de1a | ||
|
|
6ad80c045e | ||
|
|
54c6e100fb | ||
|
|
eb76bd61b4 | ||
|
|
71f37bfc30 | ||
|
|
69f959365e | ||
|
|
aef45b3c8d | ||
|
|
0897391cfb | ||
|
|
5c83b52727 | ||
|
|
0751102915 | ||
|
|
5c59da73e7 |
58
README.md
58
README.md
@@ -188,6 +188,9 @@ examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**.
|
||||
| `nomarchy.terminal` | `"ghostty"` | Terminal for keybinds and `$TERMINAL` |
|
||||
| `nomarchy.keyboard.layout` | `"us"` | XKB layout for the Hyprland session (installer writes the matching `services.xserver.xkb` in system.nix; the console + LUKS prompt follow via the distro default) |
|
||||
| `nomarchy.keyboard.variant` | `""` | XKB variant for the session |
|
||||
| `nomarchy.keyboard.devices` | `{}` | Per-device layout overrides (Hyprland `device` blocks keyed by `hyprctl devices` name) — e.g. an external keyboard with its own layout/variant |
|
||||
| `nomarchy.keyboard.layouts` | `[]` | Extra candidate layouts; when set, a watcher prompts (rofi) for a layout on a newly-connected keyboard and remembers it per-device |
|
||||
| `nomarchy.nightlight.enable` | `false` | Scheduled blue-light filter (hyprsunset) — warm at night (`.temperature`, default 4000K) between `.sunset`/`.sunrise`, no shift by day |
|
||||
| `nomarchy.hyprland.enable` | `true` | Nomarchy's Hyprland config |
|
||||
| `nomarchy.waybar.enable` | `true` | Nomarchy's Waybar |
|
||||
| `nomarchy.rofi.enable` | `true` | Themed rofi launcher + `nomarchy-menu` dispatcher |
|
||||
@@ -216,6 +219,23 @@ examples: **[docs/OVERRIDES.md](docs/OVERRIDES.md)**.
|
||||
| `nomarchy.system.power.laptop` | `false` | Marks a laptop, gating battery-only features; the installer sets it when a battery is present |
|
||||
| `nomarchy.system.power.thermal.enable` | `false` | thermald (Intel-only); the installer enables it on a GenuineIntel CPU |
|
||||
| `nomarchy.system.power.batteryChargeLimit` | `null` | Stop charging at this % (e.g. `80`) where the hardware supports it; needs `power.laptop` |
|
||||
| `nomarchy.services.tailscale.enable` | `false` | Opt-in: Tailscale mesh VPN (then `sudo tailscale up`) |
|
||||
| `nomarchy.services.syncthing.enable` | `false` | Opt-in: Syncthing file sync as the login user (GUI at `127.0.0.1:8384`) |
|
||||
| `nomarchy.services.podman.enable` | `false` | Opt-in: rootless Podman (`docker` aliased to it) |
|
||||
| `nomarchy.services.flatpak.enable` | `false` | Opt-in: Flatpak + the Flathub remote |
|
||||
| `nomarchy.services.pika.enable` | `false` | Opt-in: Pika Backup (GUI Borg backups) |
|
||||
| `nomarchy.services.steam.enable` | `false` | Opt-in: Steam via `programs.steam` (32-bit libs, controller udev, Remote-Play ports) |
|
||||
| `nomarchy.services.libvirt.enable` | `false` | Opt-in: libvirt/KVM + virt-manager GUI (login user added to `libvirtd`) |
|
||||
| `nomarchy.services.obs.enable` | `false` | Opt-in: OBS Studio + a v4l2loopback virtual camera (selectable as a webcam in Zoom/Teams) |
|
||||
| `nomarchy.services.docker.enable` | `false` | Opt-in: rootful Docker (user in `docker` group); don't enable alongside podman |
|
||||
| `nomarchy.services.kdeconnect.enable` | `false` | Opt-in: KDE Connect phone integration (opens its firewall ports) |
|
||||
| `nomarchy.services.gamemode.enable` | `false` | Opt-in: Feral GameMode performance daemon (`gamemoderun`) |
|
||||
| `nomarchy.services.adb.enable` | `false` | Opt-in: Android adb/fastboot tools (systemd handles device udev rules) |
|
||||
| `nomarchy.services.wireshark.enable` | `false` | Opt-in: Wireshark Qt GUI (user in `wireshark` group, capture without root) |
|
||||
| `nomarchy.services.ollama.enable` | `false` | Opt-in: Ollama local LLM runtime on `127.0.0.1:11434` (CPU; GPU via `services.ollama.acceleration`) |
|
||||
| `nomarchy.services.printing.enable` | `false` | Opt-in: CUPS + Avahi/mDNS network printer discovery |
|
||||
| `nomarchy.services.openrgb.enable` | `false` | Opt-in: OpenRGB daemon for RGB peripheral/motherboard lighting |
|
||||
| `nomarchy.services.restic.enable` | `false` | Opt-in: scheduled daily restic backup (set `.repository` + `.passwordFile`; 7/4/6 retention; list/restore via the `restic-nomarchy` wrapper) |
|
||||
|
||||
Beyond the `nomarchy.*` surface, the system layer turns on the usual
|
||||
desktop services with `lib.mkDefault` (override natively). One worth
|
||||
@@ -288,6 +308,32 @@ picker · `SUPER+SHIFT+T` next wallpaper · `SUPER+X` power menu ·
|
||||
clipboard history · `SUPER+Q`
|
||||
close · `SUPER+1..9` workspaces · `Print` region screenshot.
|
||||
|
||||
Shell aliases (zsh, gated on `nomarchy.shell.enable`) — `alias` lists them
|
||||
all; the curated set:
|
||||
|
||||
```sh
|
||||
# navigation
|
||||
.. ... .... # cd up 1 / 2 / 3 levels
|
||||
|
||||
# git
|
||||
g gst # git · git status -sb
|
||||
ga gaa # git add · git add -A
|
||||
gc gcm # git commit · git commit -m
|
||||
gco gsw gb # checkout · switch · branch
|
||||
gd gds # diff · diff --staged
|
||||
gl glg # log graph (last 20 · all branches)
|
||||
gp gpl gf # push · pull · fetch --all --prune
|
||||
|
||||
# nix (system/home rebuilds keep their full sys-update / home-update names)
|
||||
ns nr # nix shell · nix run (e.g. ns nixpkgs#ripgrep)
|
||||
nfu nfc # nix flake update · check
|
||||
nsearch ngc # nix search nixpkgs · nix-collect-garbage -d
|
||||
|
||||
# misc
|
||||
path # print $PATH, one entry per line
|
||||
reload # exec zsh (reload the shell)
|
||||
```
|
||||
|
||||
## 6. Extending
|
||||
|
||||
- **New theme:** drop a JSON into `themes/` (schema = any existing preset),
|
||||
@@ -296,6 +342,18 @@ close · `SUPER+1..9` workspaces · `Print` region screenshot.
|
||||
the Nix modules. One place — there is no second renderer to keep in sync.
|
||||
- **Importing more old-distro palettes:**
|
||||
`tools/import-palettes.py <palettes-dir> themes/`.
|
||||
- **New opt-in feature (convention):** when a feature is off by default and
|
||||
needs the user to set `nomarchy.*` options (e.g. night light, per-device
|
||||
keyboard layouts, monitor layout, power management), ship a **commented**
|
||||
example of it in `templates/downstream/home.nix` or `system.nix`. New
|
||||
installs then enable it by uncommenting + tweaking, rather than hunting the
|
||||
docs for the option name.
|
||||
- **Applications:** the starter complete-workstation set (LibreOffice, VS
|
||||
Code, GIMP, Inkscape) lives in the `home.packages` list in your
|
||||
`home.nix` — it's a plain package list, so curate it directly: delete a
|
||||
line to slim the machine, uncomment an extra (a browser, email, full TeX
|
||||
Live), or add your own. No `nomarchy.apps.*` toggles — a package list is
|
||||
already its own opt-out, so the distro doesn't impose these or wrap them.
|
||||
|
||||
## Roadmap & known issues
|
||||
|
||||
|
||||
250
docs/ROADMAP.md
250
docs/ROADMAP.md
@@ -37,6 +37,20 @@ how to override it. Items marked ✓ are shipped.
|
||||
launcher, no second theming pipeline; the dispatcher owns the menu
|
||||
structure, so the renderer stays swappable (we moved fuzzel → rofi 2.0
|
||||
once mainline gained native Wayland, for its richer theming)
|
||||
- ✓ **Organize the picker into category submenus:** the root picker had grown
|
||||
into a long flat list of 16 entries. It's now six: **Apps · Theme · Tools ›
|
||||
· System › · Power · Keybindings**. `Tools` (Calculator, Clipboard, Emoji,
|
||||
Files, Web, Capture, Ask) and `System` (Network, Bluetooth, DND, and the
|
||||
self-gated Snapshots / Power-profile) are submenus the dispatcher routes via
|
||||
`nomarchy-menu tools|system`, each ending in a `← Back` entry that re-opens
|
||||
the root. The direct `SUPER+CTRL+<mnemonic>` binds still hit the leaves
|
||||
straight (bypassing the menu), and the self-gated entries still hide when
|
||||
unavailable. Remaining (optional): a **Look & Feel** category once there are
|
||||
more appearance toggles to group with Theme (night-light, wallpaper).
|
||||
**General pattern (ongoing):** the grouping is a standing convention, not a
|
||||
one-off — any new feature that earns a menu entry must be placed in the
|
||||
right submenu (don't let the root creep back to a flat list), with its
|
||||
direct `SUPER+CTRL+<mnemonic>` bind and self-gating as applicable.
|
||||
- ✓ **Menu modules from rofi plugins:** the old `calc` flow committed the
|
||||
expression blind (result only in the *next* menu's `-mesg`) and `qalc -t`
|
||||
misparsed common phrasings (`15% of 200` → `rem(15, 1 B)`, the natural-
|
||||
@@ -99,10 +113,15 @@ how to override it. Items marked ✓ are shipped.
|
||||
summer-day/night menu buttons now use its `U+F000` glyph with
|
||||
`font-family: Nomarchy` pinned in their CSS (Nerd Fonts also occupy
|
||||
U+F000, so the pin is required). The other themes have no logo button.
|
||||
- **Quality-of-life command aliases:** assemble a curated collection of
|
||||
shell aliases/abbreviations for common operations (git, nix, navigation,
|
||||
the nomarchy helpers, …), themed into the zsh shell experience
|
||||
(`modules/home/shell.nix`). Decide scope and which to ship on by default.
|
||||
- ✓ **Quality-of-life command aliases:** a curated set ships on by default in
|
||||
`modules/home/shell.nix` — navigation (`..`/`...`/`....`), a git block
|
||||
(`g`, `gst`, `ga`/`gaa`, `gc`/`gcm`, `gco`/`gsw`, `gb`, `gd`/`gds`,
|
||||
`gl`/`glg`, `gp`/`gpl`, `gf`), and a nix block (`ns`, `nr`, `nfu`, `nfc`,
|
||||
`nsearch`, `ngc`), plus `path` and `reload`. Scope decision: short where it
|
||||
helps but **never shadow a real binary** (same rule as rg/fd — the git block
|
||||
deliberately avoids `gs`/ghostscript); system/home rebuilds keep their full
|
||||
`sys-update`/`home-update` names rather than getting cryptic aliases. The set
|
||||
is documented in README §5 (Day-to-day), and `alias` lists them in-shell.
|
||||
- **Theme-switch feedback:** ✓ the "rebuilding…" notification is now
|
||||
persistent (timeout 0) and replaced in place by "applied ✓" / failure
|
||||
via a synchronous tag, so a multi-minute switch never reads as a failed
|
||||
@@ -119,11 +138,21 @@ how to override it. Items marked ✓ are shipped.
|
||||
tracks the palette), `ls`→eza, `cd`→zoxide, plus `lt`/`tree`.
|
||||
`nomarchy.shell.enable`. (Deliberately did NOT alias grep→rg / find→fd:
|
||||
their flags differ enough to surprise; `rg`/`fd` ship as themselves.)
|
||||
- **Default application suite:** install a complete-workstation set
|
||||
(vscode, libreoffice, gimp, inkscape, texlive-full, …) behind a
|
||||
`nomarchy.apps.*` option surface so each is individually opt-out for
|
||||
users who want a leaner machine. Watch closure size (texlive-full is
|
||||
multi-GB — likely default-off or a lighter scheme).
|
||||
- ✓ **Default application suite:** a starter complete-workstation set —
|
||||
`libreoffice-fresh`, `vscode`, `gimp`, `inkscape` — ships *active* in the
|
||||
downstream `templates/downstream/home.nix` `home.packages`, with the heavier
|
||||
opt-ins (`texliveFull` — multi-GB; `texliveMedium` is lighter — plus
|
||||
browser/email) commented just below. **Decision: no `nomarchy.apps.*` option
|
||||
surface.** Unlike `nomarchy.services.*` (real config behind each toggle —
|
||||
systemd units, subuid ranges, the Flathub oneshot), a bare package install
|
||||
has nothing behind the toggle: `mkIf x { environment.systemPackages = [p]; }`
|
||||
just reinholds what a package list already gives, and the honest opt-out for
|
||||
a package is *deleting the line*. So the suite is the user's own curated list
|
||||
(extending the existing `# firefox` convention), not a distro-module feature —
|
||||
which also keeps the apps out of the live ISO automatically (it never starts
|
||||
from the template) and respects that editor/office/graphics are personal-taste
|
||||
choices, not defaults to impose. Unfree `vscode` evaluates fine — `mkFlake`
|
||||
and the system both set `allowUnfree = true`.
|
||||
- ✓ **Plymouth logo contrast:** the shipped art was a fixed navy that
|
||||
vanished on dark bases. `modules/nixos/plymouth.nix` now recolors every
|
||||
element from the palette at build time (flat fill, alpha kept):
|
||||
@@ -163,8 +192,18 @@ how to override it. Items marked ✓ are shipped.
|
||||
a batch is tested — rolling, no tags. Brought `main` current (it was a
|
||||
stale legacy-merge), and deleted the dead `refactor` / `wave/nixos-26.05`
|
||||
branches (kept `legacy` for history). See the comment in `flake.nix`.
|
||||
- Remaining (optional): re-check that every file still earns its place
|
||||
under the `modules`/`hosts`/`themes`/`pkgs`/`tools` rule of thumb.
|
||||
- ✓ re-checked that every file earns its place under the
|
||||
`modules`/`hosts`/`themes`/`pkgs`/`tools` rule of thumb: no orphaned
|
||||
modules (`keybinds.nix` is a *data* module `import`-ed by hyprland/rofi,
|
||||
so correctly absent from `default.nix`), no dead-code markers, no
|
||||
editor/backup cruft, each `themes/<slug>.json` palette pairs with its
|
||||
optional `<slug>/` overrides dir, and `hosts/{default,live}` are the
|
||||
documented reference + ISO hosts. Structure is clean as-is.
|
||||
- Open decision (deferred): the repo declares **no formatter** (no flake
|
||||
`formatter`, no `.editorconfig`); `.nix` files use deliberate aligned
|
||||
hand-formatting, so `nixfmt-rfc-style --check` flags ~33 files. Adopting a
|
||||
formatter would be a one-time repo-wide reformat that flattens that
|
||||
alignment — a maintainer call, not a silent cleanup, so left untouched.
|
||||
- **Full docs review & restructure:** a dedicated pass over all the prose,
|
||||
not just the code-adjacent cleanup the repo-sanitize item covers. The
|
||||
README has grown to ~540 lines with a ~200-line roadmap inline — the
|
||||
@@ -204,34 +243,98 @@ how to override it. Items marked ✓ are shipped.
|
||||
idle behaviour.
|
||||
- Remaining: a boot-only→event-driven charge-limit re-apply (udev) if a
|
||||
firmware resets the threshold on unplug.
|
||||
- Remaining: the `custom/powerprofile` Waybar indicator only lands in the
|
||||
generated config — the summer-day/night whole-swap `waybar.jsonc` don't
|
||||
show it (the module's exec points at Nix-built script store paths, which
|
||||
can't go in static JSON). Fix by promoting `powerProfileStatus`/`Cycle`
|
||||
from `writeShellScript` to named `writeShellScriptBin`s on PATH, then
|
||||
reference them by bare name from both the generated config and the
|
||||
whole-swap themes (the same parity the DND bell now has). General
|
||||
pattern: any new generated-config module needs adding to the whole-swap
|
||||
themes too.
|
||||
- ✓ **Waybar parity:** the `custom/powerprofile` indicator now shows in the
|
||||
summer-day/night whole-swap themes too. `powerProfileStatus`/`Cycle` are
|
||||
named `writeShellScriptBin`s on PATH (`waybar.nix` home.packages), so the
|
||||
generated config and the static `waybar.jsonc` both exec them by bare
|
||||
name (the same parity the DND bell has). General pattern: any new
|
||||
generated-config module needs adding to the whole-swap themes too.
|
||||
- **Full hardware enablement (beyond nixos-hardware):** nixos-hardware
|
||||
profiles cover *basic* per-model functionality (quirks, drivers), but the
|
||||
*advanced* features of a machine — keyed more on the CPU/GPU generation
|
||||
than the model — are left for the user to wire up by hand. Close that gap
|
||||
without making it finnicky: extend the installer's existing autodetection
|
||||
(DMI → nixos-hardware profile today; add CPU vendor/arch, GPU, NPU,
|
||||
fingerprint reader, SSD) to switch on the *safe, broadly-beneficial*
|
||||
features automatically, and expose a small `nomarchy.hardware.*` surface
|
||||
(per-vendor where it helps, e.g. `nomarchy.hardware.amd.*`) for the heavy
|
||||
or experimental bits. Goal: full utilisation out of the box — opt-*out* for
|
||||
the safe defaults, opt-*in* for the multi-GB / experimental extras.
|
||||
Complements nixos-hardware (model quirks) rather than replacing it; keep
|
||||
the detection in the installer's `hardware-db` so an installed machine
|
||||
bakes the right defaults, all overridable through `nomarchy.hardware.*`.
|
||||
Worked example — ThinkPad T14s (Ryzen 7 PRO 7840U + Radeon 780M):
|
||||
- **GPU compute — ROCm:** the 780M is an RDNA3 iGPU (gfx1103) ROCm doesn't
|
||||
officially list, so it needs `HSA_OVERRIDE_GFX_VERSION=11.0.0`. Multi-GB
|
||||
closure → opt-in (`nomarchy.hardware.amd.rocm.enable`); unlocks GPU
|
||||
PyTorch / Ollama.
|
||||
- **Ryzen AI NPU (XDNA):** the `amdxdna` kernel driver (mainline 6.14+) +
|
||||
the XRT/xdna userspace runtime + firmware. Niche/experimental → opt-in.
|
||||
- **AMD P-State EPP (Zen 4 power):** `amd_pstate=active` for the EPP
|
||||
governor, which power-profiles-daemon (already shipped, `power.nix`)
|
||||
drives per profile. Broadly beneficial on Zen 2+ → default on when the
|
||||
installer sees an AMD CPU.
|
||||
- **Hardware video acceleration (VA-API):** mesa's radeonsi exposes VA-API
|
||||
on AMD (`LIBVA_DRIVER_NAME=radeonsi`, libva + drivers in
|
||||
`hardware.graphics.extraPackages`); Intel uses `intel-media-driver`.
|
||||
Broadly beneficial → default on, vendor-detected.
|
||||
- **LVFS firmware & biometrics:** fwupd already ships (✓, `services.fwupd`).
|
||||
Add fingerprint: `services.fprintd` when a reader is detected, with
|
||||
opt-in PAM integration for login/sudo (password-only stays the default
|
||||
for the cautious).
|
||||
- **SSD periodic TRIM:** `services.fstrim.enable` (weekly) — safe and
|
||||
beneficial, so default on (trivial enough it may warrant being
|
||||
distro-wide regardless).
|
||||
Sub-items here can graduate into their own roadmap entries as they're
|
||||
scoped; the unifying work is the detection + `nomarchy.hardware.*` surface.
|
||||
- **Opt-in services & integrations:** the counterpart to the opt-*out*
|
||||
application suite above — heavier or more personal integrations shipped
|
||||
**off by default**, each a `nomarchy.services.<name>.enable` toggle a
|
||||
downstream flips on in one line. Keeps the base lean while making common
|
||||
additions trivial. Candidates by area:
|
||||
- **cloud/sync:** Nextcloud client; Syncthing (`services.syncthing`)
|
||||
- **local AI:** LM Studio (`lmstudio`); Ollama (`services.ollama`, optional
|
||||
GPU accel) — pairs with the menu's Ask-Claude philosophy
|
||||
- **containers/VMs:** Docker/Podman; libvirt + virt-manager
|
||||
- **networking:** Tailscale (`services.tailscale`); WireGuard
|
||||
- **gaming/media:** Steam (`programs.steam`); OBS Studio
|
||||
- **devices:** printing (CUPS + Avahi); KDE Connect / phone integration;
|
||||
OpenRGB
|
||||
- **backup:** restic/borg (`services.restic`)
|
||||
- **escape hatch:** Flatpak (`services.flatpak`) for apps outside nixpkgs
|
||||
additions trivial. **✓ Seventeen shipped** in `modules/nixos/services.nix`
|
||||
(system-side), each with a commented example in the downstream `system.nix`
|
||||
template: `tailscale`, `syncthing` (runs as the login user, GUI on
|
||||
127.0.0.1:8384), `podman` (rootless, `docker` aliased, user subuid/subgid),
|
||||
`flatpak` (+ Flathub remote added by a oneshot), `pika` (Pika Backup GUI),
|
||||
`steam` (`programs.steam` — the 32-bit stack, controller udev, Remote-Play
|
||||
ports a bare package can't), `libvirt` (libvirtd + virt-manager, user added
|
||||
to the `libvirtd` group), `obs` (OBS Studio + a v4l2loopback virtual camera
|
||||
usable as a webcam in Zoom/Teams), `docker` (rootful; asserts against the
|
||||
podman docker-compat), `kdeconnect`, `gamemode`, `adb` (android-tools;
|
||||
systemd handles the udev rules), `wireshark` (Qt GUI + the `wireshark`
|
||||
group), `ollama` (local LLM API on 127.0.0.1:11434), `printing` (CUPS +
|
||||
Avahi mDNS), `openrgb` (RGB lighting daemon), and `restic` (scheduled
|
||||
daily backup — a small option surface: `repository`/`passwordFile`/`paths`,
|
||||
7/4/6 retention). More candidates by area:
|
||||
- ✓ **cloud/sync:** Syncthing (`services.syncthing`); Nextcloud client is a
|
||||
bare tray app (in the app-suite menu, not a service)
|
||||
- ✓ **local AI:** Ollama (`nomarchy.services.ollama`, optional GPU accel)
|
||||
ships; LM Studio is a (commented) app-suite package (a bare GUI, no
|
||||
service) — pairs with the menu's Ask-Claude philosophy
|
||||
- ✓ **containers/VMs:** Podman (`virtualisation.podman`); libvirt +
|
||||
virt-manager (`nomarchy.services.libvirt`); Docker (`nomarchy.services.docker`)
|
||||
- ✓ **networking:** Tailscale (`services.tailscale`); WireGuard needs no
|
||||
toggle — NetworkManager (on by default) imports `.conf` tunnels natively;
|
||||
the `wireguard-tools` CLI is in the app-suite menu
|
||||
- ✓ **gaming/media:** Steam (`nomarchy.services.steam`); OBS Studio
|
||||
(`nomarchy.services.obs`, with the v4l2loopback virtual camera); GameMode
|
||||
(`nomarchy.services.gamemode`)
|
||||
- ✓ **devices:** printing (CUPS + Avahi, `nomarchy.services.printing`); KDE
|
||||
Connect (`nomarchy.services.kdeconnect`); OpenRGB
|
||||
(`nomarchy.services.openrgb`). Plus dev tooling — Android `adb` and
|
||||
`wireshark` toggles
|
||||
- ✓ **backup:** Pika Backup (`pika-backup`, GUI over Borg); scheduled
|
||||
headless restic (`nomarchy.services.restic`)
|
||||
- ✓ **escape hatch:** Flatpak (`services.flatpak`) + Flathub, for apps
|
||||
outside nixpkgs
|
||||
|
||||
Decisions: the curated set; whether system services and GUI apps share one
|
||||
surface or split (`nomarchy.services.*` system-side vs home-side packages);
|
||||
and how it relates to `nomarchy.apps.*` (opt-out suite). Unfree entries are
|
||||
Surface decided: `nomarchy.services.*`, system-side for system services
|
||||
(home-side ones can extend the same prefix later). Remaining: curate the
|
||||
rest (Ollama, containers, Steam/OBS, printing, backups, Flatpak …). The
|
||||
opt-out application *suite* is deliberately **not** a parallel
|
||||
`nomarchy.apps.*` surface — it's a curated `home.packages` list in the
|
||||
downstream template (see "Default application suite" above), since a bare
|
||||
package install has no config to put behind a toggle. Unfree entries are
|
||||
already covered (`allowUnfree = true`).
|
||||
- ✓ **Display / monitor management:** declarative `nomarchy.monitors` (a
|
||||
per-output schema — name/resolution/position/scale/transform/mirror/
|
||||
@@ -245,14 +348,44 @@ how to override it. Items marked ✓ are shipped.
|
||||
switching** of the *same* outputs (Hyprland's per-output rules cover the
|
||||
common "external connects → arrange" case, not multi-layout toggles), and
|
||||
optionally workspace-to-monitor binding.
|
||||
- **Night light / blue-light filter:** schedulable colour-temperature shift
|
||||
via `hyprsunset` (Hyprland-native; wlsunset/gammastep are alternatives) —
|
||||
sunset/sunrise or a fixed schedule, with a menu + Waybar toggle.
|
||||
- **Runtime keyboard-layout switching:** the session layout is a single
|
||||
`nomarchy.keyboard.layout` today. Support a list of layouts with a toggle
|
||||
bind (`hyprctl switchxkblayout` / xkb `grp:` options) and a Waybar
|
||||
indicator for the active layout. Natural follow-on to the LUKS-keymap work
|
||||
— keep the system (console/initrd) and session layouts in sync.
|
||||
- ✓ **Night light / blue-light filter:** `nomarchy.nightlight` (opt-in) —
|
||||
a scheduled colour-temperature shift via `hyprsunset` (Hyprland-native),
|
||||
warm at night, identity (no shift) by day. `modules/home/nightlight.nix`
|
||||
drives the HM `services.hyprsunset` with two time-based profiles
|
||||
(`sunrise` → identity, `sunset` → `temperature`), so hyprsunset handles the
|
||||
schedule and the on-login state. Needs an on-hardware check that hyprsunset
|
||||
applies the active profile at session start. Remaining (optional): a menu +
|
||||
Waybar toggle to force it on/off, and geo (lat/long) auto sunset/sunrise
|
||||
(would mean wlsunset, which schedules by location).
|
||||
- **Keyboard layouts (per-device + switching):**
|
||||
- ✓ **Per-device declarative layout:** `nomarchy.keyboard.devices`
|
||||
(`{ "<hyprctl-device-name>" = { layout; variant; }; }`) generates Hyprland
|
||||
`device` blocks that override the session `nomarchy.keyboard.layout` for a
|
||||
named keyboard — e.g. an external board that's physically a different
|
||||
layout than the laptop's. Hyprland applies it whenever that device
|
||||
connects (the "external keyboard remembers its layout" case, done the
|
||||
declarative way). A Waybar `hyprland/language` indicator shows the active
|
||||
layout, gated on more than one layout being in play (comma in the session
|
||||
layout, or any per-device override) so single-layout bars stay clean.
|
||||
- ✓ **Interactive new-keyboard prompt (runtime + persist):** set
|
||||
`nomarchy.keyboard.layouts` (candidate layouts) and a `nomarchy-keyboard-
|
||||
watch` daemon runs (exec-once): it polls `hyprctl devices`, and when a
|
||||
keyboard connects *after* login that isn't in `keyboard.devices` and
|
||||
hasn't been chosen before, it pops a rofi layout picker, applies the
|
||||
choice with `hyprctl switchxkblayout` (an index into the candidate set,
|
||||
which `input.kb_layout` carries), and remembers it per-device in
|
||||
`~/.local/state/nomarchy/keyboard-layouts` — re-applied silently on later
|
||||
reconnects. The boot-time set (incl. the built-in keyboard) is never
|
||||
prompted. The runtime-remember complement to the declarative
|
||||
`keyboard.devices`; a stateful runtime piece by design. **Pending an
|
||||
on-hardware test** (hotplug isn't verifiable in CI). Remaining (bonus):
|
||||
offer to write a choice into `keyboard.devices` so it graduates to the
|
||||
reproducible config.
|
||||
- Remaining: a multi-layout cycle bind (`hyprctl switchxkblayout` / xkb
|
||||
`grp:` options) for switching layouts on one keyboard; add the
|
||||
`hyprland/language` module to the summer whole-swap themes for parity
|
||||
(the gating doesn't translate to their static JSON). Keep the system
|
||||
(console/initrd) and session layouts in sync (the LUKS-keymap work).
|
||||
- ✓ **Do-Not-Disturb:** swaync DND toggle wired into the menu
|
||||
(`nomarchy-menu dnd`, in the picker, SUPER+CTRL+D) and a Waybar bell
|
||||
indicator (`custom/notification` via `swaync-client -swb`: shows the
|
||||
@@ -260,10 +393,28 @@ how to override it. Items marked ✓ are shipped.
|
||||
right-click toggles DND). Turning DND off confirms with a toast; turning
|
||||
it on is silent (notifications are suppressed — the bell-off glyph is the
|
||||
cue).
|
||||
- **Snapshot browse/restore UX:** snapper already takes BTRFS timeline
|
||||
snapshots (`nomarchy.system.snapper`); surface them from the desktop — a
|
||||
rofi menu (or btrfs-assistant) to browse/diff/restore and boot-from-
|
||||
snapshot — so rollback isn't CLI-only.
|
||||
- ✓ **Snapshot browse/restore UX:** `nomarchy-menu snapshot` (in the SUPER+M
|
||||
picker, self-gated on snapper) launches **btrfs-assistant** — the
|
||||
desktop browse/diff/restore/rollback UI, elevating via polkit (the safe
|
||||
choice for a destructive, root-only op vs a fat-fingerable rofi rollback).
|
||||
Shipped system-side gated on `nomarchy.system.snapper`. Also extended
|
||||
snapper to snapshot **/home** by default (hourly 5 / daily 7 / weekly 4)
|
||||
when it's its own BTRFS subvolume; a boot oneshot creates the required
|
||||
`/home/.snapshots` subvolume if missing (covers existing installs, no disko
|
||||
change). **The backend is VM-verified** (a full BTRFS+LUKS install: both
|
||||
`root`+`home` configs, a `/home` timeline snapshot taken, `/home/.snapshots`
|
||||
a real subvolume, the oneshot `Result=success`).
|
||||
- ⚠ **Known bug — btrfs-assistant 2.2 segfaults on launch** in nixpkgs
|
||||
26.05: it crashes inside `libbtrfsutil.so.1.4.0` (a library ABI mismatch,
|
||||
confirmed in the VM regardless of GL/Qt platform — so it's not a VM/GL
|
||||
artifact and will crash on hardware too). The menu wiring is correct and
|
||||
the app is installed, but the GUI doesn't open. The snapshot *backend*
|
||||
(the actual snapshots) is unaffected. Fix path: a nixpkgs override aligning
|
||||
its libbtrfsutil, or wait for an upstream bump; meanwhile the menu entry
|
||||
is a no-op when the binary crashes. Fallback if it lingers: a rofi-based
|
||||
snapshot menu (no btrfs-assistant dependency).
|
||||
- Remaining (optional): a keyboard-driven rofi browse for quick glances;
|
||||
boot-from-snapshot needs a systemd-boot equivalent of grub-btrfs.
|
||||
- **Update awareness:** updates are manual today (`sys-update`/`home-update`);
|
||||
add a Waybar indicator / notification when flake inputs are stale or a new
|
||||
nixpkgs rev is available (optionally with a pending-change count). Augments,
|
||||
@@ -315,5 +466,8 @@ how to override it. Items marked ✓ are shipped.
|
||||
dark. `stylix.nix` now sets `dconf` `org/gnome/desktop/interface
|
||||
color-scheme` = `prefer-light`/`prefer-dark` from `t.mode`
|
||||
(xdg-desktop-portal-gtk already ships, and `programs.dconf` is on
|
||||
system-side), so libadwaita/Qt follow the palette. Needs a live session
|
||||
to confirm the portal picks it up for all apps.
|
||||
system-side), so libadwaita/Qt follow the palette. Confirmed in a live
|
||||
session (headless QEMU): the portal's `org.freedesktop.appearance`
|
||||
`color-scheme` reads `1` on a dark theme and flips to `2` after switching
|
||||
to a light one (catppuccin-latte) — so xdg-desktop-portal-gtk does relay
|
||||
the dconf value GTK4/Qt apps read.
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
./idle.nix # hyprlock + hypridle, themed from the same JSON
|
||||
./yazi.nix # flagship TUI file manager, themed + plugins
|
||||
./osd.nix # swayosd volume/brightness OSD, themed
|
||||
./nightlight.nix # scheduled blue-light filter (hyprsunset), opt-in
|
||||
./shell.nix # zsh + starship + bat/eza/zoxide, themed
|
||||
./keys.nix # gpg-agent (fronts SSH) + pinentry-qt
|
||||
./fastfetch.nix # system info with the themed Nomarchy logo
|
||||
|
||||
@@ -54,6 +54,64 @@ let
|
||||
++ lib.optionals (m.vrr != null) [ "vrr" (toString m.vrr) ]
|
||||
++ lib.optional (m.extra != "") m.extra
|
||||
);
|
||||
|
||||
# Keyboard layout candidates: the primary nomarchy.keyboard.layout (split on
|
||||
# commas) plus any nomarchy.keyboard.layouts. Drives input.kb_layout so all
|
||||
# of them are switchable, and feeds the watcher's picker.
|
||||
kbLayouts = lib.unique (
|
||||
(lib.splitString "," config.nomarchy.keyboard.layout)
|
||||
++ config.nomarchy.keyboard.layouts
|
||||
);
|
||||
kbAutoSwitch = config.nomarchy.keyboard.layouts != [ ];
|
||||
|
||||
# On a keyboard that connects after login (not declared in
|
||||
# keyboard.devices, not already remembered), ask for a layout and persist it
|
||||
# per-device, re-applying silently on later reconnects. Stateful runtime
|
||||
# piece — the complement to the declarative keyboard.devices. Reliable
|
||||
# primitives only: poll hyprctl devices, apply via switchxkblayout (an index
|
||||
# into kbLayouts). NOTE: needs an on-hardware test (hotplug isn't verifiable
|
||||
# in CI).
|
||||
keyboardWatch = pkgs.writeShellScriptBin "nomarchy-keyboard-watch" ''
|
||||
set -u
|
||||
layouts="${lib.concatStringsSep " " kbLayouts}"
|
||||
declared="${lib.concatStringsSep " " (builtins.attrNames config.nomarchy.keyboard.devices)}"
|
||||
state="''${XDG_STATE_HOME:-$HOME/.local/state}/nomarchy/keyboard-layouts"
|
||||
mkdir -p "$(dirname "$state")"; [ -f "$state" ] || : > "$state"
|
||||
|
||||
layout_index() { i=0; for l in $layouts; do [ "$l" = "$1" ] && { printf %s "$i"; return; }; i=$((i + 1)); done; printf %s -1; }
|
||||
apply() { idx=$(layout_index "$2"); [ "$idx" -ge 0 ] && hyprctl switchxkblayout "$1" "$idx" >/dev/null 2>&1; }
|
||||
keyboards() { hyprctl devices -j | jq -r '.keyboards[].name'; }
|
||||
is_declared() { case " $declared " in *" $1 "*) return 0 ;; *) return 1 ;; esac; }
|
||||
saved_for() { while IFS='=' read -r k v; do [ "$k" = "$1" ] && { printf '%s' "$v"; return; }; done < "$state"; }
|
||||
|
||||
# Startup: re-apply remembered layouts, never prompt — so the built-in
|
||||
# keyboard just stays on the session default.
|
||||
prev=" "
|
||||
for kb in $(keyboards); do
|
||||
prev="$prev$kb "
|
||||
is_declared "$kb" && continue
|
||||
s=$(saved_for "$kb"); [ -n "$s" ] && apply "$kb" "$s"
|
||||
done
|
||||
|
||||
# Watch for keyboards that connect later.
|
||||
while :; do
|
||||
sleep 3
|
||||
cur=" "
|
||||
for kb in $(keyboards); do
|
||||
cur="$cur$kb "
|
||||
case "$prev" in *" $kb "*) continue ;; esac
|
||||
is_declared "$kb" && continue
|
||||
s=$(saved_for "$kb")
|
||||
if [ -n "$s" ]; then
|
||||
apply "$kb" "$s"
|
||||
else
|
||||
choice=$(printf '%s\n' $layouts | rofi -dmenu -p "Layout · $kb")
|
||||
[ -n "$choice" ] && { printf '%s=%s\n' "$kb" "$choice" >> "$state"; apply "$kb" "$choice"; }
|
||||
fi
|
||||
done
|
||||
prev="$cur"
|
||||
done
|
||||
'';
|
||||
in
|
||||
{
|
||||
wayland.windowManager.hyprland = lib.mkIf config.nomarchy.hyprland.enable {
|
||||
@@ -88,7 +146,7 @@ in
|
||||
# Paint the wallpaper as soon as the session is up (waits for
|
||||
# the daemon internally).
|
||||
"nomarchy-theme-sync wallpaper"
|
||||
];
|
||||
] ++ lib.optional kbAutoSwitch "${keyboardWatch}/bin/nomarchy-keyboard-watch";
|
||||
|
||||
# ── Theme-driven look ──────────────────────────────────────────
|
||||
# These flow from theme-state.json and stay at NORMAL priority:
|
||||
@@ -144,12 +202,20 @@ in
|
||||
input = {
|
||||
# kb_layout/variant come from nomarchy.keyboard.* — set that
|
||||
# option (the installer writes it; it also drives tty/LUKS).
|
||||
kb_layout = config.nomarchy.keyboard.layout;
|
||||
kb_layout = lib.concatStringsSep "," kbLayouts;
|
||||
kb_variant = config.nomarchy.keyboard.variant;
|
||||
follow_mouse = lib.mkDefault 1;
|
||||
touchpad.natural_scroll = lib.mkDefault true;
|
||||
};
|
||||
|
||||
# Per-device keyboard layouts (nomarchy.keyboard.devices): each
|
||||
# overrides the session kb_layout for one named keyboard — e.g. an
|
||||
# external board that's physically a different layout than the laptop's
|
||||
# built-in one. Hyprland applies them whenever the device connects.
|
||||
device = lib.mapAttrsToList
|
||||
(name: d: { inherit name; kb_layout = d.layout; kb_variant = d.variant; })
|
||||
config.nomarchy.keyboard.devices;
|
||||
|
||||
# dwindle:pseudotile was removed in Hyprland 0.55 (the `pseudo`
|
||||
# dispatcher still exists); setting it aborts config parsing.
|
||||
dwindle.preserve_split = lib.mkDefault true;
|
||||
|
||||
25
modules/home/nightlight.nix
Normal file
25
modules/home/nightlight.nix
Normal file
@@ -0,0 +1,25 @@
|
||||
# Night light — a scheduled blue-light filter via hyprsunset (Hyprland's own
|
||||
# gamma/temperature tool). Warm at night, identity (no shift) by day;
|
||||
# hyprsunset's time-based `profile` entries handle the schedule and pick the
|
||||
# right state on session start. Opt-in via nomarchy.nightlight.enable.
|
||||
#
|
||||
# The hyprsunset HM service module is provided by home-manager; this only
|
||||
# configures it. Override anything with plain services.hyprsunset.* options.
|
||||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.nomarchy.nightlight;
|
||||
in
|
||||
{
|
||||
config = lib.mkIf cfg.enable {
|
||||
services.hyprsunset = {
|
||||
enable = true;
|
||||
settings.profile = [
|
||||
# Daytime: identity = no colour change.
|
||||
{ time = cfg.sunrise; identity = true; }
|
||||
# Night: shift to the warm temperature.
|
||||
{ time = cfg.sunset; temperature = cfg.temperature; }
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -96,6 +96,49 @@ in
|
||||
description = "XKB variant for the Hyprland session.";
|
||||
};
|
||||
|
||||
keyboard.layouts = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ ];
|
||||
example = [ "de" "fr" ];
|
||||
description = ''
|
||||
Extra candidate layouts offered by the interactive new-keyboard
|
||||
picker. When non-empty, a small watcher runs in the session: when a
|
||||
keyboard connects that isn't covered by nomarchy.keyboard.devices and
|
||||
hasn't been chosen before, it pops a rofi picker (these layouts plus
|
||||
the primary nomarchy.keyboard.layout), applies the choice, and
|
||||
remembers it per-device (~/.local/state) — re-applying automatically
|
||||
on later reconnects. The runtime-remember complement to the
|
||||
declarative keyboard.devices; a stateful runtime piece by design.
|
||||
'';
|
||||
};
|
||||
|
||||
keyboard.devices = lib.mkOption {
|
||||
type = lib.types.attrsOf (lib.types.submodule {
|
||||
options = {
|
||||
layout = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = "XKB layout for this specific keyboard.";
|
||||
};
|
||||
variant = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "";
|
||||
description = "XKB variant for this keyboard.";
|
||||
};
|
||||
};
|
||||
});
|
||||
default = { };
|
||||
example = lib.literalExpression ''{ "keychron-keychron-k2" = { layout = "de"; }; }'';
|
||||
description = ''
|
||||
Per-device keyboard layout. The key is the device name from
|
||||
`hyprctl devices` (lower-case, hyphenated). Each entry generates a
|
||||
Hyprland `device` block that overrides the session-wide
|
||||
nomarchy.keyboard.layout for that keyboard only — e.g. an external
|
||||
keyboard that's physically a different layout than the laptop's
|
||||
built-in one. Hyprland applies it automatically whenever that
|
||||
keyboard is connected.
|
||||
'';
|
||||
};
|
||||
|
||||
package = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
default = pkgs.nomarchy-theme-sync;
|
||||
@@ -114,6 +157,33 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
nightlight = {
|
||||
enable = lib.mkEnableOption ''
|
||||
a scheduled blue-light filter (hyprsunset): warm at night, no shift
|
||||
by day. Opt-in; tune the temperature + sunrise/sunset below'';
|
||||
|
||||
temperature = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 4000;
|
||||
example = 3500;
|
||||
description = "Warm colour temperature (K) applied at night — lower is warmer.";
|
||||
};
|
||||
|
||||
sunrise = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "07:00";
|
||||
example = "06:30";
|
||||
description = "Time (HH:MM) the filter turns OFF — daytime, no colour shift.";
|
||||
};
|
||||
|
||||
sunset = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "20:00";
|
||||
example = "21:00";
|
||||
description = "Time (HH:MM) the filter turns ON — warm.";
|
||||
};
|
||||
};
|
||||
|
||||
monitors = lib.mkOption {
|
||||
type = lib.types.listOf monitorType;
|
||||
default = [ ];
|
||||
|
||||
@@ -41,9 +41,9 @@ let
|
||||
nomarchy-menu = pkgs.writeShellScriptBin "nomarchy-menu" ''
|
||||
# Nomarchy menu dispatcher — thin presentation layer over
|
||||
# `rofi -dmenu`; actions delegate to nomarchy-theme-sync, systemctl,
|
||||
# hyprctl and friends. Icons are nf-md glyphs (Pango falls back to a
|
||||
# Nerd Font for them). `nomarchy-menu` with no argument shows the
|
||||
# module picker.
|
||||
# hyprctl and friends. Menu entries carry real icons from the theme's
|
||||
# icon set (Papirus) via rofi's per-row icon protocol — see row() below.
|
||||
# `nomarchy-menu` with no argument shows the module picker.
|
||||
|
||||
urlencode() {
|
||||
local s="$1" out="" ch i
|
||||
@@ -58,16 +58,22 @@ let
|
||||
printf '%s' "$out"
|
||||
}
|
||||
|
||||
# Emit one dmenu row with a themed icon: `row "Label" icon-name`. The NUL
|
||||
# + 0x1f field separator is rofi's per-row icon protocol; printed straight
|
||||
# to the pipe because bash can't store NUL in a variable. Rendered when
|
||||
# rofi has show-icons (on globally; passed explicitly on the icon menus).
|
||||
row() { printf '%s\0icon\x1f%s\n' "$1" "$2"; }
|
||||
|
||||
case "''${1:-}" in
|
||||
power)
|
||||
choice=$(printf '%s\n' \
|
||||
" Lock" \
|
||||
" Logout" \
|
||||
" Suspend" \
|
||||
" Hibernate" \
|
||||
" Reboot" \
|
||||
" Shutdown" \
|
||||
| rofi -dmenu -p power) || exit 0
|
||||
choice=$( {
|
||||
row "Lock" system-lock-screen
|
||||
row "Logout" system-log-out
|
||||
row "Suspend" system-suspend
|
||||
row "Hibernate" system-hibernate
|
||||
row "Reboot" system-reboot
|
||||
row "Shutdown" system-shutdown
|
||||
} | rofi -dmenu -show-icons -p Power) || exit 0
|
||||
case "$choice" in
|
||||
*Lock) loginctl lock-session ;;
|
||||
*Logout) hyprctl dispatch exit ;;
|
||||
@@ -134,12 +140,12 @@ let
|
||||
exec blueman-manager ;;
|
||||
|
||||
capture)
|
||||
choice=$(printf '%s\n' \
|
||||
" Region → clipboard" \
|
||||
" Region → file" \
|
||||
" Full screen → clipboard" \
|
||||
" Full screen → file" \
|
||||
| rofi -dmenu -p capture) || exit 0
|
||||
choice=$( {
|
||||
row "Region → clipboard" applets-screenshooter
|
||||
row "Region → file" applets-screenshooter
|
||||
row "Full screen → clipboard" camera-photo
|
||||
row "Full screen → file" camera-photo
|
||||
} | rofi -dmenu -show-icons -p Capture) || exit 0
|
||||
dir="$HOME/Pictures/Screenshots"
|
||||
file="$dir/$(date +%Y%m%d-%H%M%S).png"
|
||||
case "$choice" in
|
||||
@@ -172,51 +178,85 @@ let
|
||||
&& notify-send "Do Not Disturb off" "Notifications resumed."
|
||||
exit 0 ;;
|
||||
|
||||
"")
|
||||
entries=(
|
||||
" Apps"
|
||||
" Theme"
|
||||
" Clipboard"
|
||||
" Calculator"
|
||||
" Files"
|
||||
" Emoji"
|
||||
" Web search"
|
||||
" Network"
|
||||
" Bluetooth"
|
||||
" Capture"
|
||||
" Keybindings"
|
||||
" Ask Claude"
|
||||
" Do Not Disturb"
|
||||
)
|
||||
# Power-profile picker only when this is a laptop (battery present)
|
||||
# running power-profiles-daemon — matches the Waybar indicator's
|
||||
# self-gating; desktops and the TLP backend don't show it.
|
||||
bats=(/sys/class/power_supply/BAT*)
|
||||
if [ -e "''${bats[0]}" ] && command -v powerprofilesctl >/dev/null 2>&1; then
|
||||
entries+=(" Power profile")
|
||||
fi
|
||||
entries+=(" Power")
|
||||
choice=$(printf '%s\n' "''${entries[@]}" | rofi -dmenu -p menu) || exit 0
|
||||
snapshot)
|
||||
# btrfs-assistant: snapshot browse / diff / restore / rollback over
|
||||
# snapper, elevating via polkit. Shipped system-side with
|
||||
# nomarchy.system.snapper; self-gate so the entry no-ops elsewhere.
|
||||
command -v btrfs-assistant >/dev/null 2>&1 \
|
||||
|| { notify-send "Snapshots" "btrfs-assistant isn't installed (BTRFS snapshots off?)."; exit 0; }
|
||||
exec btrfs-assistant ;;
|
||||
|
||||
tools)
|
||||
# Tools submenu — utilities you invoke. Each leaf is also reachable
|
||||
# directly (SUPER+CTRL+<mnemonic>); this just keeps the root short.
|
||||
choice=$( {
|
||||
row "Calculator" accessories-calculator
|
||||
row "Clipboard" edit-paste
|
||||
row "Emoji" face-smile-big
|
||||
row "Files" system-file-manager
|
||||
row "Web search" system-search
|
||||
row "Capture" applets-screenshooter
|
||||
row "Ask Claude" internet-chat
|
||||
row "Back" go-previous
|
||||
} | rofi -dmenu -show-icons -p Tools) || exit 0
|
||||
case "$choice" in
|
||||
*Apps*) exec rofi -show drun ;;
|
||||
*Theme*) exec "$0" theme ;;
|
||||
*Clipboard*) exec "$0" clipboard ;;
|
||||
*Calc*) exec "$0" calc ;;
|
||||
*Files*) exec "$0" files ;;
|
||||
*Clipboard*) exec "$0" clipboard ;;
|
||||
*Emoji*) exec "$0" emoji ;;
|
||||
*Files*) exec "$0" files ;;
|
||||
*Web*) exec "$0" web ;;
|
||||
*Network*) exec "$0" network ;;
|
||||
*Bluetooth*) exec "$0" bluetooth ;;
|
||||
*Capture*) exec "$0" capture ;;
|
||||
*Keybindings*) exec "$0" keybinds ;;
|
||||
*Ask*) exec "$0" ask ;;
|
||||
*Back*) exec "$0" ;;
|
||||
esac ;;
|
||||
|
||||
system)
|
||||
# System submenu — connectivity and machine state. Snapshots and the
|
||||
# power-profile picker self-gate (BTRFS+snapper / laptop on ppd), so
|
||||
# the submenu shrinks to match the hardware, like the Waybar modules.
|
||||
bats=(/sys/class/power_supply/BAT*)
|
||||
choice=$( {
|
||||
row "Network" network-wireless
|
||||
row "Bluetooth" bluetooth
|
||||
row "Do Not Disturb" notification-disabled
|
||||
command -v btrfs-assistant >/dev/null 2>&1 && row "Snapshots" timeshift
|
||||
if [ -e "''${bats[0]}" ] && command -v powerprofilesctl >/dev/null 2>&1; then
|
||||
row "Power profile" preferences-system-power
|
||||
fi
|
||||
row "Back" go-previous
|
||||
} | rofi -dmenu -show-icons -p System) || exit 0
|
||||
case "$choice" in
|
||||
*Network*) exec "$0" network ;;
|
||||
*Bluetooth*) exec "$0" bluetooth ;;
|
||||
*"Do Not Disturb"*) exec "$0" dnd ;;
|
||||
*"Power profile"*) exec "$0" power-profile ;;
|
||||
*Power*) exec "$0" power ;;
|
||||
*Snapshots*) exec "$0" snapshot ;;
|
||||
*"Power profile"*) exec "$0" power-profile ;;
|
||||
*Back*) exec "$0" ;;
|
||||
esac ;;
|
||||
|
||||
"")
|
||||
# Root picker — grouped into category submenus so the top level stays
|
||||
# short and scannable. Apps/Theme/Power/Keybindings are frequent enough
|
||||
# to keep at the top; everything else lives under Tools or System.
|
||||
choice=$( {
|
||||
row "Apps" applications-all
|
||||
row "Theme" preferences-desktop-theme
|
||||
row "Tools" applications-utilities
|
||||
row "System" preferences-system
|
||||
row "Power" system-shutdown
|
||||
row "Keybindings" preferences-desktop-keyboard
|
||||
} | rofi -dmenu -show-icons -p Menu) || exit 0
|
||||
case "$choice" in
|
||||
*Apps*) exec rofi -show drun ;;
|
||||
*Theme*) exec "$0" theme ;;
|
||||
*Tools*) exec "$0" tools ;;
|
||||
*System*) exec "$0" system ;;
|
||||
*Power*) exec "$0" power ;;
|
||||
*Keybindings*) exec "$0" keybinds ;;
|
||||
esac ;;
|
||||
|
||||
*)
|
||||
echo "usage: nomarchy-menu [power|power-profile|theme|clipboard|calc|files|emoji|web|network|bluetooth|capture|keybinds|ask|dnd]" >&2
|
||||
echo "usage: nomarchy-menu [tools|system|power|power-profile|theme|clipboard|calc|files|emoji|web|network|bluetooth|capture|keybinds|ask|dnd|snapshot]" >&2
|
||||
exit 64 ;;
|
||||
esac
|
||||
'';
|
||||
@@ -248,6 +288,10 @@ in
|
||||
drun-display-format = "{name}";
|
||||
display-drun = "apps";
|
||||
display-run = "run";
|
||||
# Size the window to the actual number of rows instead of always
|
||||
# reserving `lines` (8) — so a 6-entry menu doesn't leave empty space,
|
||||
# while the app launcher still fills + scrolls past `lines`.
|
||||
fixed-num-lines = false;
|
||||
};
|
||||
|
||||
# Whole-swap themes bring their own rofi.rasi; otherwise the theme
|
||||
@@ -300,22 +344,34 @@ in
|
||||
columns = 1;
|
||||
dynamic = true;
|
||||
scrollbar = false;
|
||||
spacing = px 2;
|
||||
spacing = px 4; # a little air so the rounded zebra rows separate
|
||||
padding = mkLiteral "8px 0px 0px 0px";
|
||||
};
|
||||
|
||||
# Rounded, roomy rows with subtle zebra striping: alternate rows
|
||||
# lift to @surface, the selected row to @accent. normal/alternate/
|
||||
# selected are rofi's row parities (the `.normal` suffix is the
|
||||
# match-state, untouched by our menus).
|
||||
"element" = {
|
||||
padding = mkLiteral "8px 12px";
|
||||
padding = mkLiteral "10px 14px";
|
||||
border-radius = px t.ui.rounding;
|
||||
spacing = px 8;
|
||||
spacing = px 12; # gap between the (larger) icon and the label
|
||||
};
|
||||
"element selected" = {
|
||||
"element normal.normal" = {
|
||||
background-color = mkLiteral "@bg";
|
||||
text-color = mkLiteral "@fg";
|
||||
};
|
||||
"element alternate.normal" = {
|
||||
background-color = mkLiteral "@surface";
|
||||
text-color = mkLiteral "@fg";
|
||||
};
|
||||
"element selected.normal" = {
|
||||
background-color = mkLiteral "@accent";
|
||||
text-color = mkLiteral "@bg";
|
||||
};
|
||||
"element-icon" = {
|
||||
background-color = mkLiteral "transparent";
|
||||
size = mkLiteral "1em";
|
||||
size = px t.ui.iconSize; # real icons (Papirus), not 1em font glyphs
|
||||
};
|
||||
"element-text" = {
|
||||
background-color = mkLiteral "transparent";
|
||||
|
||||
@@ -35,6 +35,9 @@ in
|
||||
ignoreSpace = true;
|
||||
expireDuplicatesFirst = true;
|
||||
};
|
||||
# Curated, on by default. The rule (same as rg/fd above): short where
|
||||
# it helps, but never shadow a real binary — so the git block keeps off
|
||||
# `gs` (ghostscript), and nothing here masks a coreutil.
|
||||
shellAliases = {
|
||||
cat = "bat --paging=never";
|
||||
less = "bat";
|
||||
@@ -42,8 +45,42 @@ in
|
||||
# eza enableZshIntegration aliases ls/ll/la; add a couple extras.
|
||||
lt = "eza --tree --level=2 --icons=auto --group-directories-first";
|
||||
tree = "eza --tree --icons=auto --group-directories-first";
|
||||
|
||||
# Navigation — climb without counting dots.
|
||||
".." = "cd ..";
|
||||
"..." = "cd ../..";
|
||||
"...." = "cd ../../..";
|
||||
|
||||
# Git — the verbs you retype all day.
|
||||
g = "git";
|
||||
gst = "git status -sb";
|
||||
ga = "git add";
|
||||
gaa = "git add -A";
|
||||
gc = "git commit";
|
||||
gcm = "git commit -m";
|
||||
gco = "git checkout";
|
||||
gsw = "git switch";
|
||||
gb = "git branch";
|
||||
gd = "git diff";
|
||||
gds = "git diff --staged";
|
||||
gl = "git log --oneline --graph --decorate -20";
|
||||
glg = "git log --oneline --graph --decorate --all";
|
||||
gp = "git push";
|
||||
gpl = "git pull";
|
||||
gf = "git fetch --all --prune";
|
||||
|
||||
# Nix — the flake/store verbs (system/home rebuilds already have the
|
||||
# sys-update / home-update commands, so they're not re-aliased here).
|
||||
ns = "nix shell"; # ns nixpkgs#ripgrep
|
||||
nr = "nix run"; # nr nixpkgs#cowsay
|
||||
nfu = "nix flake update";
|
||||
nfc = "nix flake check";
|
||||
nsearch = "nix search nixpkgs";
|
||||
ngc = "nix-collect-garbage -d"; # user generations; sudo for system roots
|
||||
|
||||
# Misc.
|
||||
path = "echo $PATH | tr ':' '\\n'";
|
||||
reload = "exec zsh";
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ let
|
||||
gapsOut = 12;
|
||||
borderSize = 2;
|
||||
rounding = 10;
|
||||
iconSize = 36; # rofi launcher + menu icon size (px) for the generated theme
|
||||
activeOpacity = 1.0;
|
||||
inactiveOpacity = 0.95;
|
||||
terminalOpacity = 0.96;
|
||||
|
||||
@@ -12,12 +12,23 @@
|
||||
let
|
||||
t = config.nomarchy.theme;
|
||||
|
||||
# Show the active-keyboard-layout indicator only when more than one layout
|
||||
# is in play — multiple session layouts (comma-separated) or per-device
|
||||
# overrides (nomarchy.keyboard.devices) — so single-layout bars stay clean.
|
||||
showLanguage = lib.hasInfix "," config.nomarchy.keyboard.layout
|
||||
|| config.nomarchy.keyboard.layouts != [ ]
|
||||
|| config.nomarchy.keyboard.devices != { };
|
||||
|
||||
# Power-profile indicator (power-profiles-daemon). Both scripts self-
|
||||
# gate: they exit silently unless this is a laptop (a battery is
|
||||
# present) running PPD, so the module hides itself on desktops and
|
||||
# under the TLP backend — no system→home coupling. Speedometer glyphs
|
||||
# (slow/medium/fast) map to power-saver/balanced/performance.
|
||||
powerProfileStatus = pkgs.writeShellScript "nomarchy-powerprofile-status" ''
|
||||
#
|
||||
# Named writeShellScriptBins (put on PATH via home.packages below) rather
|
||||
# than bare writeShellScript store paths, so the whole-swap themes' static
|
||||
# waybar.jsonc can exec them by name too — same as the swaync bell.
|
||||
powerProfileStatus = pkgs.writeShellScriptBin "nomarchy-powerprofile-status" ''
|
||||
case "$(ls /sys/class/power_supply/ 2>/dev/null)" in *BAT*) ;; *) exit 0 ;; esac
|
||||
command -v powerprofilesctl >/dev/null 2>&1 || exit 0
|
||||
prof=$(powerprofilesctl get 2>/dev/null) || exit 0
|
||||
@@ -29,7 +40,7 @@ let
|
||||
printf '{"text":"%s","tooltip":"Power profile: %s","class":"%s"}\n' "$icon" "$prof" "$prof"
|
||||
'';
|
||||
# Cycle to the next profile this machine actually offers (wraps around).
|
||||
powerProfileCycle = pkgs.writeShellScript "nomarchy-powerprofile-cycle" ''
|
||||
powerProfileCycle = pkgs.writeShellScriptBin "nomarchy-powerprofile-cycle" ''
|
||||
command -v powerprofilesctl >/dev/null 2>&1 || exit 0
|
||||
cur=$(powerprofilesctl get 2>/dev/null) || exit 0
|
||||
avail=$(powerprofilesctl list 2>/dev/null | sed -nE 's/^[* ] ([a-z-]+):$/\1/p')
|
||||
@@ -66,7 +77,9 @@ let
|
||||
|
||||
modules-left = [ "hyprland/workspaces" "hyprland/window" ];
|
||||
modules-center = [ "clock" ];
|
||||
modules-right = [ "tray" "pulseaudio" "network" "cpu" "memory" "custom/powerprofile" "battery" "custom/notification" ];
|
||||
modules-right = [ "tray" "pulseaudio" "network" "cpu" "memory" "custom/powerprofile" ]
|
||||
++ lib.optional showLanguage "hyprland/language"
|
||||
++ [ "battery" "custom/notification" ];
|
||||
|
||||
"hyprland/workspaces" = {
|
||||
format = "{icon}";
|
||||
@@ -84,6 +97,13 @@ let
|
||||
tooltip-format = "<tt><small>{calendar}</small></tt>";
|
||||
};
|
||||
|
||||
# Active keyboard layout (per focused device) — only placed in
|
||||
# modules-right when showLanguage (see above).
|
||||
"hyprland/language" = {
|
||||
format = " {short}";
|
||||
tooltip = false;
|
||||
};
|
||||
|
||||
pulseaudio = {
|
||||
format = "{icon} {volume}%";
|
||||
format-muted = "";
|
||||
@@ -112,10 +132,10 @@ let
|
||||
};
|
||||
|
||||
"custom/powerprofile" = {
|
||||
exec = "${powerProfileStatus}";
|
||||
exec = "nomarchy-powerprofile-status";
|
||||
return-type = "json";
|
||||
interval = 5;
|
||||
on-click = "${powerProfileCycle}";
|
||||
on-click = "nomarchy-powerprofile-cycle";
|
||||
};
|
||||
|
||||
# swaync notification bell + Do-Not-Disturb state. `-swb` streams JSON
|
||||
@@ -189,7 +209,7 @@ let
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#tray, #pulseaudio, #network, #cpu, #memory, #custom-powerprofile, #battery, #custom-notification {
|
||||
#tray, #pulseaudio, #network, #cpu, #memory, #custom-powerprofile, #language, #battery, #custom-notification {
|
||||
color: @subtext;
|
||||
padding: 0 10px;
|
||||
}
|
||||
@@ -227,4 +247,9 @@ in
|
||||
else generatedStyle
|
||||
);
|
||||
};
|
||||
|
||||
# The power-profile helpers on PATH, so both the generated bar and the
|
||||
# whole-swap themes' static waybar.jsonc can exec them by name.
|
||||
home.packages = lib.optionals config.nomarchy.waybar.enable
|
||||
[ powerProfileStatus powerProfileCycle ];
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ let
|
||||
'';
|
||||
in
|
||||
{
|
||||
imports = [ ./options.nix ./plymouth.nix ./file-manager.nix ./power.nix ];
|
||||
imports = [ ./options.nix ./plymouth.nix ./file-manager.nix ./power.nix ./services.nix ];
|
||||
|
||||
config = {
|
||||
# The safe half of distro branding: distroName flows into
|
||||
@@ -185,7 +185,7 @@ in
|
||||
# is a clean no-op rather than a failing timer.
|
||||
services.snapper.configs = lib.mkIf
|
||||
(cfg.snapper.enable && (config.fileSystems."/".fsType or "") == "btrfs")
|
||||
{
|
||||
({
|
||||
root = {
|
||||
SUBVOLUME = "/";
|
||||
TIMELINE_CREATE = true;
|
||||
@@ -196,6 +196,39 @@ in
|
||||
TIMELINE_LIMIT_MONTHLY = "0";
|
||||
TIMELINE_LIMIT_YEARLY = "0";
|
||||
};
|
||||
}
|
||||
# /home is user data — snapshot it too when it's its own BTRFS subvolume
|
||||
# (the installer's @home). Keep the recent hourlies for oops-recovery
|
||||
# plus dailies, and a month of weeklies; "at least daily" by default.
|
||||
// lib.optionalAttrs ((config.fileSystems."/home".fsType or "") == "btrfs") {
|
||||
home = {
|
||||
SUBVOLUME = "/home";
|
||||
TIMELINE_CREATE = true;
|
||||
TIMELINE_CLEANUP = true;
|
||||
TIMELINE_LIMIT_HOURLY = "5";
|
||||
TIMELINE_LIMIT_DAILY = "7";
|
||||
TIMELINE_LIMIT_WEEKLY = "4";
|
||||
TIMELINE_LIMIT_MONTHLY = "0";
|
||||
TIMELINE_LIMIT_YEARLY = "0";
|
||||
};
|
||||
});
|
||||
|
||||
# snapper needs a `.snapshots` subvolume inside each tracked subvolume.
|
||||
# The installer made one for / (@snapshots → /.snapshots) but not /home,
|
||||
# so create a nested one there if missing (idempotent via the condition;
|
||||
# excluded from /home snapshots since BTRFS snapshots are non-recursive).
|
||||
# Runs at boot so it also fixes already-installed machines — no disko
|
||||
# change needed.
|
||||
systemd.services.nomarchy-home-snapshots-subvol = lib.mkIf
|
||||
(cfg.snapper.enable && (config.fileSystems."/home".fsType or "") == "btrfs") {
|
||||
description = "Create /home/.snapshots for snapper if missing";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
before = [ "snapper-timeline.service" "snapper-cleanup.service" ];
|
||||
unitConfig.ConditionPathExists = "!/home/.snapshots";
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
ExecStart = "${pkgs.btrfs-progs}/bin/btrfs subvolume create /home/.snapshots";
|
||||
};
|
||||
};
|
||||
|
||||
# ── Fonts ────────────────────────────────────────────────────────
|
||||
@@ -285,7 +318,10 @@ in
|
||||
--cleanup-algorithm number
|
||||
echo "Rebuilding..."
|
||||
nixos-rebuild switch --flake /etc/nixos#default "$@"
|
||||
'');
|
||||
'')
|
||||
# The desktop snapshot manager (browse / diff / restore / rollback over
|
||||
# snapper, elevating via polkit) — what `nomarchy-menu snapshot` launches.
|
||||
++ lib.optional cfg.snapper.enable pkgs.btrfs-assistant;
|
||||
|
||||
# Don't let boot entries fill the ESP over the years.
|
||||
boot.loader.systemd-boot.configurationLimit = lib.mkDefault 10;
|
||||
|
||||
280
modules/nixos/services.nix
Normal file
280
modules/nixos/services.nix
Normal file
@@ -0,0 +1,280 @@
|
||||
# Opt-in services & integrations — off by default, one toggle each, enabled
|
||||
# in system.nix (see the commented examples in the downstream template).
|
||||
# These are system-side: their own `nomarchy.services.*` namespace, distinct
|
||||
# from the distro's core `nomarchy.system.*` toggles. `username` (from
|
||||
# specialArgs) is read lazily, only where a service needs it.
|
||||
{ config, lib, pkgs, ... }@args:
|
||||
|
||||
let
|
||||
cfg = config.nomarchy.services;
|
||||
in
|
||||
{
|
||||
options.nomarchy.services = {
|
||||
tailscale.enable = lib.mkEnableOption ''
|
||||
Tailscale, the mesh VPN — ships the daemon; authenticate once with
|
||||
`sudo tailscale up`'';
|
||||
|
||||
syncthing.enable = lib.mkEnableOption ''
|
||||
Syncthing continuous file sync, running as the login user — add
|
||||
folders and devices in the web UI at http://127.0.0.1:8384'';
|
||||
|
||||
podman.enable = lib.mkEnableOption ''
|
||||
Podman for rootless containers, with `docker` aliased to it and DNS on
|
||||
the default network; the login user gets a subuid/subgid range'';
|
||||
|
||||
flatpak.enable = lib.mkEnableOption ''
|
||||
Flatpak, with the Flathub remote added system-wide — install apps with
|
||||
`flatpak install flathub <app>`'';
|
||||
|
||||
pika.enable = lib.mkEnableOption ''
|
||||
Pika Backup, a friendly GUI for scheduled Borg backups (the app;
|
||||
configure repositories and schedules inside it)'';
|
||||
|
||||
steam.enable = lib.mkEnableOption ''
|
||||
Steam (Valve's game store/launcher) via programs.steam — which wires up
|
||||
the 32-bit graphics libraries, controller udev rules and Remote-Play
|
||||
firewall ports a bare package can't. Unfree'';
|
||||
|
||||
libvirt.enable = lib.mkEnableOption ''
|
||||
libvirt/KVM virtualisation with the virt-manager GUI — runs the
|
||||
libvirtd daemon and adds the login user to the `libvirtd` group, so VMs
|
||||
can be managed without root'';
|
||||
|
||||
obs.enable = lib.mkEnableOption ''
|
||||
OBS Studio for screen recording and streaming, plus a v4l2loopback
|
||||
virtual camera — so an OBS scene can be selected as a webcam in Zoom,
|
||||
Teams or a browser (screen capture itself already works over the
|
||||
desktop's PipeWire portal)'';
|
||||
|
||||
docker.enable = lib.mkEnableOption ''
|
||||
Docker (rootful) with the login user in the `docker` group. Mutually
|
||||
exclusive with the podman toggle's docker-compat (both provide the
|
||||
`docker` command) — enable only one'';
|
||||
|
||||
kdeconnect.enable = lib.mkEnableOption ''
|
||||
KDE Connect for phone integration — shared clipboard, notifications and
|
||||
file transfer with a paired phone; opens the firewall ports it needs'';
|
||||
|
||||
gamemode.enable = lib.mkEnableOption ''
|
||||
Feral GameMode — a daemon games ask to apply a performance CPU governor
|
||||
and IO/nice priorities while running (via `gamemoderun`)'';
|
||||
|
||||
adb.enable = lib.mkEnableOption ''
|
||||
the Android platform tools (adb/fastboot) for flashing/debugging phones.
|
||||
systemd ≥258 applies the device uaccess udev rules automatically, so the
|
||||
tools alone are enough — no group dance'';
|
||||
|
||||
wireshark.enable = lib.mkEnableOption ''
|
||||
Wireshark (the Qt GUI) for packet capture, with the login user in the
|
||||
`wireshark` group so capture works without root'';
|
||||
|
||||
ollama.enable = lib.mkEnableOption ''
|
||||
Ollama, a local LLM runtime serving an API on 127.0.0.1:11434 (pair it
|
||||
with the lmstudio/alpaca GUIs). CPU by default — set
|
||||
`services.ollama.acceleration` natively for GPU offload'';
|
||||
|
||||
printing.enable = lib.mkEnableOption ''
|
||||
CUPS printing with Avahi/mDNS, so network printers are auto-discovered
|
||||
(add vendor drivers via `services.printing.drivers`)'';
|
||||
|
||||
openrgb.enable = lib.mkEnableOption ''
|
||||
the OpenRGB daemon and GUI for controlling RGB lighting on peripherals
|
||||
and (where supported) motherboards — installs the udev rules for device
|
||||
access. Some motherboard controllers also need `hardware.i2c.enable`'';
|
||||
|
||||
restic = {
|
||||
enable = lib.mkEnableOption ''
|
||||
a scheduled (daily) restic backup — headless, complementing the Pika
|
||||
GUI. Backs up `paths` with sane retention (7 daily / 4 weekly /
|
||||
6 monthly); needs a `repository` and a `passwordFile` (below). List
|
||||
and restore with the generated `restic-nomarchy` wrapper (repo +
|
||||
password baked in), e.g. `restic-nomarchy snapshots`'';
|
||||
|
||||
repository = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "";
|
||||
example = "/mnt/backup/restic";
|
||||
description = ''
|
||||
The restic repository as a path or URL — e.g. `/mnt/backup/restic`,
|
||||
`sftp:user@host:/srv/restic`, `b2:bucket:/path`. Required when
|
||||
`restic.enable` is set.
|
||||
'';
|
||||
};
|
||||
|
||||
passwordFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
default = null;
|
||||
example = "/etc/nomarchy/restic-password";
|
||||
description = ''
|
||||
Path to a file holding the repository password. Give an absolute
|
||||
path that lives ON THE MACHINE — not a `./file` in the flake, which
|
||||
would copy the secret into the world-readable Nix store. Required
|
||||
when `restic.enable` is set.
|
||||
'';
|
||||
};
|
||||
|
||||
paths = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
default = [ "/home" ];
|
||||
description = "Paths to include in the backup.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkMerge [
|
||||
(lib.mkIf cfg.tailscale.enable {
|
||||
services.tailscale.enable = true;
|
||||
})
|
||||
|
||||
(lib.mkIf cfg.syncthing.enable {
|
||||
services.syncthing = {
|
||||
enable = true;
|
||||
user = args.username;
|
||||
# Manage folders/devices in the GUI; don't let the (empty) declarative
|
||||
# config wipe them on a rebuild.
|
||||
overrideDevices = false;
|
||||
overrideFolders = false;
|
||||
guiAddress = "127.0.0.1:8384";
|
||||
};
|
||||
})
|
||||
|
||||
(lib.mkIf cfg.podman.enable {
|
||||
virtualisation.podman = {
|
||||
enable = true;
|
||||
dockerCompat = lib.mkDefault true; # `docker` → podman (off if you run real docker)
|
||||
defaultNetwork.settings.dns_enabled = true; # DNS for rootless containers
|
||||
};
|
||||
# Rootless podman needs a subuid/subgid range for the user.
|
||||
users.users.${args.username}.autoSubUidGidRange = lib.mkDefault true;
|
||||
})
|
||||
|
||||
(lib.mkIf cfg.flatpak.enable {
|
||||
services.flatpak.enable = true;
|
||||
# Flatpak ships no remotes; add Flathub system-wide. Idempotent
|
||||
# (--if-not-exists), and it reads the .flatpakrepo over the network, so
|
||||
# it waits for connectivity and simply retries next boot if offline.
|
||||
systemd.services.nomarchy-flathub = {
|
||||
description = "Add the Flathub Flatpak remote";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
path = [ pkgs.flatpak ];
|
||||
serviceConfig.Type = "oneshot";
|
||||
script = ''
|
||||
flatpak remote-add --system --if-not-exists flathub \
|
||||
https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
'';
|
||||
};
|
||||
})
|
||||
|
||||
(lib.mkIf cfg.pika.enable {
|
||||
environment.systemPackages = [ pkgs.pika-backup ];
|
||||
})
|
||||
|
||||
(lib.mkIf cfg.steam.enable {
|
||||
# The Steam package alone misses the system bits a game needs: the
|
||||
# 32-bit graphics stack, controller udev rules, Remote-Play ports.
|
||||
programs.steam.enable = true;
|
||||
})
|
||||
|
||||
(lib.mkIf cfg.libvirt.enable {
|
||||
virtualisation.libvirtd.enable = true;
|
||||
programs.virt-manager.enable = true;
|
||||
# Group membership lets the user reach the system libvirtd socket
|
||||
# (manage VMs) without sudo. Merges with the user's other extraGroups.
|
||||
users.users.${args.username}.extraGroups = [ "libvirtd" ];
|
||||
})
|
||||
|
||||
(lib.mkIf cfg.obs.enable {
|
||||
environment.systemPackages = [ pkgs.obs-studio ];
|
||||
# Virtual camera: OBS streams a scene into a v4l2 loopback device that
|
||||
# Zoom/Teams/browsers then pick up as a webcam. exclusive_caps=1 is the
|
||||
# bit that makes those apps actually list the device.
|
||||
boot.extraModulePackages = [ config.boot.kernelPackages.v4l2loopback ];
|
||||
boot.kernelModules = [ "v4l2loopback" ];
|
||||
boot.extraModprobeConfig = ''
|
||||
options v4l2loopback devices=1 video_nr=10 card_label="OBS Virtual Camera" exclusive_caps=1
|
||||
'';
|
||||
})
|
||||
|
||||
(lib.mkIf cfg.docker.enable {
|
||||
virtualisation.docker.enable = true;
|
||||
users.users.${args.username}.extraGroups = [ "docker" ];
|
||||
# Podman's docker-compat and real Docker both claim the `docker`
|
||||
# command — guard against enabling both (unless you turn that off).
|
||||
assertions = [{
|
||||
assertion = !(cfg.podman.enable && config.virtualisation.podman.dockerCompat);
|
||||
message = ''
|
||||
nomarchy.services: docker and podman both provide the `docker`
|
||||
command. Enable only one, or set
|
||||
`virtualisation.podman.dockerCompat = false`.
|
||||
'';
|
||||
}];
|
||||
})
|
||||
|
||||
(lib.mkIf cfg.kdeconnect.enable {
|
||||
programs.kdeconnect.enable = true; # installs the app and opens its ports
|
||||
})
|
||||
|
||||
(lib.mkIf cfg.gamemode.enable {
|
||||
programs.gamemode.enable = true;
|
||||
})
|
||||
|
||||
(lib.mkIf cfg.adb.enable {
|
||||
# `programs.adb` was removed — systemd ≥258 handles the device uaccess
|
||||
# udev rules, so the tools package is all that's needed now.
|
||||
environment.systemPackages = [ pkgs.android-tools ];
|
||||
})
|
||||
|
||||
(lib.mkIf cfg.wireshark.enable {
|
||||
programs.wireshark = {
|
||||
enable = true;
|
||||
package = pkgs.wireshark; # the Qt GUI (the module defaults to the CLI)
|
||||
};
|
||||
users.users.${args.username}.extraGroups = [ "wireshark" ];
|
||||
})
|
||||
|
||||
(lib.mkIf cfg.ollama.enable {
|
||||
services.ollama.enable = true;
|
||||
})
|
||||
|
||||
(lib.mkIf cfg.printing.enable {
|
||||
services.printing.enable = true;
|
||||
# mDNS so network printers show up without manual setup.
|
||||
services.avahi = {
|
||||
enable = true;
|
||||
nssmdns4 = true;
|
||||
openFirewall = true;
|
||||
};
|
||||
})
|
||||
|
||||
(lib.mkIf cfg.openrgb.enable {
|
||||
services.hardware.openrgb.enable = true;
|
||||
})
|
||||
|
||||
(lib.mkIf cfg.restic.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = cfg.restic.repository != "";
|
||||
message = "nomarchy.services.restic.repository must be set when restic is enabled.";
|
||||
}
|
||||
{
|
||||
assertion = cfg.restic.passwordFile != null;
|
||||
message = "nomarchy.services.restic.passwordFile must be set when restic is enabled.";
|
||||
}
|
||||
];
|
||||
services.restic.backups.nomarchy = {
|
||||
repository = cfg.restic.repository;
|
||||
passwordFile = cfg.restic.passwordFile;
|
||||
paths = cfg.restic.paths;
|
||||
initialize = true; # create the repo on first run if it doesn't exist
|
||||
extraBackupArgs = [ "--exclude-caches" ];
|
||||
pruneOpts = [ "--keep-daily 7" "--keep-weekly 4" "--keep-monthly 6" ];
|
||||
timerConfig = {
|
||||
OnCalendar = "daily";
|
||||
Persistent = true; # catch up a missed run after downtime
|
||||
};
|
||||
};
|
||||
})
|
||||
];
|
||||
}
|
||||
@@ -5,15 +5,136 @@
|
||||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
# Examples:
|
||||
# ── Overrides (the desktop is on by default; tweak or opt out) ──────
|
||||
# nomarchy.terminal = "kitty"; # swap the default terminal
|
||||
# nomarchy.waybar.enable = false; # bring your own bar
|
||||
# nomarchy.stylix.enable = false; # opt out of GTK/Qt theming
|
||||
# nomarchy.keyboard.layout = "de"; # session keyboard (pair with
|
||||
# # xkb + console.useXkbConfig in
|
||||
# # system.nix for tty/LUKS)
|
||||
# nomarchy.keyboard.layout = "de"; # session keyboard (set the matching
|
||||
# # services.xserver.xkb.layout in
|
||||
# # system.nix too; the console + LUKS
|
||||
# # prompt follow it automatically)
|
||||
|
||||
# ── Opt-in features — uncomment and tweak to enable ─────────────────
|
||||
# nomarchy.nightlight = { # scheduled blue-light filter (hyprsunset)
|
||||
# enable = true;
|
||||
# temperature = 4000; # night warmth in K — lower is warmer
|
||||
# sunset = "20:00";
|
||||
# sunrise = "07:00";
|
||||
# };
|
||||
#
|
||||
# nomarchy.monitors = [ # declarative per-output layout, hotplug-applied
|
||||
# { name = "eDP-1"; position = "0x0"; }
|
||||
# { name = "HDMI-A-1"; position = "auto-right"; }
|
||||
# ]; # names from `hyprctl devices`
|
||||
#
|
||||
# nomarchy.keyboard.devices = { # a specific keyboard's own layout
|
||||
# "keychron-keychron-k2" = { layout = "de"; };
|
||||
# };
|
||||
#
|
||||
# nomarchy.keyboard.layouts = [ "de" "fr" ]; # rofi-prompt for a layout when a
|
||||
# # new keyboard connects + remember it
|
||||
|
||||
# ── Application suite ───────────────────────────────────────────────
|
||||
# A starter complete-workstation set, installed for your user — yours to
|
||||
# curate. Delete a line to slim the machine; uncomment a suggestion (or
|
||||
# add your own) to extend it. The desktop itself — terminal, editor
|
||||
# keybinds, theming — comes from the modules; these are the heavier
|
||||
# standalone apps the distro doesn't impose by default.
|
||||
#
|
||||
# Apps that need system setup beyond a package (Steam's 32-bit stack,
|
||||
# a daemon, group membership, a kernel module) are NOT here — they're
|
||||
# opt-in service toggles in system.nix instead (nomarchy.services.* —
|
||||
# steam, libvirt, obs, docker, gamemode, kdeconnect, adb, wireshark,
|
||||
# ollama, printing).
|
||||
home.packages = with pkgs; [
|
||||
libreoffice-fresh # office suite (documents, sheets, slides)
|
||||
vscode # code editor (unfree; allowUnfree is on)
|
||||
gimp # raster image editor
|
||||
inkscape # vector graphics
|
||||
mpv # media player
|
||||
amberol # music player (local library; streaming → spotify below)
|
||||
|
||||
# ── More apps — uncomment to add ─────────────────────────────────
|
||||
# Web browsers
|
||||
# firefox
|
||||
# chromium
|
||||
# ungoogled-chromium
|
||||
# brave # (unfree)
|
||||
# google-chrome # (unfree)
|
||||
|
||||
# Communication
|
||||
# signal-desktop
|
||||
# telegram-desktop
|
||||
# element-desktop # Matrix
|
||||
# vesktop # Discord (themeable client)
|
||||
# slack # (unfree)
|
||||
# zoom-us # (unfree)
|
||||
# teams-for-linux # community client (no official Linux app)
|
||||
# thunderbird # email
|
||||
|
||||
# Office & documents
|
||||
# onlyoffice-desktopeditors # stronger MS-Office fidelity
|
||||
# kdePackages.okular # PDF viewer
|
||||
# xournalpp # PDF annotation
|
||||
# pdfarranger
|
||||
# calibre # ebook library
|
||||
# texliveFull # full LaTeX toolchain — multi-GB (texliveMedium is lighter)
|
||||
|
||||
# Notes / research
|
||||
# logseq
|
||||
# obsidian # (unfree)
|
||||
# joplin-desktop
|
||||
# anki-bin # flashcards
|
||||
# zotero # reference manager
|
||||
|
||||
# Code & data
|
||||
# zed-editor
|
||||
# lazygit
|
||||
# dbeaver-bin # database GUI
|
||||
# bruno # API client
|
||||
# distrobox # containerized dev shells
|
||||
|
||||
# Graphics & design
|
||||
# krita # digital painting
|
||||
# blender # 3D
|
||||
# darktable # RAW photo
|
||||
|
||||
# Audio / video
|
||||
# audacity
|
||||
# shotcut # video editor
|
||||
# kdePackages.kdenlive # video editor (pulls in KDE libs)
|
||||
# handbrake # transcoder
|
||||
|
||||
# Media
|
||||
# vlc
|
||||
# spotify # (unfree)
|
||||
# newsflash # RSS reader
|
||||
|
||||
# Gaming (Steam + GameMode are service toggles in system.nix)
|
||||
# lutris
|
||||
# heroic # Epic/GOG launcher
|
||||
# bottles # Wine prefixes
|
||||
# prismlauncher # Minecraft
|
||||
# protonup-qt # Proton-GE manager
|
||||
# mangohud # in-game overlay
|
||||
|
||||
# Files, sync & torrents
|
||||
# nextcloud-client
|
||||
# localsend # AirDrop-like transfer
|
||||
# qbittorrent
|
||||
|
||||
# Security
|
||||
# keepassxc
|
||||
# bitwarden-desktop
|
||||
|
||||
# System
|
||||
# gnome-disk-utility
|
||||
# gparted
|
||||
# baobab # disk usage
|
||||
# wireguard-tools # WireGuard CLI (NetworkManager imports .conf tunnels natively)
|
||||
|
||||
# Local AI (Ollama is a service toggle in system.nix)
|
||||
# lmstudio # (unfree; large closure)
|
||||
# alpaca # GTK Ollama UI
|
||||
];
|
||||
}
|
||||
|
||||
@@ -18,9 +18,41 @@
|
||||
extraGroups = [ "wheel" "networkmanager" "video" "input" ];
|
||||
};
|
||||
|
||||
# Examples:
|
||||
# ── Overrides (uncomment to change) ─────────────────────────────────
|
||||
# nomarchy.system.greeter.enable = false; # bring your own login manager
|
||||
# environment.systemPackages = [ pkgs.htop ];
|
||||
|
||||
# ── Opt-in features — uncomment and tweak to enable ─────────────────
|
||||
# nomarchy.system.power = { # active power management (laptops)
|
||||
# enable = true;
|
||||
# backend = "ppd"; # "ppd" (default) or "tlp"
|
||||
# laptop = true; # required by batteryChargeLimit
|
||||
# batteryChargeLimit = 80; # cap charging for longevity
|
||||
# thermal.enable = true; # thermald (Intel CPUs)
|
||||
# };
|
||||
#
|
||||
# nomarchy.services.tailscale.enable = true; # mesh VPN — then `sudo tailscale up`
|
||||
# nomarchy.services.syncthing.enable = true; # file sync — GUI at http://127.0.0.1:8384
|
||||
# nomarchy.services.podman.enable = true; # rootless containers (docker → podman)
|
||||
# nomarchy.services.flatpak.enable = true; # Flatpak + the Flathub remote
|
||||
# nomarchy.services.pika.enable = true; # Pika Backup (GUI Borg backups)
|
||||
# nomarchy.services.steam.enable = true; # Steam (32-bit libs, controllers, ports)
|
||||
# nomarchy.services.libvirt.enable = true; # libvirt/KVM + virt-manager GUI
|
||||
# nomarchy.services.obs.enable = true; # OBS Studio + v4l2loopback virtual camera
|
||||
# nomarchy.services.docker.enable = true; # Docker rootful (not alongside podman)
|
||||
# nomarchy.services.kdeconnect.enable = true;# KDE Connect phone integration (opens ports)
|
||||
# nomarchy.services.gamemode.enable = true; # Feral GameMode performance daemon
|
||||
# nomarchy.services.adb.enable = true; # Android adb/fastboot tools
|
||||
# nomarchy.services.wireshark.enable = true; # Wireshark GUI (wireshark group, no root)
|
||||
# nomarchy.services.ollama.enable = true; # local LLM runtime (127.0.0.1:11434)
|
||||
# nomarchy.services.printing.enable = true; # CUPS + Avahi network printer discovery
|
||||
# nomarchy.services.openrgb.enable = true; # RGB peripheral/motherboard lighting daemon
|
||||
# nomarchy.services.restic = { # scheduled (daily) restic backup
|
||||
# enable = true;
|
||||
# repository = "/mnt/backup/restic"; # path or URL (sftp:/b2:/…)
|
||||
# passwordFile = "/etc/nomarchy/restic-password"; # absolute path, NOT in the flake
|
||||
# paths = [ "/home" ];
|
||||
# };
|
||||
|
||||
system.stateVersion = "26.05";
|
||||
}
|
||||
|
||||
@@ -29,7 +29,8 @@ window {
|
||||
border-color: @yellow;
|
||||
border-radius: 15px;
|
||||
width: 40%;
|
||||
height: 50%;
|
||||
/* No fixed height — size to content (fixed-num-lines:false) so every
|
||||
* entry shows without scrolling and there's no empty space below. */
|
||||
location: center;
|
||||
anchor: center;
|
||||
x-offset: 0;
|
||||
@@ -90,6 +91,12 @@ element {
|
||||
spacing: 15px;
|
||||
}
|
||||
|
||||
/* Zebra striping: every other row lifts with a faint light wash over the
|
||||
* dark window (bg0 at ~12% alpha). The selected row below still wins. */
|
||||
element alternate.normal {
|
||||
background-color: #fdf6e31f;
|
||||
}
|
||||
|
||||
element-icon {
|
||||
background-color: transparent;
|
||||
text-color: inherit;
|
||||
|
||||
@@ -48,6 +48,7 @@ window#waybar {
|
||||
#workspaces,
|
||||
#pulseaudio,
|
||||
#network,
|
||||
#custom-powerprofile,
|
||||
#battery,
|
||||
#tray,
|
||||
#custom-notification,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
"modules-left": ["custom/launcher", "clock", "clock#date"],
|
||||
"modules-center": ["hyprland/workspaces"],
|
||||
"modules-right": ["pulseaudio", "network", "battery", "tray", "custom/notification", "custom/powermenu"],
|
||||
"modules-right": ["pulseaudio", "network", "custom/powerprofile", "battery", "tray", "custom/notification", "custom/powermenu"],
|
||||
|
||||
"hyprland/workspaces": {
|
||||
"disable-scroll": true,
|
||||
@@ -80,6 +80,13 @@
|
||||
|
||||
"tray": { "spacing": 8 },
|
||||
|
||||
"custom/powerprofile": {
|
||||
"return-type": "json",
|
||||
"interval": 5,
|
||||
"exec": "nomarchy-powerprofile-status",
|
||||
"on-click": "nomarchy-powerprofile-cycle"
|
||||
},
|
||||
|
||||
"custom/notification": {
|
||||
"format": "{icon}",
|
||||
"return-type": "json",
|
||||
|
||||
@@ -29,7 +29,8 @@ window {
|
||||
border-color: @yellow;
|
||||
border-radius: 15px;
|
||||
width: 40%;
|
||||
height: 50%;
|
||||
/* No fixed height — size to content (fixed-num-lines:false) so every
|
||||
* entry shows without scrolling and there's no empty space below. */
|
||||
location: center;
|
||||
anchor: center;
|
||||
x-offset: 0;
|
||||
@@ -90,6 +91,12 @@ element {
|
||||
spacing: 15px;
|
||||
}
|
||||
|
||||
/* Zebra striping: every other row lifts with a faint dark wash over the
|
||||
* cream window (bg0 at ~12% alpha). The selected row below still wins. */
|
||||
element alternate.normal {
|
||||
background-color: #2d353b1f;
|
||||
}
|
||||
|
||||
element-icon {
|
||||
background-color: transparent;
|
||||
text-color: inherit;
|
||||
|
||||
@@ -54,6 +54,7 @@ window#waybar {
|
||||
#workspaces,
|
||||
#pulseaudio,
|
||||
#network,
|
||||
#custom-powerprofile,
|
||||
#idle_inhibitor,
|
||||
#battery,
|
||||
#custom-notification,
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
"modules-left": ["custom/nomarchy", "clock", "clock#date"],
|
||||
"modules-center": ["hyprland/workspaces"],
|
||||
"modules-right": ["idle_inhibitor", "pulseaudio", "network", "battery", "tray", "custom/notification", "custom/powermenu"],
|
||||
"modules-right": ["idle_inhibitor", "pulseaudio", "network", "custom/powerprofile", "battery", "tray", "custom/notification", "custom/powermenu"],
|
||||
|
||||
"hyprland/workspaces": {
|
||||
"disable-scroll": true,
|
||||
@@ -87,6 +87,13 @@
|
||||
|
||||
"tray": { "spacing": 8 },
|
||||
|
||||
"custom/powerprofile": {
|
||||
"return-type": "json",
|
||||
"interval": 5,
|
||||
"exec": "nomarchy-powerprofile-status",
|
||||
"on-click": "nomarchy-powerprofile-cycle"
|
||||
},
|
||||
|
||||
"custom/notification": {
|
||||
"format": "{icon}",
|
||||
"return-type": "json",
|
||||
|
||||
Reference in New Issue
Block a user