diff --git a/README.md b/README.md index 111a0c4..49b4905 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ Flat on purpose. Two module trees, one options file each, no hidden layers. ├── pkgs/ │ ├── nomarchy-theme-sync/ # state writer + rebuild dispatcher (Python) │ └── nomarchy-install/ # live-ISO installer (gum + disko + mkFlake) -├── templates/downstream/ # `nix flake init -t` starter for users +├── templates/downstream/ # machine flake SoT (`flake init` + installer copy/patch) ├── docs/TESTING.md # how to verify changes (incl. AI-agent rules) ├── docs/HARDWARE.md # firmware, drivers, profiles, unsupported machines ├── docs/OVERRIDES.md # how downstream users override defaults diff --git a/agent/BACKLOG.md b/agent/BACKLOG.md index ac0bb27..5591281 100644 --- a/agent/BACKLOG.md +++ b/agent/BACKLOG.md @@ -103,6 +103,15 @@ implement. Bernardo moves accepted items into a tier.* ### Pre-existing +- **Default theme → boreal** + Ship Boreal as the distro default instead of Tokyo Night: root + `theme-state.json`, `templates/downstream/theme-state.json` (and any + greeter/plymouth fallbacks that hardcode Tokyo Night when state is + missing). Preset already exists under `themes/boreal/`. Capture + `preview.png` if still missing so the picker looks first-class. + Cost: copy/apply palette into seed state files + grep for tokyo-night + defaults; V0 contrast, V1/V2 theme apply, V3 visual on hardware. + - **Battery charge-limit — make the toggle instant** `[blocked:hw]` (follow-up to the preset toggle shipped 2026-07-07, iteration #55). The control-center now has a proper toggle (Off / 80% / 90% / 60% / @@ -240,15 +249,21 @@ implement. Bernardo moves accepted items into a tier.* `rollback`, …). Misleading for direct invocation / scripting. Cost: one string in `rofi.nix`. -- **Template / seed state drift** +- **Template / seed state drift (residual after SoT work)** (1) `templates/downstream/theme-state.json` lacks `border` and - `settings` keys present in repo root state (eval merges defaults — - not a break, but new installs ship a thinner shape); (2) no - commented `nomarchy.system.snapper.enable` or `hardware.i2c.*` in - `system.nix` despite opt-in convention; (3) installer-generated - `home.nix` is minimal (keyboard only) while `flake init` template - ships a fat default package suite — product divergence worth - documenting or aligning. Cost: small template/docs. + `settings` keys present in repo root state (eval merges defaults); + (2) no commented `nomarchy.system.snapper.enable` or + `hardware.i2c.*` in template `system.nix` despite opt-in convention. + Main home/system sync is **Installer consumes template as SoT** + below — do that first, then close these residual gaps on the + template itself. Cost: small template polish. + +- **Installer ↔ template SoT — follow-ups** (shipped 2026-07-09: copy + + `patch-template.py`). Remaining: (1) optional CI that the install + share ships the same template files; (2) V2 `test-install.sh` after + ISO rebuild (HM pre-activate now pulls starter packages — larger + closure); (3) residual theme-state / i2c comment polish on template + (see residual seed drift item). - **Secondary docs polish** README layout tree omits many real modules and pkgs @@ -600,6 +615,117 @@ of that doc — design notes live there (§4–§10).* Prefer the commented-guidance item first; promote only with hardware-queue coverage. Cost: high; `[big]` + `[blocked:hw]`. +### Installer process (audit 2026-07-09) + +*Full-path review of live ISO → `nomarchy-install` → disko → config +generation → offline/online `nixos-install` → HM pre-activate → first +boot. Philosophy applied: **opinionated, stability-first** — prefer safer +defaults and fewer install questions over option sprawl. Day-2 firmware / +fingerprint / re-detect stay in Hardware product above, not in the +installer questionnaire.* + +#### Already strong (do not “fix” with more choices) + +- Single whole-disk GPT, UEFI + systemd-boot only; multi-disk RAID / + dual-boot / BIOS deferred (ROADMAP LATER). +- LUKS2 **default** + greeter auto-login (one gate); type-to-confirm wipe; + live medium excluded from disk list; careful pre-wipe. +- BTRFS subvols + snapper on by default; hibernation swap default = RAM; + hardware autodetection with **safe defaults on, heavy bits commented**. +- Offline-capable install (compose-lock, ISO store pin, daemon + substituter); HM pre-activate so first boot is themed. +- No Secure Boot theater; no install-time app-suite *wizard* / custom + partitioner / pbkdf knobs. (Starter apps come from the **template** + `home.packages` once the installer consumes the template as SoT — + see above; not a separate install questionnaire.) +- Live: no idle suspend (avoids mid-install sleep); WiFi via normal + desktop NM path. + +#### Bugs (stability contracts — fix, don’t optionalize) + +- **Installer swap=0 / unattended LUKS fail-open** — already under + *Bugs / broken behavior* above (`"0G"` vs `"0"`; missing + `NOMARCHY_LUKS_PASSPHRASE` → cleartext). Same audit reconfirmed both + as install-path P0. + +- **Offline mode must not silently fall back to network lock** + When the network probe fails (or install is offline), + `compose-lock.py` failure currently **warns** and runs `nix flake + lock` (needs network). That can turn a promised offline install into a + hung/partial one. **Fix:** if offline path is active and compose-lock + fails → **fail the install** with a clear message (dirty ISO / missing + narHash). Network `flake lock` only when the probe said online. + Cost: small branch in `nomarchy-install.sh`; V2 offline install test. + +#### Safety / clarity (one line each — no new install menus) + +- **Review panel: warn when target already has Windows / BitLocker / + NTFS / crypto_LUKS** + Typed disk-name confirm is solid, but multi-OS machines still risk + “wrong disk.” At selection or review, if `lsblk -f`/`blkid` shows + BitLocker, NTFS, Microsoft, or existing LUKS on the **chosen** disk, + add one explicit line: data will be destroyed. Still single-disk, + whole-disk only — **no** dual-boot path. Cost: few lines of probe + + review string; V0 + V2 optional. + +- **Review panel: offline vs online source line** + Review always implies “no network needed” even when the online path + (skip full store copy, use substituters) is active. Make the Source + line conditional on the same `cache.nixos.org` probe so expectations + match. Cost: tiny; V0. + +- **User password minimum length = LUKS (8 chars)** + LUKS requires ≥8; account password only requires non-empty. Align to + 8 for one consistent rule — not a strength meter. Cost: one check; + V0. + +#### Live ISO discoverability (one durable cue) + +- **Live: one always-visible “Install Nomarchy” action** + Install is only in: 3s notify-send toast, getty helpLine, and + tribal knowledge of `nomarchy-install`. No desktop entry / menu row. + **One** durable surface on the live host only: e.g. + `xdg.desktopEntries` (Terminal=true) **or** a single Tools/System + menu item that opens a terminal into `nomarchy-install`. Prefer that + over more install-time questions or a second installer UI. Also fix + stale `hosts/live.nix` header (“No installer yet” — already in docs + drift pack). Cost: live.nix only; V1 ISO rebuild + V2 smoke. + +#### Generated config / post-install polish + +- **HM pre-activate failure → one durable recovery hint on target** + Installer already prints the `home-manager switch` one-liner and logs + to `/var/log/nomarchy-hm-preactivate.log`. If pre-activate fails, also + drop a small flag file or MOTD snippet on the target so first login + still shows the fix after the live session is gone. Avoid permanent + first-boot checklists (fwupd/fprintd belong in menu/doctor). Cost: + small install-script write; V2. + +- **MIGRATION.md: installer snapshot layout vs `@home-snapshots`** + Installer creates disko `@snapshots` → `/.snapshots` and a first-boot + oneshot for nested `/home/.snapshots`. MIGRATION still speaks of + top-level `@home-snapshots` (TuringMachine vocabulary). One paragraph + clarifying installer layout vs migration machines prevents wrong + expectations. Cost: docs only. + +#### Test / pure contracts (after P0 bugs) + +- **Installer pure checks + expanded unattended matrix** — overlaps + existing *Tooling* items (disko `swapSize` contract, compose-lock + py_compile, hardware-db ∈ nixos-hardware). After swap/LUKS fixes: + document or script unattended no-swap and explicit no-LUKS paths in + `test-install.sh` (env flags only — no interactive options). Do **not** + grow the interactive installer questionnaire. + +#### Explicit non-goals for the installer (defer) + +Do **not** add install-time: dual-boot / preserve partitions, multi-disk +RAID, Secure Boot/lanzaboote, recovery-key *wizard* (optional silent +recovery print is the only recovery-key idea worth considering later), +custom partitioner, online/offline user toggle, app suite selection, +pbkdf/TRIM knobs, or a second installer frontend. Stability and the +single golden path win. + ## Decisions `[human]` diff --git a/agent/CONVENTIONS.md b/agent/CONVENTIONS.md index d0c5cbd..c6e5542 100644 --- a/agent/CONVENTIONS.md +++ b/agent/CONVENTIONS.md @@ -34,7 +34,9 @@ those, it probably shouldn't exist. in the downstream template's `home.packages` — opt-out is deleting the line. - **Opt-in features** ship a commented example in - `templates/downstream/home.nix` or `system.nix`. + `templates/downstream/home.nix` or `system.nix`. That template is the + single source of truth for machine files: `nomarchy-install` copies it + and patches install-time values only (never a thinner second catalog). - **Menu:** new entries go in the right submenu (Tools › / System ›; root stays six entries), end lists with the shared `↩ Back`, self-gate on the feature's availability, and add the direct diff --git a/pkgs/nomarchy-install/default.nix b/pkgs/nomarchy-install/default.nix index 7ad21f5..72dad64 100644 --- a/pkgs/nomarchy-install/default.nix +++ b/pkgs/nomarchy-install/default.nix @@ -50,7 +50,11 @@ stdenvNoCC.mkDerivation { # Empty flake registry: no network lookups for indirect refs. echo '{"flakes":[],"version":2}' > "$share/registry.json" mkdir -p "$share/template" - cp ${templateDir}/home.nix ${templateDir}/theme-state.json "$share/template/" + # Full downstream template is the SoT; install script copies + patches. + cp ${templateDir}/flake.nix ${templateDir}/system.nix \ + ${templateDir}/home.nix ${templateDir}/theme-state.json \ + "$share/template/" + install -Dm644 patch-template.py "$share/patch-template.py" # nixos-install / nixos-generate-config / nixos-enter / nix / systemd # tools come from the live system on purpose — they must match it. diff --git a/pkgs/nomarchy-install/nomarchy-install.sh b/pkgs/nomarchy-install/nomarchy-install.sh index f40f1b5..f5d9254 100644 --- a/pkgs/nomarchy-install/nomarchy-install.sh +++ b/pkgs/nomarchy-install/nomarchy-install.sh @@ -330,21 +330,14 @@ rm -f "$LUKS_KEY_PATH" "$disko_log" success "Disk partitioned and mounted at /mnt" # Hibernation plumbing: the swapfile's physical offset goes into the -# kernel cmdline. Deactivate swap first so nixos-generate-config doesn't -# also emit a swapDevices entry (we write our own, with resume wiring). -RESUME_CONFIG="" +# kernel cmdline (patched into system.nix). Deactivate swap first so +# nixos-generate-config doesn't also emit a swapDevices entry. +resume_offset="" +root_uuid="" if [[ "$SWAP_GB" != "0" ]]; then swapoff -a 2>/dev/null || true resume_offset=$(btrfs inspect-internal map-swapfile -r /mnt/swap/swapfile) root_uuid=$(findmnt -no UUID /mnt) - RESUME_CONFIG=$(cat < "$FLAKE_DIR/home.nix" </dev/null && thermald=true -{ - # Keyboard for the desktop session; console + LUKS prompt get the - # same layout from system.nix (xkb + console.useXkbConfig). - nomarchy.keyboard.layout = "$KB_LAYOUT"; - nomarchy.keyboard.variant = "$KB_VARIANT"; - - # Examples: - # nomarchy.terminal = "kitty"; # swap the default terminal - # nomarchy.waybar.enable = false; # bring your own bar - # nomarchy.stylix.enable = false; # opt out of GTK/Qt theming - - home.packages = with pkgs; [ - # firefox - ]; -} -EOF - -hw_nix="" +# JSON for patch-template.py (stdin). Hardware profiles as a JSON array. +hw_json="[" +first=1 for p in "${HW_PROFILES[@]:-}"; do - [[ -n "$p" ]] && hw_nix+=" \"$p\"" + [[ -z "$p" ]] && continue + if [[ $first -eq 1 ]]; then first=0; else hw_json+=","; fi + hw_json+=$(printf '%s' "$p" | python3 -c 'import json,sys; print(json.dumps(sys.stdin.read().rstrip("\n")))') done +hw_json+="]" -cat > "$FLAKE_DIR/flake.nix" </dev/null; then - power_lines+=" - nomarchy.system.power.thermal.enable = true; # thermald (Intel)" - fi - POWER_CONFIG=$(cat < "$FLAKE_DIR/system.nix" < # reads a JSON object from stdin +""" +from __future__ import annotations + +import json +import re +import sys +from pathlib import Path + +BEGIN = " # __NOMARCHY_INSTALLER_BEGIN__" +END = " # __NOMARCHY_INSTALLER_END__" + + +def nix_str(s: str) -> str: + """Escape a string for a Nix double-quoted literal.""" + return ( + s.replace("\\", "\\\\") + .replace('"', '\\"') + .replace("${", "\\${") + .replace("\n", "\\n") + ) + + +def replace_once(text: str, old: str, new: str, label: str) -> str: + if old not in text: + sys.exit(f"patch-template: missing placeholder for {label}: {old!r}") + return text.replace(old, new, 1) + + +def patch_flake(text: str, v: dict) -> str: + text = replace_once( + text, + 'description = "My Nomarchy machine";', + f'description = "{nix_str(v["hostname"])} — my Nomarchy machine";', + "flake description", + ) + text = replace_once( + text, + 'username = "me"; # <- your login name', + f'username = "{nix_str(v["username"])}"; # <- your login name', + "flake username", + ) + profiles = v.get("hardwareProfiles") or [] + if profiles: + items = " ".join(f'"{nix_str(p)}"' for p in profiles) + hw_line = f" hardwareProfile = [ {items} ];" + else: + hw_line = " # hardwareProfile = null; # no nixos-hardware profiles selected" + # Replace the optional hardwareProfile comment block with the install choice. + text, n = re.subn( + r"\n # Optional: a nixos-hardware module name for your machine, e\.g\.\n" + r" # hardwareProfile = \"framework-13-7040-amd\";\n" + r" # Names: https://github.com/NixOS/nixos-hardware\n" + r" # \(the future installer fills this in automatically from DMI data\)\n", + f"\n{hw_line}\n" + f" # Names: https://github.com/NixOS/nixos-hardware\n", + text, + count=1, + ) + if n != 1: + sys.exit("patch-template: could not patch hardwareProfile block in flake.nix") + return text + + +def patch_home(text: str, v: dict) -> str: + layout = nix_str(v["keyboardLayout"]) + variant = nix_str(v.get("keyboardVariant") or "") + text = replace_once( + text, + ' nomarchy.keyboard.layout = "us";', + f' nomarchy.keyboard.layout = "{layout}";', + "home keyboard layout", + ) + text = replace_once( + text, + ' nomarchy.keyboard.variant = "";', + f' nomarchy.keyboard.variant = "{variant}";', + "home keyboard variant", + ) + return text + + +def build_installer_region(v: dict) -> str: + lines: list[str] = [ + BEGIN, + " # Written by nomarchy-install from live detection. Safe defaults are", + " # active; heavier opt-ins stay in the commented catalog below.", + ] + + if v.get("autoLogin"): + user = nix_str(v["username"]) + lines += [ + " # LUKS passphrase already gates this machine — skip the greeter password.", + f' nomarchy.system.greeter.autoLogin = "{user}";', + ] + + if v.get("laptop"): + lines += [ + " # Laptop power (PPD + menu/Waybar). Uncomment to cap charge at 80%.", + " nomarchy.system.power.laptop = true;", + " # nomarchy.system.power.batteryChargeLimit = 80;", + ] + if v.get("thermald"): + lines.append( + " nomarchy.system.power.thermal.enable = true; # thermald (Intel)" + ) + + hw = v.get("hardware") or {} + if any(hw.get(k) for k in ("intel", "amd", "fingerprint", "cameraIr", "npu")): + lines.append(" # Hardware enablement (auto-detected).") + if hw.get("intel"): + lines.append( + " nomarchy.hardware.intel.enable = true; # GuC/HuC (i915)" + ) + if hw.get("intelGucOff"): + lines.append( + " nomarchy.hardware.intel.guc = false; # xe driver → GuC default-on" + ) + lines.append( + " # nomarchy.hardware.intel.computeRuntime = true; # OpenCL/oneVPL (opt-in)" + ) + if hw.get("amd"): + lines += [ + " nomarchy.hardware.amd.enable = true; # amd-pstate + VA-API", + " # nomarchy.hardware.amd.rocm.enable = true; # ROCm (multi-GB, opt-in)", + ' # nomarchy.hardware.amd.rocm.gfxOverride = ""; # e.g. "11.0.0" for unlisted iGPU', + ] + if hw.get("fingerprint"): + lines += [ + " nomarchy.hardware.fingerprint.enable = true; # fprintd (enroll: fprintd-enroll)", + " # nomarchy.hardware.fingerprint.pam = true; # login + sudo (opt-in)", + ] + if hw.get("cameraIr"): + lines.append( + " nomarchy.hardware.camera.hideIrSensor = true; # dual-sensor: hide IR node" + ) + if hw.get("npu"): + vendor = nix_str(hw["npu"]) + lines += [ + f" # nomarchy.hardware.npu.enable = true; # {vendor} NPU (experimental; userspace BYO)", + " # nomarchy.hardware.latestKernel = true; # if the NPU driver needs a newer kernel", + ] + + if v.get("resumeOffset") is not None: + root_uuid = nix_str(v["rootUuid"]) + offset = v["resumeOffset"] + lines += [ + " # Swapfile (hibernation-ready: resume points into it).", + ' swapDevices = [{ device = "/swap/swapfile"; }];', + f' boot.resumeDevice = "/dev/disk/by-uuid/{root_uuid}";', + f' boot.kernelParams = [ "resume_offset={offset}" ];', + ] + + # Always on for installer layout (BTRFS + @snapshots). + lines += [ + " # Hourly/daily BTRFS timeline snapshots + nixos-rebuild-snap.", + " nomarchy.system.snapper.enable = true;", + END, + ] + return "\n".join(lines) + "\n" + + +def patch_system(text: str, v: dict) -> str: + text = replace_once( + text, + ' networking.hostName = "my-nomarchy";', + f' networking.hostName = "{nix_str(v["hostname"])}";', + "hostName", + ) + text = replace_once( + text, + ' time.timeZone = "UTC";', + f' time.timeZone = "{nix_str(v["timezone"])}";', + "timeZone", + ) + text = replace_once( + text, + ' i18n.defaultLocale = "en_US.UTF-8";', + f' i18n.defaultLocale = "{nix_str(v["locale"])}";', + "locale", + ) + text = replace_once( + text, + ' services.xserver.xkb.layout = "us";', + f' services.xserver.xkb.layout = "{nix_str(v["keyboardLayout"])}";', + "xkb layout", + ) + text = replace_once( + text, + ' services.xserver.xkb.variant = "";', + f' services.xserver.xkb.variant = "{nix_str(v.get("keyboardVariant") or "")}";', + "xkb variant", + ) + + # Inject password into the user attrset (template has no password for flake-init). + user_block = """ users.users.${username} = { + isNormalUser = true; + extraGroups = [ "wheel" "networkmanager" "video" "input" ]; + };""" + # HASHED_PASSWORD is sha-512 crypt; alphabet is safe in Nix double quotes. + hashed = nix_str(v["hashedPassword"]) + user_patched = f""" users.users.${{username}} = {{ + isNormalUser = true; + extraGroups = [ "wheel" "networkmanager" "video" "input" ]; + initialHashedPassword = "{hashed}"; + }};""" + text = replace_once(text, user_block, user_patched, "user password") + + if BEGIN not in text or END not in text: + sys.exit("patch-template: system.nix missing __NOMARCHY_INSTALLER__ markers") + region = build_installer_region(v) + text = re.sub( + re.escape(BEGIN) + r".*?" + re.escape(END) + r"\n?", + region, + text, + count=1, + flags=re.DOTALL, + ) + return text + + +def main() -> None: + if len(sys.argv) != 2: + sys.exit("usage: patch-template.py ") + flake_dir = Path(sys.argv[1]) + vals = json.load(sys.stdin) + + mapping = { + "flake.nix": patch_flake, + "home.nix": patch_home, + "system.nix": patch_system, + } + for name, fn in mapping.items(): + path = flake_dir / name + path.write_text(fn(path.read_text(), vals)) + print(f"patch-template: patched {', '.join(mapping)} in {flake_dir}") + + +if __name__ == "__main__": + main() diff --git a/templates/downstream/README.md b/templates/downstream/README.md index a791685..2a4ee32 100644 --- a/templates/downstream/README.md +++ b/templates/downstream/README.md @@ -1,5 +1,11 @@ # My Nomarchy machine +This directory is the **single source of truth** for a Nomarchy machine +flake. `nix flake init -t …` copies it as-is; **`nomarchy-install` copies +the same files and only patches** install-time values (username, hostname, +keyboard, detected hardware, password hash, …). Keep commented opt-ins and +`home.packages` here — do not invent a second catalog in the installer. + 1. Overwrite the placeholder hardware config with the real one: `nixos-generate-config --show-hardware-config > hardware-configuration.nix` 2. Set `flake.nix` up **once** (Nomarchy repo URL, your username, optionally diff --git a/templates/downstream/flake.nix b/templates/downstream/flake.nix index a764067..29f8aff 100644 --- a/templates/downstream/flake.nix +++ b/templates/downstream/flake.nix @@ -5,6 +5,7 @@ # tested together upstream. This file is written once (by you or the # installer) and never hand-edited afterwards; your machine lives in # system.nix and home.nix. v1 is the release branch. + # Installer copies this template and patches username + hardwareProfile. inputs.nomarchy.url = "git+https://git.bemagri.xyz/bernardo/nomarchy.git?ref=v1"; outputs = { nomarchy, ... }: diff --git a/templates/downstream/home.nix b/templates/downstream/home.nix index 96e120d..e47ef01 100644 --- a/templates/downstream/home.nix +++ b/templates/downstream/home.nix @@ -2,17 +2,21 @@ # Ghostty, theming engine, Stylix) comes from homeModules.nomarchy; # tune it via the nomarchy.* options, add your own packages and # programs below. +# +# Source of truth for install and flake-init: nomarchy-install copies this +# file and only patches keyboard layout/variant. { pkgs, ... }: { + # Session keyboard — match services.xserver.xkb.* in system.nix (console + # + LUKS prompt follow xkb via the distro module). Installer patches these. + nomarchy.keyboard.layout = "us"; + nomarchy.keyboard.variant = ""; + # ── Overrides (the desktop is on by default; tweak or opt out) ────── # nomarchy.terminal = "kitty"; # swap the default terminal # nomarchy.waybar.enable = false; # bring your own bar # nomarchy.stylix.enable = false; # opt out of GTK/Qt theming - # nomarchy.keyboard.layout = "de"; # session keyboard (set the matching - # # services.xserver.xkb.layout in - # # system.nix too; the console + LUKS - # # prompt follow it automatically) # ── Opt-in features — uncomment and tweak to enable ───────────────── # nomarchy.nightlight = { # scheduled blue-light filter (hyprsunset) diff --git a/templates/downstream/system.nix b/templates/downstream/system.nix index 404e49f..809b3df 100644 --- a/templates/downstream/system.nix +++ b/templates/downstream/system.nix @@ -2,6 +2,9 @@ # The distro itself comes from nomarchy.nixosModules.nomarchy — override # any of its defaults here with plain NixOS options (they use mkDefault), # or via the nomarchy.system.* toggles. +# +# Source of truth for install and flake-init: nomarchy-install copies this +# file and only patches placeholders + the __NOMARCHY_INSTALLER__ region. { pkgs, username, ... }: { @@ -12,12 +15,28 @@ time.timeZone = "UTC"; i18n.defaultLocale = "en_US.UTF-8"; + # One keyboard layout everywhere: xkb is the source of truth; the distro + # defaults (console.useXkbConfig + systemd initrd) derive the virtual + # console and the LUKS passphrase prompt from it. Match home.nix + # nomarchy.keyboard.*. + services.xserver.xkb.layout = "us"; + services.xserver.xkb.variant = ""; + # Your login user — `username` flows in from flake.nix automatically. + # The installer adds initialHashedPassword here; flake-init: set a password + # (or users.mutableUsers) before first boot if needed. users.users.${username} = { isNormalUser = true; extraGroups = [ "wheel" "networkmanager" "video" "input" ]; }; + # __NOMARCHY_INSTALLER_BEGIN__ + # Installer fills this region (power, hardware, snapper, resume, autoLogin). + # Flake-init: leave empty and use the commented catalog below, or enable + # snapper when on BTRFS with a /.snapshots subvolume: + # nomarchy.system.snapper.enable = true; + # __NOMARCHY_INSTALLER_END__ + # ── Overrides (uncomment to change) ───────────────────────────────── # nomarchy.system.greeter.enable = false; # bring your own login manager # environment.systemPackages = [ pkgs.htop ]; @@ -48,6 +67,8 @@ # latestKernel = true; # newest kernel for very-new hardware (drivers not yet in the default) # camera.hideIrSensor = true; # dual-sensor webcam: hide the IR node so apps get the color cam # # (installer-set when a paired RGB+IR webcam is detected) + # i2c.enable = true; # /dev/i2c-* (RGB, sensors); ddcci is distro-default for external brightness + # i2c.ddcci = true; # already mkDefault true — set false to opt out # }; # # nomarchy.services.tailscale.enable = true; # mesh VPN — connect from System › VPN