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.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.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.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.hyprland.enable` | `true` | Nomarchy's Hyprland config |
|
||||||
| `nomarchy.waybar.enable` | `true` | Nomarchy's Waybar |
|
| `nomarchy.waybar.enable` | `true` | Nomarchy's Waybar |
|
||||||
| `nomarchy.rofi.enable` | `true` | Themed rofi launcher + `nomarchy-menu` dispatcher |
|
| `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.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.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.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
|
Beyond the `nomarchy.*` surface, the system layer turns on the usual
|
||||||
desktop services with `lib.mkDefault` (override natively). One worth
|
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`
|
clipboard history · `SUPER+Q`
|
||||||
close · `SUPER+1..9` workspaces · `Print` region screenshot.
|
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
|
## 6. Extending
|
||||||
|
|
||||||
- **New theme:** drop a JSON into `themes/` (schema = any existing preset),
|
- **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.
|
the Nix modules. One place — there is no second renderer to keep in sync.
|
||||||
- **Importing more old-distro palettes:**
|
- **Importing more old-distro palettes:**
|
||||||
`tools/import-palettes.py <palettes-dir> themes/`.
|
`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
|
## 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
|
launcher, no second theming pipeline; the dispatcher owns the menu
|
||||||
structure, so the renderer stays swappable (we moved fuzzel → rofi 2.0
|
structure, so the renderer stays swappable (we moved fuzzel → rofi 2.0
|
||||||
once mainline gained native Wayland, for its richer theming)
|
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
|
- ✓ **Menu modules from rofi plugins:** the old `calc` flow committed the
|
||||||
expression blind (result only in the *next* menu's `-mesg`) and `qalc -t`
|
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-
|
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
|
summer-day/night menu buttons now use its `U+F000` glyph with
|
||||||
`font-family: Nomarchy` pinned in their CSS (Nerd Fonts also occupy
|
`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.
|
U+F000, so the pin is required). The other themes have no logo button.
|
||||||
- **Quality-of-life command aliases:** assemble a curated collection of
|
- ✓ **Quality-of-life command aliases:** a curated set ships on by default in
|
||||||
shell aliases/abbreviations for common operations (git, nix, navigation,
|
`modules/home/shell.nix` — navigation (`..`/`...`/`....`), a git block
|
||||||
the nomarchy helpers, …), themed into the zsh shell experience
|
(`g`, `gst`, `ga`/`gaa`, `gc`/`gcm`, `gco`/`gsw`, `gb`, `gd`/`gds`,
|
||||||
(`modules/home/shell.nix`). Decide scope and which to ship on by default.
|
`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
|
- **Theme-switch feedback:** ✓ the "rebuilding…" notification is now
|
||||||
persistent (timeout 0) and replaced in place by "applied ✓" / failure
|
persistent (timeout 0) and replaced in place by "applied ✓" / failure
|
||||||
via a synchronous tag, so a multi-minute switch never reads as a failed
|
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`.
|
tracks the palette), `ls`→eza, `cd`→zoxide, plus `lt`/`tree`.
|
||||||
`nomarchy.shell.enable`. (Deliberately did NOT alias grep→rg / find→fd:
|
`nomarchy.shell.enable`. (Deliberately did NOT alias grep→rg / find→fd:
|
||||||
their flags differ enough to surprise; `rg`/`fd` ship as themselves.)
|
their flags differ enough to surprise; `rg`/`fd` ship as themselves.)
|
||||||
- **Default application suite:** install a complete-workstation set
|
- ✓ **Default application suite:** a starter complete-workstation set —
|
||||||
(vscode, libreoffice, gimp, inkscape, texlive-full, …) behind a
|
`libreoffice-fresh`, `vscode`, `gimp`, `inkscape` — ships *active* in the
|
||||||
`nomarchy.apps.*` option surface so each is individually opt-out for
|
downstream `templates/downstream/home.nix` `home.packages`, with the heavier
|
||||||
users who want a leaner machine. Watch closure size (texlive-full is
|
opt-ins (`texliveFull` — multi-GB; `texliveMedium` is lighter — plus
|
||||||
multi-GB — likely default-off or a lighter scheme).
|
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
|
- ✓ **Plymouth logo contrast:** the shipped art was a fixed navy that
|
||||||
vanished on dark bases. `modules/nixos/plymouth.nix` now recolors every
|
vanished on dark bases. `modules/nixos/plymouth.nix` now recolors every
|
||||||
element from the palette at build time (flat fill, alpha kept):
|
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
|
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`
|
stale legacy-merge), and deleted the dead `refactor` / `wave/nixos-26.05`
|
||||||
branches (kept `legacy` for history). See the comment in `flake.nix`.
|
branches (kept `legacy` for history). See the comment in `flake.nix`.
|
||||||
- Remaining (optional): re-check that every file still earns its place
|
- ✓ re-checked that every file earns its place under the
|
||||||
under the `modules`/`hosts`/`themes`/`pkgs`/`tools` rule of thumb.
|
`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,
|
- **Full docs review & restructure:** a dedicated pass over all the prose,
|
||||||
not just the code-adjacent cleanup the repo-sanitize item covers. The
|
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
|
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.
|
idle behaviour.
|
||||||
- Remaining: a boot-only→event-driven charge-limit re-apply (udev) if a
|
- Remaining: a boot-only→event-driven charge-limit re-apply (udev) if a
|
||||||
firmware resets the threshold on unplug.
|
firmware resets the threshold on unplug.
|
||||||
- Remaining: the `custom/powerprofile` Waybar indicator only lands in the
|
- ✓ **Waybar parity:** the `custom/powerprofile` indicator now shows in the
|
||||||
generated config — the summer-day/night whole-swap `waybar.jsonc` don't
|
summer-day/night whole-swap themes too. `powerProfileStatus`/`Cycle` are
|
||||||
show it (the module's exec points at Nix-built script store paths, which
|
named `writeShellScriptBin`s on PATH (`waybar.nix` home.packages), so the
|
||||||
can't go in static JSON). Fix by promoting `powerProfileStatus`/`Cycle`
|
generated config and the static `waybar.jsonc` both exec them by bare
|
||||||
from `writeShellScript` to named `writeShellScriptBin`s on PATH, then
|
name (the same parity the DND bell has). General pattern: any new
|
||||||
reference them by bare name from both the generated config and the
|
generated-config module needs adding to the whole-swap themes too.
|
||||||
whole-swap themes (the same parity the DND bell now has). General
|
- **Full hardware enablement (beyond nixos-hardware):** nixos-hardware
|
||||||
pattern: any new generated-config module needs adding to the whole-swap
|
profiles cover *basic* per-model functionality (quirks, drivers), but the
|
||||||
themes too.
|
*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*
|
- **Opt-in services & integrations:** the counterpart to the opt-*out*
|
||||||
application suite above — heavier or more personal integrations shipped
|
application suite above — heavier or more personal integrations shipped
|
||||||
**off by default**, each a `nomarchy.services.<name>.enable` toggle a
|
**off by default**, each a `nomarchy.services.<name>.enable` toggle a
|
||||||
downstream flips on in one line. Keeps the base lean while making common
|
downstream flips on in one line. Keeps the base lean while making common
|
||||||
additions trivial. Candidates by area:
|
additions trivial. **✓ Seventeen shipped** in `modules/nixos/services.nix`
|
||||||
- **cloud/sync:** Nextcloud client; Syncthing (`services.syncthing`)
|
(system-side), each with a commented example in the downstream `system.nix`
|
||||||
- **local AI:** LM Studio (`lmstudio`); Ollama (`services.ollama`, optional
|
template: `tailscale`, `syncthing` (runs as the login user, GUI on
|
||||||
GPU accel) — pairs with the menu's Ask-Claude philosophy
|
127.0.0.1:8384), `podman` (rootless, `docker` aliased, user subuid/subgid),
|
||||||
- **containers/VMs:** Docker/Podman; libvirt + virt-manager
|
`flatpak` (+ Flathub remote added by a oneshot), `pika` (Pika Backup GUI),
|
||||||
- **networking:** Tailscale (`services.tailscale`); WireGuard
|
`steam` (`programs.steam` — the 32-bit stack, controller udev, Remote-Play
|
||||||
- **gaming/media:** Steam (`programs.steam`); OBS Studio
|
ports a bare package can't), `libvirt` (libvirtd + virt-manager, user added
|
||||||
- **devices:** printing (CUPS + Avahi); KDE Connect / phone integration;
|
to the `libvirtd` group), `obs` (OBS Studio + a v4l2loopback virtual camera
|
||||||
OpenRGB
|
usable as a webcam in Zoom/Teams), `docker` (rootful; asserts against the
|
||||||
- **backup:** restic/borg (`services.restic`)
|
podman docker-compat), `kdeconnect`, `gamemode`, `adb` (android-tools;
|
||||||
- **escape hatch:** Flatpak (`services.flatpak`) for apps outside nixpkgs
|
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 decided: `nomarchy.services.*`, system-side for system services
|
||||||
surface or split (`nomarchy.services.*` system-side vs home-side packages);
|
(home-side ones can extend the same prefix later). Remaining: curate the
|
||||||
and how it relates to `nomarchy.apps.*` (opt-out suite). Unfree entries are
|
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`).
|
already covered (`allowUnfree = true`).
|
||||||
- ✓ **Display / monitor management:** declarative `nomarchy.monitors` (a
|
- ✓ **Display / monitor management:** declarative `nomarchy.monitors` (a
|
||||||
per-output schema — name/resolution/position/scale/transform/mirror/
|
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
|
switching** of the *same* outputs (Hyprland's per-output rules cover the
|
||||||
common "external connects → arrange" case, not multi-layout toggles), and
|
common "external connects → arrange" case, not multi-layout toggles), and
|
||||||
optionally workspace-to-monitor binding.
|
optionally workspace-to-monitor binding.
|
||||||
- **Night light / blue-light filter:** schedulable colour-temperature shift
|
- ✓ **Night light / blue-light filter:** `nomarchy.nightlight` (opt-in) —
|
||||||
via `hyprsunset` (Hyprland-native; wlsunset/gammastep are alternatives) —
|
a scheduled colour-temperature shift via `hyprsunset` (Hyprland-native),
|
||||||
sunset/sunrise or a fixed schedule, with a menu + Waybar toggle.
|
warm at night, identity (no shift) by day. `modules/home/nightlight.nix`
|
||||||
- **Runtime keyboard-layout switching:** the session layout is a single
|
drives the HM `services.hyprsunset` with two time-based profiles
|
||||||
`nomarchy.keyboard.layout` today. Support a list of layouts with a toggle
|
(`sunrise` → identity, `sunset` → `temperature`), so hyprsunset handles the
|
||||||
bind (`hyprctl switchxkblayout` / xkb `grp:` options) and a Waybar
|
schedule and the on-login state. Needs an on-hardware check that hyprsunset
|
||||||
indicator for the active layout. Natural follow-on to the LUKS-keymap work
|
applies the active profile at session start. Remaining (optional): a menu +
|
||||||
— keep the system (console/initrd) and session layouts in sync.
|
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
|
- ✓ **Do-Not-Disturb:** swaync DND toggle wired into the menu
|
||||||
(`nomarchy-menu dnd`, in the picker, SUPER+CTRL+D) and a Waybar bell
|
(`nomarchy-menu dnd`, in the picker, SUPER+CTRL+D) and a Waybar bell
|
||||||
indicator (`custom/notification` via `swaync-client -swb`: shows the
|
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
|
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
|
it on is silent (notifications are suppressed — the bell-off glyph is the
|
||||||
cue).
|
cue).
|
||||||
- **Snapshot browse/restore UX:** snapper already takes BTRFS timeline
|
- ✓ **Snapshot browse/restore UX:** `nomarchy-menu snapshot` (in the SUPER+M
|
||||||
snapshots (`nomarchy.system.snapper`); surface them from the desktop — a
|
picker, self-gated on snapper) launches **btrfs-assistant** — the
|
||||||
rofi menu (or btrfs-assistant) to browse/diff/restore and boot-from-
|
desktop browse/diff/restore/rollback UI, elevating via polkit (the safe
|
||||||
snapshot — so rollback isn't CLI-only.
|
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`);
|
- **Update awareness:** updates are manual today (`sys-update`/`home-update`);
|
||||||
add a Waybar indicator / notification when flake inputs are stale or a new
|
add a Waybar indicator / notification when flake inputs are stale or a new
|
||||||
nixpkgs rev is available (optionally with a pending-change count). Augments,
|
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
|
dark. `stylix.nix` now sets `dconf` `org/gnome/desktop/interface
|
||||||
color-scheme` = `prefer-light`/`prefer-dark` from `t.mode`
|
color-scheme` = `prefer-light`/`prefer-dark` from `t.mode`
|
||||||
(xdg-desktop-portal-gtk already ships, and `programs.dconf` is on
|
(xdg-desktop-portal-gtk already ships, and `programs.dconf` is on
|
||||||
system-side), so libadwaita/Qt follow the palette. Needs a live session
|
system-side), so libadwaita/Qt follow the palette. Confirmed in a live
|
||||||
to confirm the portal picks it up for all apps.
|
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
|
./idle.nix # hyprlock + hypridle, themed from the same JSON
|
||||||
./yazi.nix # flagship TUI file manager, themed + plugins
|
./yazi.nix # flagship TUI file manager, themed + plugins
|
||||||
./osd.nix # swayosd volume/brightness OSD, themed
|
./osd.nix # swayosd volume/brightness OSD, themed
|
||||||
|
./nightlight.nix # scheduled blue-light filter (hyprsunset), opt-in
|
||||||
./shell.nix # zsh + starship + bat/eza/zoxide, themed
|
./shell.nix # zsh + starship + bat/eza/zoxide, themed
|
||||||
./keys.nix # gpg-agent (fronts SSH) + pinentry-qt
|
./keys.nix # gpg-agent (fronts SSH) + pinentry-qt
|
||||||
./fastfetch.nix # system info with the themed Nomarchy logo
|
./fastfetch.nix # system info with the themed Nomarchy logo
|
||||||
|
|||||||
@@ -54,6 +54,64 @@ let
|
|||||||
++ lib.optionals (m.vrr != null) [ "vrr" (toString m.vrr) ]
|
++ lib.optionals (m.vrr != null) [ "vrr" (toString m.vrr) ]
|
||||||
++ lib.optional (m.extra != "") m.extra
|
++ 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
|
in
|
||||||
{
|
{
|
||||||
wayland.windowManager.hyprland = lib.mkIf config.nomarchy.hyprland.enable {
|
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
|
# Paint the wallpaper as soon as the session is up (waits for
|
||||||
# the daemon internally).
|
# the daemon internally).
|
||||||
"nomarchy-theme-sync wallpaper"
|
"nomarchy-theme-sync wallpaper"
|
||||||
];
|
] ++ lib.optional kbAutoSwitch "${keyboardWatch}/bin/nomarchy-keyboard-watch";
|
||||||
|
|
||||||
# ── Theme-driven look ──────────────────────────────────────────
|
# ── Theme-driven look ──────────────────────────────────────────
|
||||||
# These flow from theme-state.json and stay at NORMAL priority:
|
# These flow from theme-state.json and stay at NORMAL priority:
|
||||||
@@ -144,12 +202,20 @@ in
|
|||||||
input = {
|
input = {
|
||||||
# kb_layout/variant come from nomarchy.keyboard.* — set that
|
# kb_layout/variant come from nomarchy.keyboard.* — set that
|
||||||
# option (the installer writes it; it also drives tty/LUKS).
|
# 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;
|
kb_variant = config.nomarchy.keyboard.variant;
|
||||||
follow_mouse = lib.mkDefault 1;
|
follow_mouse = lib.mkDefault 1;
|
||||||
touchpad.natural_scroll = lib.mkDefault true;
|
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`
|
# dwindle:pseudotile was removed in Hyprland 0.55 (the `pseudo`
|
||||||
# dispatcher still exists); setting it aborts config parsing.
|
# dispatcher still exists); setting it aborts config parsing.
|
||||||
dwindle.preserve_split = lib.mkDefault true;
|
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.";
|
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 {
|
package = lib.mkOption {
|
||||||
type = lib.types.package;
|
type = lib.types.package;
|
||||||
default = pkgs.nomarchy-theme-sync;
|
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 {
|
monitors = lib.mkOption {
|
||||||
type = lib.types.listOf monitorType;
|
type = lib.types.listOf monitorType;
|
||||||
default = [ ];
|
default = [ ];
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ let
|
|||||||
nomarchy-menu = pkgs.writeShellScriptBin "nomarchy-menu" ''
|
nomarchy-menu = pkgs.writeShellScriptBin "nomarchy-menu" ''
|
||||||
# Nomarchy menu dispatcher — thin presentation layer over
|
# Nomarchy menu dispatcher — thin presentation layer over
|
||||||
# `rofi -dmenu`; actions delegate to nomarchy-theme-sync, systemctl,
|
# `rofi -dmenu`; actions delegate to nomarchy-theme-sync, systemctl,
|
||||||
# hyprctl and friends. Icons are nf-md glyphs (Pango falls back to a
|
# hyprctl and friends. Menu entries carry real icons from the theme's
|
||||||
# Nerd Font for them). `nomarchy-menu` with no argument shows the
|
# icon set (Papirus) via rofi's per-row icon protocol — see row() below.
|
||||||
# module picker.
|
# `nomarchy-menu` with no argument shows the module picker.
|
||||||
|
|
||||||
urlencode() {
|
urlencode() {
|
||||||
local s="$1" out="" ch i
|
local s="$1" out="" ch i
|
||||||
@@ -58,16 +58,22 @@ let
|
|||||||
printf '%s' "$out"
|
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
|
case "''${1:-}" in
|
||||||
power)
|
power)
|
||||||
choice=$(printf '%s\n' \
|
choice=$( {
|
||||||
" Lock" \
|
row "Lock" system-lock-screen
|
||||||
" Logout" \
|
row "Logout" system-log-out
|
||||||
" Suspend" \
|
row "Suspend" system-suspend
|
||||||
" Hibernate" \
|
row "Hibernate" system-hibernate
|
||||||
" Reboot" \
|
row "Reboot" system-reboot
|
||||||
" Shutdown" \
|
row "Shutdown" system-shutdown
|
||||||
| rofi -dmenu -p power) || exit 0
|
} | rofi -dmenu -show-icons -p Power) || exit 0
|
||||||
case "$choice" in
|
case "$choice" in
|
||||||
*Lock) loginctl lock-session ;;
|
*Lock) loginctl lock-session ;;
|
||||||
*Logout) hyprctl dispatch exit ;;
|
*Logout) hyprctl dispatch exit ;;
|
||||||
@@ -134,12 +140,12 @@ let
|
|||||||
exec blueman-manager ;;
|
exec blueman-manager ;;
|
||||||
|
|
||||||
capture)
|
capture)
|
||||||
choice=$(printf '%s\n' \
|
choice=$( {
|
||||||
" Region → clipboard" \
|
row "Region → clipboard" applets-screenshooter
|
||||||
" Region → file" \
|
row "Region → file" applets-screenshooter
|
||||||
" Full screen → clipboard" \
|
row "Full screen → clipboard" camera-photo
|
||||||
" Full screen → file" \
|
row "Full screen → file" camera-photo
|
||||||
| rofi -dmenu -p capture) || exit 0
|
} | rofi -dmenu -show-icons -p Capture) || exit 0
|
||||||
dir="$HOME/Pictures/Screenshots"
|
dir="$HOME/Pictures/Screenshots"
|
||||||
file="$dir/$(date +%Y%m%d-%H%M%S).png"
|
file="$dir/$(date +%Y%m%d-%H%M%S).png"
|
||||||
case "$choice" in
|
case "$choice" in
|
||||||
@@ -172,51 +178,85 @@ let
|
|||||||
&& notify-send "Do Not Disturb off" "Notifications resumed."
|
&& notify-send "Do Not Disturb off" "Notifications resumed."
|
||||||
exit 0 ;;
|
exit 0 ;;
|
||||||
|
|
||||||
"")
|
snapshot)
|
||||||
entries=(
|
# btrfs-assistant: snapshot browse / diff / restore / rollback over
|
||||||
" Apps"
|
# snapper, elevating via polkit. Shipped system-side with
|
||||||
" Theme"
|
# nomarchy.system.snapper; self-gate so the entry no-ops elsewhere.
|
||||||
" Clipboard"
|
command -v btrfs-assistant >/dev/null 2>&1 \
|
||||||
" Calculator"
|
|| { notify-send "Snapshots" "btrfs-assistant isn't installed (BTRFS snapshots off?)."; exit 0; }
|
||||||
" Files"
|
exec btrfs-assistant ;;
|
||||||
" Emoji"
|
|
||||||
" Web search"
|
tools)
|
||||||
" Network"
|
# Tools submenu — utilities you invoke. Each leaf is also reachable
|
||||||
" Bluetooth"
|
# directly (SUPER+CTRL+<mnemonic>); this just keeps the root short.
|
||||||
" Capture"
|
choice=$( {
|
||||||
" Keybindings"
|
row "Calculator" accessories-calculator
|
||||||
" Ask Claude"
|
row "Clipboard" edit-paste
|
||||||
" Do Not Disturb"
|
row "Emoji" face-smile-big
|
||||||
)
|
row "Files" system-file-manager
|
||||||
# Power-profile picker only when this is a laptop (battery present)
|
row "Web search" system-search
|
||||||
# running power-profiles-daemon — matches the Waybar indicator's
|
row "Capture" applets-screenshooter
|
||||||
# self-gating; desktops and the TLP backend don't show it.
|
row "Ask Claude" internet-chat
|
||||||
|
row "Back" go-previous
|
||||||
|
} | rofi -dmenu -show-icons -p Tools) || exit 0
|
||||||
|
case "$choice" in
|
||||||
|
*Calc*) exec "$0" calc ;;
|
||||||
|
*Clipboard*) exec "$0" clipboard ;;
|
||||||
|
*Emoji*) exec "$0" emoji ;;
|
||||||
|
*Files*) exec "$0" files ;;
|
||||||
|
*Web*) exec "$0" web ;;
|
||||||
|
*Capture*) exec "$0" capture ;;
|
||||||
|
*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*)
|
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
|
if [ -e "''${bats[0]}" ] && command -v powerprofilesctl >/dev/null 2>&1; then
|
||||||
entries+=(" Power profile")
|
row "Power profile" preferences-system-power
|
||||||
fi
|
fi
|
||||||
entries+=(" Power")
|
row "Back" go-previous
|
||||||
choice=$(printf '%s\n' "''${entries[@]}" | rofi -dmenu -p menu) || exit 0
|
} | 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 ;;
|
||||||
|
*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
|
case "$choice" in
|
||||||
*Apps*) exec rofi -show drun ;;
|
*Apps*) exec rofi -show drun ;;
|
||||||
*Theme*) exec "$0" theme ;;
|
*Theme*) exec "$0" theme ;;
|
||||||
*Clipboard*) exec "$0" clipboard ;;
|
*Tools*) exec "$0" tools ;;
|
||||||
*Calc*) exec "$0" calc ;;
|
*System*) exec "$0" system ;;
|
||||||
*Files*) exec "$0" files ;;
|
|
||||||
*Emoji*) exec "$0" emoji ;;
|
|
||||||
*Web*) exec "$0" web ;;
|
|
||||||
*Network*) exec "$0" network ;;
|
|
||||||
*Bluetooth*) exec "$0" bluetooth ;;
|
|
||||||
*Capture*) exec "$0" capture ;;
|
|
||||||
*Keybindings*) exec "$0" keybinds ;;
|
|
||||||
*Ask*) exec "$0" ask ;;
|
|
||||||
*"Do Not Disturb"*) exec "$0" dnd ;;
|
|
||||||
*"Power profile"*) exec "$0" power-profile ;;
|
|
||||||
*Power*) exec "$0" power ;;
|
*Power*) exec "$0" power ;;
|
||||||
|
*Keybindings*) exec "$0" keybinds ;;
|
||||||
esac ;;
|
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 ;;
|
exit 64 ;;
|
||||||
esac
|
esac
|
||||||
'';
|
'';
|
||||||
@@ -248,6 +288,10 @@ in
|
|||||||
drun-display-format = "{name}";
|
drun-display-format = "{name}";
|
||||||
display-drun = "apps";
|
display-drun = "apps";
|
||||||
display-run = "run";
|
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
|
# Whole-swap themes bring their own rofi.rasi; otherwise the theme
|
||||||
@@ -300,22 +344,34 @@ in
|
|||||||
columns = 1;
|
columns = 1;
|
||||||
dynamic = true;
|
dynamic = true;
|
||||||
scrollbar = false;
|
scrollbar = false;
|
||||||
spacing = px 2;
|
spacing = px 4; # a little air so the rounded zebra rows separate
|
||||||
padding = mkLiteral "8px 0px 0px 0px";
|
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" = {
|
"element" = {
|
||||||
padding = mkLiteral "8px 12px";
|
padding = mkLiteral "10px 14px";
|
||||||
border-radius = px t.ui.rounding;
|
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";
|
background-color = mkLiteral "@accent";
|
||||||
text-color = mkLiteral "@bg";
|
text-color = mkLiteral "@bg";
|
||||||
};
|
};
|
||||||
"element-icon" = {
|
"element-icon" = {
|
||||||
background-color = mkLiteral "transparent";
|
background-color = mkLiteral "transparent";
|
||||||
size = mkLiteral "1em";
|
size = px t.ui.iconSize; # real icons (Papirus), not 1em font glyphs
|
||||||
};
|
};
|
||||||
"element-text" = {
|
"element-text" = {
|
||||||
background-color = mkLiteral "transparent";
|
background-color = mkLiteral "transparent";
|
||||||
|
|||||||
@@ -35,6 +35,9 @@ in
|
|||||||
ignoreSpace = true;
|
ignoreSpace = true;
|
||||||
expireDuplicatesFirst = 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 = {
|
shellAliases = {
|
||||||
cat = "bat --paging=never";
|
cat = "bat --paging=never";
|
||||||
less = "bat";
|
less = "bat";
|
||||||
@@ -42,8 +45,42 @@ in
|
|||||||
# eza enableZshIntegration aliases ls/ll/la; add a couple extras.
|
# eza enableZshIntegration aliases ls/ll/la; add a couple extras.
|
||||||
lt = "eza --tree --level=2 --icons=auto --group-directories-first";
|
lt = "eza --tree --level=2 --icons=auto --group-directories-first";
|
||||||
tree = "eza --tree --icons=auto --group-directories-first";
|
tree = "eza --tree --icons=auto --group-directories-first";
|
||||||
|
|
||||||
|
# Navigation — climb without counting dots.
|
||||||
".." = "cd ..";
|
".." = "cd ..";
|
||||||
"..." = "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;
|
gapsOut = 12;
|
||||||
borderSize = 2;
|
borderSize = 2;
|
||||||
rounding = 10;
|
rounding = 10;
|
||||||
|
iconSize = 36; # rofi launcher + menu icon size (px) for the generated theme
|
||||||
activeOpacity = 1.0;
|
activeOpacity = 1.0;
|
||||||
inactiveOpacity = 0.95;
|
inactiveOpacity = 0.95;
|
||||||
terminalOpacity = 0.96;
|
terminalOpacity = 0.96;
|
||||||
|
|||||||
@@ -12,12 +12,23 @@
|
|||||||
let
|
let
|
||||||
t = config.nomarchy.theme;
|
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-
|
# Power-profile indicator (power-profiles-daemon). Both scripts self-
|
||||||
# gate: they exit silently unless this is a laptop (a battery is
|
# gate: they exit silently unless this is a laptop (a battery is
|
||||||
# present) running PPD, so the module hides itself on desktops and
|
# present) running PPD, so the module hides itself on desktops and
|
||||||
# under the TLP backend — no system→home coupling. Speedometer glyphs
|
# under the TLP backend — no system→home coupling. Speedometer glyphs
|
||||||
# (slow/medium/fast) map to power-saver/balanced/performance.
|
# (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
|
case "$(ls /sys/class/power_supply/ 2>/dev/null)" in *BAT*) ;; *) exit 0 ;; esac
|
||||||
command -v powerprofilesctl >/dev/null 2>&1 || exit 0
|
command -v powerprofilesctl >/dev/null 2>&1 || exit 0
|
||||||
prof=$(powerprofilesctl get 2>/dev/null) || 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"
|
printf '{"text":"%s","tooltip":"Power profile: %s","class":"%s"}\n' "$icon" "$prof" "$prof"
|
||||||
'';
|
'';
|
||||||
# Cycle to the next profile this machine actually offers (wraps around).
|
# 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
|
command -v powerprofilesctl >/dev/null 2>&1 || exit 0
|
||||||
cur=$(powerprofilesctl get 2>/dev/null) || exit 0
|
cur=$(powerprofilesctl get 2>/dev/null) || exit 0
|
||||||
avail=$(powerprofilesctl list 2>/dev/null | sed -nE 's/^[* ] ([a-z-]+):$/\1/p')
|
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-left = [ "hyprland/workspaces" "hyprland/window" ];
|
||||||
modules-center = [ "clock" ];
|
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" = {
|
"hyprland/workspaces" = {
|
||||||
format = "{icon}";
|
format = "{icon}";
|
||||||
@@ -84,6 +97,13 @@ let
|
|||||||
tooltip-format = "<tt><small>{calendar}</small></tt>";
|
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 = {
|
pulseaudio = {
|
||||||
format = "{icon} {volume}%";
|
format = "{icon} {volume}%";
|
||||||
format-muted = "";
|
format-muted = "";
|
||||||
@@ -112,10 +132,10 @@ let
|
|||||||
};
|
};
|
||||||
|
|
||||||
"custom/powerprofile" = {
|
"custom/powerprofile" = {
|
||||||
exec = "${powerProfileStatus}";
|
exec = "nomarchy-powerprofile-status";
|
||||||
return-type = "json";
|
return-type = "json";
|
||||||
interval = 5;
|
interval = 5;
|
||||||
on-click = "${powerProfileCycle}";
|
on-click = "nomarchy-powerprofile-cycle";
|
||||||
};
|
};
|
||||||
|
|
||||||
# swaync notification bell + Do-Not-Disturb state. `-swb` streams JSON
|
# swaync notification bell + Do-Not-Disturb state. `-swb` streams JSON
|
||||||
@@ -189,7 +209,7 @@ let
|
|||||||
font-weight: bold;
|
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;
|
color: @subtext;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
}
|
}
|
||||||
@@ -227,4 +247,9 @@ in
|
|||||||
else generatedStyle
|
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
|
in
|
||||||
{
|
{
|
||||||
imports = [ ./options.nix ./plymouth.nix ./file-manager.nix ./power.nix ];
|
imports = [ ./options.nix ./plymouth.nix ./file-manager.nix ./power.nix ./services.nix ];
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
# The safe half of distro branding: distroName flows into
|
# The safe half of distro branding: distroName flows into
|
||||||
@@ -185,7 +185,7 @@ in
|
|||||||
# is a clean no-op rather than a failing timer.
|
# is a clean no-op rather than a failing timer.
|
||||||
services.snapper.configs = lib.mkIf
|
services.snapper.configs = lib.mkIf
|
||||||
(cfg.snapper.enable && (config.fileSystems."/".fsType or "") == "btrfs")
|
(cfg.snapper.enable && (config.fileSystems."/".fsType or "") == "btrfs")
|
||||||
{
|
({
|
||||||
root = {
|
root = {
|
||||||
SUBVOLUME = "/";
|
SUBVOLUME = "/";
|
||||||
TIMELINE_CREATE = true;
|
TIMELINE_CREATE = true;
|
||||||
@@ -196,6 +196,39 @@ in
|
|||||||
TIMELINE_LIMIT_MONTHLY = "0";
|
TIMELINE_LIMIT_MONTHLY = "0";
|
||||||
TIMELINE_LIMIT_YEARLY = "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 ────────────────────────────────────────────────────────
|
# ── Fonts ────────────────────────────────────────────────────────
|
||||||
@@ -285,7 +318,10 @@ in
|
|||||||
--cleanup-algorithm number
|
--cleanup-algorithm number
|
||||||
echo "Rebuilding..."
|
echo "Rebuilding..."
|
||||||
nixos-rebuild switch --flake /etc/nixos#default "$@"
|
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.
|
# Don't let boot entries fill the ESP over the years.
|
||||||
boot.loader.systemd-boot.configurationLimit = lib.mkDefault 10;
|
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, ... }:
|
{ pkgs, ... }:
|
||||||
|
|
||||||
{
|
{
|
||||||
# Examples:
|
# ── Overrides (the desktop is on by default; tweak or opt out) ──────
|
||||||
# nomarchy.terminal = "kitty"; # swap the default terminal
|
# nomarchy.terminal = "kitty"; # swap the default terminal
|
||||||
# nomarchy.waybar.enable = false; # bring your own bar
|
# nomarchy.waybar.enable = false; # bring your own bar
|
||||||
# nomarchy.stylix.enable = false; # opt out of GTK/Qt theming
|
# nomarchy.stylix.enable = false; # opt out of GTK/Qt theming
|
||||||
# nomarchy.keyboard.layout = "de"; # session keyboard (pair with
|
# nomarchy.keyboard.layout = "de"; # session keyboard (set the matching
|
||||||
# # xkb + console.useXkbConfig in
|
# # services.xserver.xkb.layout in
|
||||||
# # system.nix for tty/LUKS)
|
# # 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; [
|
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
|
# 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" ];
|
extraGroups = [ "wheel" "networkmanager" "video" "input" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
# Examples:
|
# ── Overrides (uncomment to change) ─────────────────────────────────
|
||||||
# nomarchy.system.greeter.enable = false; # bring your own login manager
|
# nomarchy.system.greeter.enable = false; # bring your own login manager
|
||||||
# environment.systemPackages = [ pkgs.htop ];
|
# 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";
|
system.stateVersion = "26.05";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ window {
|
|||||||
border-color: @yellow;
|
border-color: @yellow;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
width: 40%;
|
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;
|
location: center;
|
||||||
anchor: center;
|
anchor: center;
|
||||||
x-offset: 0;
|
x-offset: 0;
|
||||||
@@ -90,6 +91,12 @@ element {
|
|||||||
spacing: 15px;
|
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 {
|
element-icon {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
text-color: inherit;
|
text-color: inherit;
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ window#waybar {
|
|||||||
#workspaces,
|
#workspaces,
|
||||||
#pulseaudio,
|
#pulseaudio,
|
||||||
#network,
|
#network,
|
||||||
|
#custom-powerprofile,
|
||||||
#battery,
|
#battery,
|
||||||
#tray,
|
#tray,
|
||||||
#custom-notification,
|
#custom-notification,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
"modules-left": ["custom/launcher", "clock", "clock#date"],
|
"modules-left": ["custom/launcher", "clock", "clock#date"],
|
||||||
"modules-center": ["hyprland/workspaces"],
|
"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": {
|
"hyprland/workspaces": {
|
||||||
"disable-scroll": true,
|
"disable-scroll": true,
|
||||||
@@ -80,6 +80,13 @@
|
|||||||
|
|
||||||
"tray": { "spacing": 8 },
|
"tray": { "spacing": 8 },
|
||||||
|
|
||||||
|
"custom/powerprofile": {
|
||||||
|
"return-type": "json",
|
||||||
|
"interval": 5,
|
||||||
|
"exec": "nomarchy-powerprofile-status",
|
||||||
|
"on-click": "nomarchy-powerprofile-cycle"
|
||||||
|
},
|
||||||
|
|
||||||
"custom/notification": {
|
"custom/notification": {
|
||||||
"format": "{icon}",
|
"format": "{icon}",
|
||||||
"return-type": "json",
|
"return-type": "json",
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ window {
|
|||||||
border-color: @yellow;
|
border-color: @yellow;
|
||||||
border-radius: 15px;
|
border-radius: 15px;
|
||||||
width: 40%;
|
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;
|
location: center;
|
||||||
anchor: center;
|
anchor: center;
|
||||||
x-offset: 0;
|
x-offset: 0;
|
||||||
@@ -90,6 +91,12 @@ element {
|
|||||||
spacing: 15px;
|
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 {
|
element-icon {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
text-color: inherit;
|
text-color: inherit;
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ window#waybar {
|
|||||||
#workspaces,
|
#workspaces,
|
||||||
#pulseaudio,
|
#pulseaudio,
|
||||||
#network,
|
#network,
|
||||||
|
#custom-powerprofile,
|
||||||
#idle_inhibitor,
|
#idle_inhibitor,
|
||||||
#battery,
|
#battery,
|
||||||
#custom-notification,
|
#custom-notification,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
"modules-left": ["custom/nomarchy", "clock", "clock#date"],
|
"modules-left": ["custom/nomarchy", "clock", "clock#date"],
|
||||||
"modules-center": ["hyprland/workspaces"],
|
"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": {
|
"hyprland/workspaces": {
|
||||||
"disable-scroll": true,
|
"disable-scroll": true,
|
||||||
@@ -87,6 +87,13 @@
|
|||||||
|
|
||||||
"tray": { "spacing": 8 },
|
"tray": { "spacing": 8 },
|
||||||
|
|
||||||
|
"custom/powerprofile": {
|
||||||
|
"return-type": "json",
|
||||||
|
"interval": 5,
|
||||||
|
"exec": "nomarchy-powerprofile-status",
|
||||||
|
"on-click": "nomarchy-powerprofile-cycle"
|
||||||
|
},
|
||||||
|
|
||||||
"custom/notification": {
|
"custom/notification": {
|
||||||
"format": "{icon}",
|
"format": "{icon}",
|
||||||
"return-type": "json",
|
"return-type": "json",
|
||||||
|
|||||||
Reference in New Issue
Block a user