From 05bab5576e7b416f03563404d83b3bfb40e28772 Mon Sep 17 00:00:00 2001 From: Bernardo Magri Date: Sat, 11 Jul 2026 15:06:21 +0100 Subject: [PATCH] fix: migration first-boot, fingerprint PAM, menus, mic LED MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Post-migration annoyances from TuringMachine: - Force fprintAuth from fingerprint.pam (NixOS defaults it on with fprintd) - First-boot toast waits for swaync, retries, only marks after success - Night light status/toggle handles masked units + hyprsunset/wlsunset - VPN menu: define rofi_menu in nomarchy-vpn (was a silent no-op) - Left arrow = ↩ Back in list menus; GTK menu contrast via Stylix - Mic-mute LED udev + sync after wpctl; EasyEffects starts after tray - Migration guide: HM before GUI login, fingerprint/browser/VPN notes Verified: flake check --no-build; checks.first-boot; checks.hardware-toggles (pam=false node). V3 session items pending after user rebuild. --- docs/MIGRATION.md | 37 ++++++-- flake.nix | 27 ++++-- modules/home/default.nix | 9 ++ modules/home/first-boot.nix | 18 +++- modules/home/hyprland.nix | 23 ++++- modules/home/nightlight.nix | 25 ++++-- modules/home/rofi.nix | 86 +++++++++++++++---- modules/home/stylix.nix | 51 ++++++++++- modules/nixos/default.nix | 11 +++ modules/nixos/hardware.nix | 15 +++- .../nomarchy-first-boot.sh | 27 +++++- 11 files changed, 278 insertions(+), 51 deletions(-) diff --git a/docs/MIGRATION.md b/docs/MIGRATION.md index f3782e9..a647d87 100644 --- a/docs/MIGRATION.md +++ b/docs/MIGRATION.md @@ -226,14 +226,23 @@ Expect to resolve a few 25.11→26.05 option renames. # breaks, REBOOT and you are back in your old generation, untouched. sudo nixos-rebuild test --flake ~/nomarchy-migrate#default -# Bring the desktop (home-manager) up: -home-manager switch --flake ~/nomarchy-migrate#bernardo +# Bring the desktop (home-manager) up BEFORE the first graphical login. +# Hyprland without an HM generation shows the yellow "autogenerated +# config" banner (and no Nomarchy theming) — finish this step first. +home-manager switch --flake ~/nomarchy-migrate#bernardo -b bak ``` Log into Hyprland and sanity‑check: Waybar renders, `SUPER+M` opens the -menu, theming is coherent, `SUPER+?` shows the cheatsheet. Confirm the -machine‑specific things you care about still work — suspend/hibernate, the -AMD GPU (`vainfo` → radeonsi), display brightness. +menu, theming is coherent, `SUPER+?` shows the cheatsheet, **no** yellow +Hyprland autogenerated banner. Confirm the machine‑specific things you +care about still work — suspend/hibernate, the AMD GPU (`vainfo` → +radeonsi), display brightness. + +You should also get a one-shot **You're set** toast (menu / themes / +keys). If it never appears: `systemctl --user status nomarchy-first-boot` +and re-try with +`nomarchy-theme-sync set settings.firstBootShown false --no-switch` +then log out/in. If anything is wrong: **reboot → old generation.** Nothing is committed as default yet. @@ -245,10 +254,26 @@ default yet. - **Power:** verify `powerprofilesctl get` works and the Waybar battery / power‑profile icons open the power menu. Your ryzenadj scripts are gone; if you miss a specific TDP behaviour, that's a follow‑up, not a blocker. -- **Theme:** `nomarchy-menu theme` → pick **nord** (writes +- **Theme:** `nomarchy-menu theme` → pick a preset (writes `theme-state.json`). - **Snapshots:** `nomarchy-menu` → System → Snapshots should see your existing `@snapshots` subvolume. +- **Fingerprint:** `fingerprint.enable = true` only starts **fprintd** + (enrollment). Login/sudo finger auth is **`fingerprint.pam`** and is + opt-in — leave it commented until you've enrolled. NixOS defaults PAM + on whenever fprintd is enabled; Nomarchy forces PAM to follow the + `pam` flag, but only after a **system** rebuild. Verify with + `grep pam_fprintd /etc/pam.d/sudo` (should be empty when pam is off). +- **Browser profiles:** Nomarchy does not manage Chromium/Firefox state. + Bookmarks/extensions live under `~/.config/chromium` (or + `~/.config/google-chrome` / ungoogled paths if that was your previous + browser). A package name change can look like "extensions vanished" + while bookmarks remain — point the new browser at the old profile dir + or re-install extensions; nothing was wiped by the activation. +- **VPN:** NetworkManager connections survive under + `/etc/NetworkManager` and your home. System › VPN lists NM + `vpn`/`wireguard` profiles; import any that lived outside NM. + Tailscale is opt-in (`nomarchy.services.tailscale.enable`). - **Secrets/services:** if you relied on agenix‑managed secrets for a service, layer `agenix` back into `system.nix` as a machine‑specific import (Nomarchy doesn't manage secrets). If you don't need them, leave diff --git a/flake.nix b/flake.nix index 1e59e6d..a508b11 100644 --- a/flake.nix +++ b/flake.nix @@ -489,15 +489,23 @@ # what they SET (kernel cmdline, fprintd, PAM). Real hardware # behaviour (firmware/driver/device) needs bare metal and is out of # scope here. Imports only hardware.nix, so the VM stays minimal. + # Two nodes: pam=true must wire pam_fprintd; pam=false must NOT + # (NixOS defaults fprintAuth from fprintd.enable — we force off). hardware-toggles = pkgs.testers.runNixOSTest { name = "nomarchy-hardware-toggles"; - nodes.machine = { ... }: { - imports = [ ./modules/nixos/hardware.nix ]; - nomarchy.hardware = { - intel.enable = true; - amd.enable = true; - fingerprint = { enable = true; pam = true; }; - npu.enable = true; + nodes = { + machine = { ... }: { + imports = [ ./modules/nixos/hardware.nix ]; + nomarchy.hardware = { + intel.enable = true; + amd.enable = true; + fingerprint = { enable = true; pam = true; }; + npu.enable = true; + }; + }; + nopam = { ... }: { + imports = [ ./modules/nixos/hardware.nix ]; + nomarchy.hardware.fingerprint = { enable = true; pam = false; }; }; }; testScript = '' @@ -507,6 +515,11 @@ assert "i915.enable_guc=3" in cmdline, cmdline machine.succeed("systemctl cat fprintd.service") machine.succeed("grep -q pam_fprintd /etc/pam.d/sudo") + + nopam.wait_for_unit("multi-user.target") + nopam.succeed("systemctl cat fprintd.service") + nopam.fail("grep -q pam_fprintd /etc/pam.d/sudo") + nopam.fail("grep -q pam_fprintd /etc/pam.d/login") ''; }; diff --git a/modules/home/default.nix b/modules/home/default.nix index be86649..548d8f7 100644 --- a/modules/home/default.nix +++ b/modules/home/default.nix @@ -40,7 +40,16 @@ services.udiskie.enable = true; # Microphone noise cancellation (rnnoise) and audio EQ. + # Tray icon is built into EasyEffects 8 (Qt StatusNotifierItem); the + # daemon must start after a tray host (Waybar) or the icon is missing + # for the whole session (wwmm/easyeffects#4636). services.easyeffects.enable = true; + systemd.user.services.easyeffects = { + Unit = { + After = [ "graphical-session.target" "tray.target" "waybar.service" ]; + Wants = [ "tray.target" ]; + }; + }; # Wifi from the bar: nm-applet lives in waybar's tray (SNI flag via # preferStatusNotifierItems — without it there is no tray icon). diff --git a/modules/home/first-boot.nix b/modules/home/first-boot.nix index 722cc39..c6683c3 100644 --- a/modules/home/first-boot.nix +++ b/modules/home/first-boot.nix @@ -11,8 +11,18 @@ Unit = { Description = "Nomarchy first-session welcome toast"; PartOf = [ "graphical-session.target" ]; - # After the session (and preferably the notification daemon) is up. - After = [ "graphical-session.target" "graphical-session-pre.target" ]; + # After the session AND the notification daemon. Without + # After=swaync, first login races D-Bus Notifications and the + # toast times out (migration: journal "Timeout was reached") — + # or worse, a later success writes firstBootShown without the + # user ever seeing the toast. Wants= so we still run if swaync + # is disabled (notify-send will fail and leave the marker unset). + After = [ + "graphical-session.target" + "graphical-session-pre.target" + "swaync.service" + ]; + Wants = [ "swaync.service" ]; }; Service = { Type = "oneshot"; @@ -27,8 +37,8 @@ ]}" "NOMARCHY_PATH=%h/.nomarchy" ]; - # Brief settle so swaync is accepting toasts (live ISO uses sleep 3). - ExecStartPre = "${pkgs.coreutils}/bin/sleep 2"; + # Brief settle after swaync is up; the script also retries. + ExecStartPre = "${pkgs.coreutils}/bin/sleep 1"; ExecStart = "${pkgs.nomarchy-first-boot}/bin/nomarchy-first-boot"; }; Install.WantedBy = [ "graphical-session.target" ]; diff --git a/modules/home/hyprland.nix b/modules/home/hyprland.nix index 07ce2f8..b1cb7d4 100644 --- a/modules/home/hyprland.nix +++ b/modules/home/hyprland.nix @@ -20,14 +20,29 @@ let # --custom-progress is the mic level (0.0-1.0), --custom-icon the # muted/active glyph. Pure bash builtins for parsing so the script needs no # PATH beyond the two pinned tools. + # + # Hardware mic-mute LEDs (ThinkPad platform::micmute, HDA *::micmute, …) + # are driven by the kernel's audio-micmute trigger, which tracks ALSA — + # not PipeWire software mute. Sync the LED from the resulting mute state + # when the node is group-writable (udev rule in modules/nixos/default.nix). micMute = pkgs.writeShellScript "nomarchy-mic-mute" '' ${pkgs.wireplumber}/bin/wpctl set-mute @DEFAULT_SOURCE@ toggle out=$(${pkgs.wireplumber}/bin/wpctl get-volume @DEFAULT_SOURCE@) set -- $out # "Volume: 0.85 [MUTED]" -> $2 is the level case "$out" in - *MUTED*) icon=microphone-sensitivity-muted-symbolic ;; - *) icon=microphone-sensitivity-high-symbolic ;; + *MUTED*) icon=microphone-sensitivity-muted-symbolic; led=1 ;; + *) icon=microphone-sensitivity-high-symbolic; led=0 ;; esac + for led_dir in /sys/class/leds/*micmute* /sys/class/leds/*::micmute; do + [ -e "$led_dir/brightness" ] || continue + if [ -w "$led_dir/brightness" ]; then + # Drop the kernel trigger so a stuck audio-micmute state cannot + # override our write (verified: ALSA Capture mute alone does not + # flip platform::micmute on ThinkPad T14s). + [ -w "$led_dir/trigger" ] && echo none > "$led_dir/trigger" 2>/dev/null || true + echo "$led" > "$led_dir/brightness" 2>/dev/null || true + fi + done ${pkgs.swayosd}/bin/swayosd-client --custom-icon "$icon" --custom-progress "$2" ''; @@ -446,6 +461,10 @@ in ]; misc = { + # Kill the water-drop splash + the yellow "autogenerated config" + # banner. Migrations that log into Hyprland before the first + # home-manager switch still see the banner (no managed conf yet) — + # see docs/MIGRATION.md. Once HM owns hyprland.conf these stay off. disable_hyprland_logo = lib.mkDefault true; disable_splash_rendering = lib.mkDefault true; force_default_wallpaper = lib.mkDefault 0; diff --git a/modules/home/nightlight.nix b/modules/home/nightlight.nix index 3599db6..923191a 100644 --- a/modules/home/nightlight.nix +++ b/modules/home/nightlight.nix @@ -45,29 +45,42 @@ let v=$(${sync} get settings.nightlight.on 2>/dev/null) || v=${onDefault} case "$v" in true|True) return 0 ;; *) return 1 ;; esac } - installed() { systemctl --user cat "$unit" >/dev/null 2>&1; } - start() { systemctl --user start "$unit" 2>/dev/null || true; } + # LoadState=loaded means a real unit (not the empty-file mask HM/packages + # leave behind for unused hyprsunset). `systemctl cat` alone succeeds on + # a masked unit and wrongly reported "installed" → silent no-op start. + installed() { + [ "$(systemctl --user show -p LoadState --value "$unit" 2>/dev/null)" = loaded ] + } + active() { systemctl --user is-active --quiet "$unit" 2>/dev/null; } + start() { + systemctl --user daemon-reload 2>/dev/null || true + systemctl --user unmask "$unit" 2>/dev/null || true + systemctl --user start "$unit" 2>/dev/null || true + } stop() { systemctl --user stop "$unit" 2>/dev/null || true; } case "''${1:-toggle}" in should-start) is_on ;; # ExecCondition gate (login/reboot) status) # Waybar (polls every 3s): moon while running; print nothing otherwise so - # the module self-hides — enable / re-enable from the System menu. - systemctl --user is-active --quiet "$unit" \ + # the module self-hides — enable / re-enable from Look & Feel. + # `active` alone is the truth for the moon; menu labels use the same. + active \ && printf '{"text":"󰖔","tooltip":"Night light on — ${if geo then "follows your location" else "warm on schedule"} (click to disable)","class":"on"}\n' exit 0 ;; + # Plain on/off string for menus (hyprsunset OR wlsunset; not hard-coded). + is-active) if active; then echo on; else echo off; fi ;; on) if installed; then write_on true; start; else install_feature; start; fi ;; off) write_on false; stop ;; toggle) - if systemctl --user is-active --quiet "$unit"; then + if active; then write_on false; stop # on -> off (instant) elif installed; then write_on true; start # installed, off -> on (instant) else install_feature; start # first enable (rebuilds) fi ;; - *) echo "usage: nomarchy-nightlight [toggle|status|on|off|should-start]" >&2; exit 64 ;; + *) echo "usage: nomarchy-nightlight [toggle|status|on|off|should-start|is-active]" >&2; exit 64 ;; esac ''; in diff --git a/modules/home/rofi.nix b/modules/home/rofi.nix index 8f74373..147c3e8 100644 --- a/modules/home/rofi.nix +++ b/modules/home/rofi.nix @@ -234,7 +234,34 @@ let # when config.rasi has case-sensitive: false for drun/modi — so # SUPER+Space (apps) matched "ste"→Steam while the main Menu filter # did not match "sys"→System. Always pass -i on menu pickers. - rofi_menu() { command rofi -dmenu -i "$@"; } + # + # Left arrow → one level up (same as choosing "↩ Back"). rofi's + # -kb-custom-1 returns exit 10; Esc still returns non-zero and quits + # the whole menu. Free-text prompts should treat "$BACK" as cancel. + # Pass --grid as the first arg to skip Left=Back (multi-column theme + # picker needs Left for column motion). + rofi_menu() { + local out rc left_back=1 + if [ "''${1:-}" = --grid ]; then + left_back=0 + shift + fi + if [ "$left_back" -eq 1 ]; then + out=$(command rofi -dmenu -i -kb-custom-1 Left "$@" /dev/null + # Use the toggle script (knows hyprsunset vs wlsunset); a hard-coded + # hyprsunset-only check always showed (off) in geo mode and when + # the unit was still the empty HM mask from a disabled package unit. + if [ "$(nomarchy-nightlight is-active 2>/dev/null)" = on ] then row "Night light (on)" weather-clear-night else row "Night light (off)" weather-clear-night fi @@ -680,7 +710,7 @@ ${themeRows} # which lags behind model releases; npx caches after first run. The # REPL stays open for follow-ups. q=$(rofi_menu -p claude < /dev/null) || exit 0 - [ -n "$q" ] || exit 0 + { [ -n "$q" ] && [ "$q" != "$BACK" ]; } || exit 0 exec ${cfg.terminal} -e npx --yes @anthropic-ai/claude-code@latest "$q" ;; dnd) @@ -752,9 +782,9 @@ ${themeRows} case "$choice" in "Sunset"*) key=sunset; label=Sunset; cur="''${sunset:-20:00}" ;; esac t=$(rofi_menu -p "$label HH:MM (now $cur)" < /dev/null) || exec "$0" autotheme case "$t" in + ""|"$BACK") : ;; [01][0-9]:[0-5][0-9]|2[0-3]:[0-5][0-9]) nomarchy-theme-sync --quiet set "settings.autoTheme.$key" "$t" --no-switch ;; - "") : ;; *) notify-send "Auto theme" "Ignored '$t' — use HH:MM (24-hour)." ;; esac exec "$0" autotheme ;; @@ -1119,7 +1149,7 @@ ${themeRows} *60%*) set_limit 60 "Set to 60%" ;; *Custom*) limit=$(rofi_menu -p "Charge limit % (50–100)" < /dev/null) || exit 0 - [ -n "$limit" ] || exit 0 + { [ -n "$limit" ] && [ "$limit" != "$BACK" ]; } || exit 0 case "$limit" in *[!0-9]*) notify-send "Battery limit" "Not a whole number: $limit"; exit 0 ;; esac @@ -1172,6 +1202,23 @@ ${themeRows} nomarchy-vpn = pkgs.writeShellScriptBin "nomarchy-vpn" '' set -u BACK="↩ Back" + # Own helper — this is a separate package from nomarchy-menu, so the + # menu's rofi_menu function is not in scope (was a silent no-op: every + # VPN submenu died with "rofi_menu: command not found"). + rofi_menu() { + local out rc + out=$(command rofi -dmenu -i -kb-custom-1 Left "$@" /dev/null \ @@ -1180,12 +1227,15 @@ ${themeRows} toggle_conn() { mark=''${1%% *}; name=''${1#* } + err= if [ "$mark" = "●" ]; then - nmcli connection down "$name" >/dev/null 2>&1 \ - && notify-send "VPN" "Disconnected $name" || notify-send "VPN" "Could not disconnect $name" + err=$(nmcli connection down "$name" 2>&1) \ + && notify-send "VPN" "Disconnected $name" \ + || notify-send "VPN" "Could not disconnect $name''${err:+ — $err}" else - nmcli connection up "$name" >/dev/null 2>&1 \ - && notify-send "VPN" "Connected $name" || notify-send "VPN" "Could not connect $name (auth or bad config?)" + err=$(nmcli connection up "$name" 2>&1) \ + && notify-send "VPN" "Connected $name" \ + || notify-send "VPN" "Could not connect $name''${err:+ — $err}" fi } @@ -1196,11 +1246,9 @@ ${themeRows} | rofi_menu -p "Import VPN config" ) || return { [ -z "$file" ] || [ "$file" = "$BACK" ]; } && return case "$file" in *.ovpn) type=openvpn ;; *) type=wireguard ;; esac - if nmcli connection import type "$type" file "$HOME/$file" >/dev/null 2>&1; then - notify-send "VPN" "Imported $type config: $(basename "$file")" - else - notify-send "VPN" "Import failed ($type plugin missing, or bad file?)" - fi + err=$(nmcli connection import type "$type" file "$HOME/$file" 2>&1) \ + && notify-send "VPN" "Imported $type config: $(basename "$file")" \ + || notify-send "VPN" "Import failed''${err:+ — $err}" } ts_state() { tailscale status --json 2>/dev/null | jq -r '.BackendState // "Unknown"'; } @@ -1257,14 +1305,18 @@ ${themeRows} while :; do have_ts="" command -v tailscale >/dev/null 2>&1 && have_ts=1 + rows=$(conn_rows) choice=$( { - conn_rows + if [ -n "$rows" ]; then printf '%s\n' "$rows" + else printf 'No VPNs yet — import a config or enable Tailscale\n' + fi printf 'Import config…\n' [ -n "$have_ts" ] && printf 'Tailscale ›\n' printf '%s\n' "$BACK" } | rofi_menu -p VPN ) || exit 0 case "$choice" in "$BACK"|"") exec nomarchy-menu system ;; + "No VPNs yet"*) ;; # hint row — stay in menu "Import config…") import_config ;; "Tailscale ›") tailscale_menu ;; "● "*|"○ "*) toggle_conn "$choice" ;; diff --git a/modules/home/stylix.nix b/modules/home/stylix.nix index 1e966b8..bef8207 100644 --- a/modules/home/stylix.nix +++ b/modules/home/stylix.nix @@ -45,7 +45,56 @@ in base16Scheme = base16; targets = { - gtk.enable = true; + gtk = { + enable = true; + # nm-applet / classic GTK menus: Stylix base16 selection can + # collapse into surface/muted on dark themes (Boreal: selected + # row and submenu arrows were effectively invisible). High- + # contrast selected rows + explicit arrow colour. Appended to + # Stylix's generated gtk.css (not gtk.gtk3.extraCss — that is a + # no-op under Stylix). + extraCss = '' + menu menuitem, .menu menuitem, .context-menu menuitem { + color: #${base16.base05}; + } + menu menuitem:hover, menu menuitem:selected, + .menu menuitem:hover, .menu menuitem:selected, + .context-menu menuitem:hover, .context-menu menuitem:selected, + menubar > menuitem:hover { + background-color: #${base16.base0D}; + color: #${base16.base00}; + } + menu menuitem:disabled, .menu menuitem:disabled { + color: #${base16.base03}; + } + /* Submenu disclosure arrows — without this they pick muted. */ + menu menuitem arrow, .menu menuitem arrow, + menu menuitem:hover arrow, menu menuitem:selected arrow { + min-width: 16px; + min-height: 16px; + margin-left: 8px; + color: inherit; + -gtk-icon-source: -gtk-icontheme("pan-end-symbolic"); + } + menu menuitem:hover arrow, menu menuitem:selected arrow { + color: #${base16.base00}; + } + check, radio { + min-width: 14px; + min-height: 14px; + color: inherit; + } + /* GTK4 / popover menus (some portals). */ + popover.menu contents modelbutton { + color: #${base16.base05}; + } + popover.menu contents modelbutton:hover, + popover.menu contents modelbutton:selected { + background-color: #${base16.base0D}; + color: #${base16.base00}; + } + ''; + }; qt.enable = true; # No-op unless programs.zathura is on (viewers.nix enables it). zathura.enable = true; diff --git a/modules/nixos/default.nix b/modules/nixos/default.nix index 97eb267..59ea7be 100644 --- a/modules/nixos/default.nix +++ b/modules/nixos/default.nix @@ -450,6 +450,17 @@ in # users without root. Harmless baseline even if the OSD is disabled. services.udev.packages = [ pkgs.swayosd ]; + # Mic-mute LED sysfs nodes (ThinkPad platform::micmute, HDA *::micmute) + # default to root-only. PipeWire mute does not drive the kernel's + # audio-micmute trigger, so nomarchy-mic-mute writes brightness itself. + # Group `video` matches the backlight udev pattern (swayosd) and the + # template login user's extraGroups. + services.udev.extraRules = lib.mkAfter '' + ACTION=="add", SUBSYSTEM=="leds", KERNEL=="*micmute*", \ + RUN+="${pkgs.coreutils}/bin/chgrp video /sys/class/leds/%k/brightness /sys/class/leds/%k/trigger", \ + RUN+="${pkgs.coreutils}/bin/chmod g+w /sys/class/leds/%k/brightness /sys/class/leds/%k/trigger" + ''; + # ── Nix itself ─────────────────────────────────────────────────── nix = { settings = { diff --git a/modules/nixos/hardware.nix b/modules/nixos/hardware.nix index e001712..8a73f7b 100644 --- a/modules/nixos/hardware.nix +++ b/modules/nixos/hardware.nix @@ -193,12 +193,19 @@ in }) # ── Fingerprint ──────────────────────────────────────────────────── + # NixOS defaults security.pam.services.*.fprintAuth to + # services.fprintd.enable — so turning on fprintd alone would enable + # finger auth for login/sudo/greetd/passwd/… even when the user (or + # the migration template) left fingerprint.pam commented off. Force + # every interactive service we care about to follow our opt-in flag. (lib.mkIf cfg.fingerprint.enable { services.fprintd.enable = true; - }) - (lib.mkIf (cfg.fingerprint.enable && cfg.fingerprint.pam) { - security.pam.services.login.fprintAuth = true; - security.pam.services.sudo.fprintAuth = true; + security.pam.services = lib.genAttrs [ + "login" "sudo" "su" "greetd" "hyprlock" "sshd" + "passwd" "chsh" "chfn" "chpasswd" + "polkit-1" "swaylock" + "groupadd" "groupdel" "groupmod" "groupmems" + ] (_: { fprintAuth = cfg.fingerprint.pam; }); }) # ── Newest kernel for very-new hardware (opt-in escape hatch) ────── diff --git a/pkgs/nomarchy-first-boot/nomarchy-first-boot.sh b/pkgs/nomarchy-first-boot/nomarchy-first-boot.sh index b83354e..afc0a77 100644 --- a/pkgs/nomarchy-first-boot/nomarchy-first-boot.sh +++ b/pkgs/nomarchy-first-boot/nomarchy-first-boot.sh @@ -19,13 +19,32 @@ case "$shown" in true|1|yes) exit 0 ;; esac -# Dismissible sheet, not a wizard. Body lines match SUPER+? mnemonics. -notify-send -a Nomarchy -u normal -t 0 \ - "You're set" \ - "SUPER+M menu · SUPER+T themes · SUPER+? keys +# Wait for the notification daemon (swaync). On first login the unit can +# race graphical-session and get "Timeout was reached" from D-Bus — then +# either no toast, or a toast that never lands while we still write the +# marker. Retry notify-send; only persist the marker after a success. +body="SUPER+M menu · SUPER+T themes · SUPER+? keys Wi‑Fi: System › Network (or the bar tray) Anything off? System › Doctor" +ok= +i=0 +while [ "$i" -lt 8 ]; do + if notify-send -a Nomarchy -u normal -t 0 \ + "You're set" \ + "$body"; then + ok=1 + break + fi + i=$((i + 1)) + sleep 2 +done + +if [ -z "$ok" ]; then + # Leave firstBootShown alone so the next login can try again. + exit 1 +fi + # Persist in the checkout so re-login is silent. --no-switch: marker only. if ! nomarchy-theme-sync --quiet set settings.firstBootShown true --no-switch; then # No writable flake checkout (or tool missing) — still showed the toast;