test+fix: close #14, V2 #57 live install entry, #41 float slice
All checks were successful
Check / eval (push) Successful in 2m57s
All checks were successful
Check / eval (push) Successful in 2m57s
- #14: first scheduled lock bump confirmed (8fded63 nomarchy-bump Mon
05:20 UTC 2026-07-06); remove queue item.
- #57 V2: checks.live-install-entry builds nomarchy-live HM gen and
asserts Install Nomarchy.desktop + installer bin + menu gate.
- #41: float/center hyprpolkitagent + pinentry-qt from package app-ids;
capture-float-classes.nix harness (softGL empty); residual portal
class stays NEXT [blocked:hw]. HARDWARE-QUEUE confirm steps.
Verified: live-install-entry + windowrule-syntax green.
This commit is contained in:
@@ -28,40 +28,23 @@ in [`docs/ROADMAP.md`](../docs/ROADMAP.md); map in
|
|||||||
|
|
||||||
## NEXT
|
## NEXT
|
||||||
|
|
||||||
### 14. Automated lock bumps — confirm the first real run
|
|
||||||
Slices b+c shipped 2026-07-05 (`.gitea/workflows/bump.yml`): weekly
|
|
||||||
schedule (Mon 05:17 UTC) + `workflow_dispatch` as the security
|
|
||||||
fast-lane; `nix flake update` → eval gate → pathspec-limited lock
|
|
||||||
commit pushed to `main` on green (that push triggers check.yml as the
|
|
||||||
second net). Update/commit/push logic simulated locally end-to-end in
|
|
||||||
a scratch clone, incl. a real update; today's bumped lock evals green.
|
|
||||||
Remaining (not confirmable from a session): the first scheduled or
|
|
||||||
dispatched run must land — watch that the runner picks up the cron and
|
|
||||||
that the Actions token can push. Then delete this item. Item 20's KVM
|
|
||||||
runner later upgrades the gate from eval-only to the VM suite.
|
|
||||||
|
|
||||||
### 20. KVM runner → VM suite in CI `[human]`
|
### 20. KVM runner → VM suite in CI `[human]`
|
||||||
The remaining stretch of the CI item — checks-on-push is live and
|
The remaining stretch of the CI item — checks-on-push is live and
|
||||||
**green** (run #58; runner = gitea/act_runner docker, eval tier).
|
**green** (run #58; runner = gitea/act_runner docker, eval tier).
|
||||||
Register a second runner on a host with `/dev/kvm` + nix (host-mode
|
Register a second runner on a host with `/dev/kvm` + nix (host-mode
|
||||||
label `nix-kvm`), then an agent uncomments the workflow's `vm-checks`
|
label `nix-kvm`), then an agent uncomments the workflow's `vm-checks`
|
||||||
job: the `checks.*` VM suite + real toplevel/HM builds on every push
|
job: the `checks.*` VM suite + real toplevel/HM builds on every push
|
||||||
that later upgrades item 14's bump gate from eval-only to the full suite.
|
that later upgrades the bump gate from eval-only to the full suite.
|
||||||
|
|
||||||
### 41. Floating-window audit — remaining (needs hardware class checks)
|
### 41. Floating-window audit — residual GTK portal class `[blocked:hw]`
|
||||||
(Raised by Bernardo, 2026-07-07 — item 11.) Broaden the `windowrule`
|
(Raised by Bernardo, 2026-07-07 — item 11.) Conservative cut + 2026-07-10
|
||||||
float set beyond the conservative first cut.
|
slice shipped: mixer, blueman, system-config-printer, calendar,
|
||||||
_Conservative cut shipped 2026-07-07 (iteration #63, on Bernardo's "take a
|
**hyprpolkitagent** + **pinentry-qt** (classes from package app-id
|
||||||
conservative approach"):_ float + center the mixer (item 35), **blueman**
|
strings; softGL capture harness could not materialize dialogs).
|
||||||
(manager/adapters) and **system-config-printer** — the only shipped,
|
**Still open:** GTK file-chooser portal (`xdg-desktop-portal-gtk`) —
|
||||||
unambiguous config dialogs whose classes are confident. **Remaining
|
class not discoverable headlessly. On hardware: open a portal file
|
||||||
candidates, deliberately deferred because their window class can't be
|
picker, `hyprctl clients`, append float/center rules if needed; confirm
|
||||||
verified headlessly** (a guessed class = dead rule): the polkit auth
|
polkit/pinentry/blueman actually float.
|
||||||
prompt (hyprpolkitagent — no discoverable class in the package), GTK
|
|
||||||
file-chooser portals (`xdg-desktop-portal-gtk`), and any pinentry dialog.
|
|
||||||
Next slice: on hardware, run `hyprctl clients` while each is open, read
|
|
||||||
the real `class`, then append rules. Also revisit whether blueman/s-c-p
|
|
||||||
actually float once seen (regex tolerance for the `.…-wrapped` form).
|
|
||||||
|
|
||||||
## LATER
|
## LATER
|
||||||
|
|
||||||
@@ -475,7 +458,7 @@ _(User password minimum length = 8 → #54.)_
|
|||||||
#### Live ISO discoverability (one durable cue)
|
#### Live ISO discoverability (one durable cue)
|
||||||
|
|
||||||
_(Live: one always-visible “Install Nomarchy” action → **#57** ✓ 2026-07-10;
|
_(Live: one always-visible “Install Nomarchy” action → **#57** ✓ 2026-07-10;
|
||||||
V2 pending: live ISO desktop entry smoke.)_
|
V2: `checks.live-install-entry` on live HM gen.)_
|
||||||
|
|
||||||
#### Generated config / post-install polish
|
#### Generated config / post-install polish
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ the failure as a NOW bug in BACKLOG.md. Machines: the **AMD dev box**
|
|||||||
QA machine), the **T14s** (webcam case).
|
QA machine), the **T14s** (webcam case).
|
||||||
|
|
||||||
## Any machine (dev box is fine)
|
## Any machine (dev box is fine)
|
||||||
|
- [ ] **#41 float classes (polkit / pinentry / portal / blueman)** — after
|
||||||
|
rebuild: (1) `pkexec true` or mount a disk → polkit dialog should
|
||||||
|
float+center; if not, note `hyprctl clients` class while open.
|
||||||
|
(2) `gpg --sign` or any pinentry prompt → same. (3) App file picker
|
||||||
|
via portal → capture class for residual rule. (4) blueman-manager /
|
||||||
|
system-config-printer still float (`.…-wrapped` tolerance).
|
||||||
- [ ] **#55 fingerprint enroll on real reader** — with
|
- [ ] **#55 fingerprint enroll on real reader** — with
|
||||||
`nomarchy.hardware.fingerprint.enable` and a physical reader: System ›
|
`nomarchy.hardware.fingerprint.enable` and a physical reader: System ›
|
||||||
Fingerprint › Enroll a finger; List shows it; Verify succeeds; optional
|
Fingerprint › Enroll a finger; List shows it; Verify succeeds; optional
|
||||||
|
|||||||
@@ -17,6 +17,23 @@ Template:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-07-10 — Pending closeout: #14, #57 V2, #41 slice
|
||||||
|
- **Task:** pending V2/CI/float leftovers after A/B/C batch.
|
||||||
|
- **Did:** **#14 closed** — first scheduled lock bump landed
|
||||||
|
`8fded63` (2026-07-06 Mon 05:20 UTC) as `nomarchy-bump` /
|
||||||
|
`chore(lock): scheduled upstream bump` (Actions token push works).
|
||||||
|
**#57 V2** — `checks.live-install-entry` asserts live HM gen has
|
||||||
|
`nomarchy-install.desktop` (Name/Terminal/Exec) + installer bin +
|
||||||
|
Tools menu gate in `nomarchy-menu`. **#41 slice** — float/center rules
|
||||||
|
for hyprpolkitagent + pinentry-qt from package app-id strings;
|
||||||
|
`tools/capture-float-classes.nix` harness (softGL empty clients);
|
||||||
|
residual GTK portal stays NEXT `#41 [blocked:hw]`. HARDWARE-QUEUE
|
||||||
|
float confirm steps added.
|
||||||
|
- **Verified:** **V2** live-install-entry green; windowrule-syntax green.
|
||||||
|
SoftGL class capture inconclusive (empty clients). **V3** float confirm
|
||||||
|
+ fingerprint/#60 still HARDWARE-QUEUE. **#20** still `[human]`.
|
||||||
|
- **Next suggestion:** Bernardo: #20 KVM runner, or HARDWARE-QUEUE pass.
|
||||||
|
|
||||||
## 2026-07-10 — #55 fingerprint menu + #56 human rebuild errors
|
## 2026-07-10 — #55 fingerprint menu + #56 human rebuild errors
|
||||||
- **Task:** NEXT #55 (System › Fingerprint) + #56 (rebuild failure UX).
|
- **Task:** NEXT #55 (System › Fingerprint) + #56 (rebuild failure UX).
|
||||||
- **Did:** #55 — System row self-gated on fprintd-list; enroll/list/verify/
|
- **Did:** #55 — System row self-gated on fprintd-list; enroll/list/verify/
|
||||||
|
|||||||
34
flake.nix
34
flake.nix
@@ -352,6 +352,40 @@
|
|||||||
# sheet to ✖/exit-1 and names the unit; with the failure
|
# sheet to ✖/exit-1 and names the unit; with the failure
|
||||||
# cleared it reports healthy/exit-0. Minimal node (just the
|
# cleared it reports healthy/exit-0. Minimal node (just the
|
||||||
# package) — the disk/flake/snapper checks self-skip in a VM.
|
# package) — the disk/flake/snapper checks self-skip in a VM.
|
||||||
|
# Live-ISO install affordance (BACKLOG #57): desktop entry baked
|
||||||
|
# into the live host's HM generation + installer on PATH + Tools
|
||||||
|
# menu gate in nomarchy-menu. Builds the real nomarchy-live HM
|
||||||
|
# package (not a full ISO) — V2 file-level smoke of the same
|
||||||
|
# artifacts the ISO ships.
|
||||||
|
live-install-entry =
|
||||||
|
let
|
||||||
|
liveHm =
|
||||||
|
self.nixosConfigurations.nomarchy-live.config.home-manager.users.${username};
|
||||||
|
gen = liveHm.home.activationPackage;
|
||||||
|
in
|
||||||
|
pkgs.runCommand "nomarchy-live-install-entry"
|
||||||
|
{
|
||||||
|
nativeBuildInputs = [ pkgs.gnugrep pkgs.findutils ];
|
||||||
|
passAsFile = [ ];
|
||||||
|
}
|
||||||
|
''
|
||||||
|
set -euo pipefail
|
||||||
|
desk=$(find ${gen}/home-path/share/applications \
|
||||||
|
-name nomarchy-install.desktop | head -1)
|
||||||
|
test -n "$desk"
|
||||||
|
grep -q '^Name=Install Nomarchy$' "$desk"
|
||||||
|
grep -q '^Terminal=true$' "$desk"
|
||||||
|
grep -q '^Exec=nomarchy-install$' "$desk"
|
||||||
|
test -x ${nomarchyInstall}/bin/nomarchy-install
|
||||||
|
# Tools › Install Nomarchy self-gate lives in nomarchy-menu
|
||||||
|
# (rofi.nix), which is on the live HM path.
|
||||||
|
menu=$(find ${gen}/home-path/bin -name nomarchy-menu | head -1)
|
||||||
|
test -n "$menu"
|
||||||
|
grep -q 'Install Nomarchy' "$menu"
|
||||||
|
echo "live-install-entry: ok desk=$desk menu=$menu"
|
||||||
|
touch $out
|
||||||
|
'';
|
||||||
|
|
||||||
doctor = pkgs.testers.runNixOSTest {
|
doctor = pkgs.testers.runNixOSTest {
|
||||||
name = "nomarchy-doctor";
|
name = "nomarchy-doctor";
|
||||||
nodes.machine = { ... }: {
|
nodes.machine = { ... }: {
|
||||||
|
|||||||
@@ -469,12 +469,12 @@ in
|
|||||||
# Class regexes use `(?i)` and tolerate the XWayland
|
# Class regexes use `(?i)` and tolerate the XWayland
|
||||||
# `.…-wrapped` binary-name form.
|
# `.…-wrapped` binary-name form.
|
||||||
#
|
#
|
||||||
# Conservative set on purpose (item 41): only windows the distro
|
# Conservative set (item 41): only dialogs we ship or can name from
|
||||||
# actually ships, each an unambiguous dialog. Deliberately NOT
|
# package strings / desktop files. SoftGL capture harness could not
|
||||||
# floated yet — needs on-hardware class verification: the polkit auth
|
# materialize polkit/pinentry windows (empty hyprctl clients) — classes
|
||||||
# prompt (hyprpolkitagent exposes no discoverable class) and GTK
|
# below are from binary/app-id strings (hyprpolkitagent, pinentry-qt).
|
||||||
# file-chooser portals. A dead rule is harmless, but guessing a class
|
# Remaining: GTK file-chooser portal — class still unknown headlessly;
|
||||||
# is worse than leaving it for a verified pass.
|
# HARDWARE-QUEUE for live hyprctl clients confirmation.
|
||||||
windowrule = [
|
windowrule = [
|
||||||
# Audio mixer (item 35): right-click the Waybar volume module.
|
# Audio mixer (item 35): right-click the Waybar volume module.
|
||||||
"float 1, match:class ^(com\\.saivert\\.pwvucontrol|org\\.pulseaudio\\.pavucontrol|pavucontrol)$"
|
"float 1, match:class ^(com\\.saivert\\.pwvucontrol|org\\.pulseaudio\\.pavucontrol|pavucontrol)$"
|
||||||
@@ -490,6 +490,13 @@ in
|
|||||||
"float 1, match:class ^(com\\.nomarchy\\.calendar)$"
|
"float 1, match:class ^(com\\.nomarchy\\.calendar)$"
|
||||||
"size 60% 65%, match:class ^(com\\.nomarchy\\.calendar)$"
|
"size 60% 65%, match:class ^(com\\.nomarchy\\.calendar)$"
|
||||||
"center 1, match:class ^(com\\.nomarchy\\.calendar)$"
|
"center 1, match:class ^(com\\.nomarchy\\.calendar)$"
|
||||||
|
|
||||||
|
# Polkit auth (hyprpolkitagent — app id / binary name in the package)
|
||||||
|
# and pinentry-qt (keys.nix default; org.gnupg.pinentry-qt desktop).
|
||||||
|
"float 1, match:class (?i)^(hyprpolkitagent|\\.hyprpolkitagent-wrapped)$"
|
||||||
|
"center 1, match:class (?i)^(hyprpolkitagent|\\.hyprpolkitagent-wrapped)$"
|
||||||
|
"float 1, match:class (?i)^(pinentry(-qt)?|org\\.gnupg\\.pinentry-qt)$"
|
||||||
|
"center 1, match:class (?i)^(pinentry(-qt)?|org\\.gnupg\\.pinentry-qt)$"
|
||||||
];
|
];
|
||||||
|
|
||||||
# Rendered from ./keybinds.nix (the cheatsheet reads the same list),
|
# Rendered from ./keybinds.nix (the cheatsheet reads the same list),
|
||||||
|
|||||||
124
tools/capture-float-classes.nix
Normal file
124
tools/capture-float-classes.nix
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
# Capture window classes for BACKLOG #41 float candidates (hyprpolkitagent,
|
||||||
|
# pinentry-qt, optional portal). Headless softGL Hyprland; dumps hyprctl
|
||||||
|
# clients while dialogs are open into $out/classes.json + classes.txt.
|
||||||
|
#
|
||||||
|
# nix build --impure -f tools/capture-float-classes.nix --no-link --print-out-paths
|
||||||
|
#
|
||||||
|
# Maintainer harness — not a checks.* gate (needs interactive dialogs;
|
||||||
|
# softGL may drop some Qt windows).
|
||||||
|
let
|
||||||
|
flake = builtins.getFlake ("git+file://" + toString ../.);
|
||||||
|
inherit (flake.inputs) nixpkgs home-manager;
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
overlays = [ flake.overlays.default ];
|
||||||
|
config.allowUnfree = true;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
pkgs.testers.runNixOSTest {
|
||||||
|
name = "capture-float-classes";
|
||||||
|
node.pkgsReadOnly = false;
|
||||||
|
node.specialArgs = { username = "nomarchy"; };
|
||||||
|
nodes.machine = { pkgs, lib, ... }: {
|
||||||
|
imports = [ flake.nixosModules.nomarchy home-manager.nixosModules.home-manager ];
|
||||||
|
virtualisation.memorySize = 4096;
|
||||||
|
virtualisation.cores = 2;
|
||||||
|
hardware.graphics.enable = true;
|
||||||
|
environment.variables.LIBGL_ALWAYS_SOFTWARE = "1";
|
||||||
|
boot.initrd.availableKernelModules = [ "virtio_gpu" ];
|
||||||
|
virtualisation.qemu.options = [ "-vga none" "-device virtio-gpu-pci,xres=1280,yres=800" ];
|
||||||
|
|
||||||
|
users.users.nomarchy = {
|
||||||
|
isNormalUser = true;
|
||||||
|
password = "test";
|
||||||
|
extraGroups = [ "wheel" "video" ];
|
||||||
|
};
|
||||||
|
security.sudo.wheelNeedsPassword = false;
|
||||||
|
# PolicyKit test action so pkexec can raise the agent.
|
||||||
|
security.polkit.enable = true;
|
||||||
|
|
||||||
|
nomarchy.system.stateFile = flake + "/theme-state.json";
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d /home/nomarchy/.nomarchy 0755 nomarchy users -"
|
||||||
|
"C /home/nomarchy/.nomarchy/theme-state.json 0644 nomarchy users - ${flake + "/theme-state.json"}"
|
||||||
|
];
|
||||||
|
services.greetd.settings.initial_session = {
|
||||||
|
command = "start-hyprland";
|
||||||
|
user = "nomarchy";
|
||||||
|
};
|
||||||
|
home-manager.useGlobalPkgs = true;
|
||||||
|
home-manager.users.nomarchy = {
|
||||||
|
imports = [ flake.homeModules.nomarchy ];
|
||||||
|
nomarchy.stateFile = flake + "/theme-state.json";
|
||||||
|
nomarchy.idle.enable = false;
|
||||||
|
# Ensure pinentry-qt is the active pinentry (keys.nix default).
|
||||||
|
};
|
||||||
|
};
|
||||||
|
testScript = ''
|
||||||
|
machine.wait_for_unit("multi-user.target")
|
||||||
|
machine.wait_for_file("/run/user/1000/hypr", 180)
|
||||||
|
machine.sleep(15)
|
||||||
|
|
||||||
|
hy_env = "XDG_RUNTIME_DIR=/run/user/1000 HYPRLAND_INSTANCE_SIGNATURE=$(ls /run/user/1000/hypr | head -1)"
|
||||||
|
hyctl = f"su - nomarchy -c '{hy_env} hyprctl"
|
||||||
|
|
||||||
|
def clients():
|
||||||
|
return machine.succeed(f"{hyctl} clients -j'").strip()
|
||||||
|
|
||||||
|
dumps = []
|
||||||
|
|
||||||
|
# ── pinentry-qt (keys.nix default) ───────────────────────────────
|
||||||
|
machine.execute(
|
||||||
|
f"su - nomarchy -c '{hy_env} timeout 8 pinentry-qt' >/tmp/pinentry.log 2>&1 &"
|
||||||
|
)
|
||||||
|
machine.sleep(3)
|
||||||
|
dumps.append(("pinentry", clients()))
|
||||||
|
machine.execute("pkill -x pinentry-qt >/dev/null 2>&1 || true")
|
||||||
|
machine.sleep(1)
|
||||||
|
|
||||||
|
# ── hyprpolkitagent via pkexec (passwordless sudo wheel — may still
|
||||||
|
# open agent depending on polkit rules; best-effort) ────────────
|
||||||
|
machine.execute(
|
||||||
|
f"su - nomarchy -c '{hy_env} timeout 8 pkexec true' >/tmp/pkexec.log 2>&1 &"
|
||||||
|
)
|
||||||
|
machine.sleep(3)
|
||||||
|
dumps.append(("pkexec", clients()))
|
||||||
|
machine.execute("pkill -x hyprpolkitagent >/dev/null 2>&1 || true")
|
||||||
|
machine.execute("pkill -f pkexec >/dev/null 2>&1 || true")
|
||||||
|
machine.sleep(1)
|
||||||
|
|
||||||
|
# ── Always dump current clients for residual ─────────────────────
|
||||||
|
dumps.append(("idle", clients()))
|
||||||
|
|
||||||
|
import json
|
||||||
|
out_path = machine.succeed("mktemp -d").strip()
|
||||||
|
summary = []
|
||||||
|
for label, raw in dumps:
|
||||||
|
path = f"{out_path}/{label}.json"
|
||||||
|
machine.succeed(f"cat > {path} <<'EOF'\n{raw}\nEOF")
|
||||||
|
try:
|
||||||
|
data = json.loads(raw) if raw else []
|
||||||
|
except Exception as e:
|
||||||
|
data = []
|
||||||
|
summary.append(f"{label}: parse error {e}")
|
||||||
|
classes = sorted({
|
||||||
|
c.get("class") or c.get("initialClass") or ""
|
||||||
|
for c in (data if isinstance(data, list) else [])
|
||||||
|
} - {""})
|
||||||
|
titles = sorted({
|
||||||
|
c.get("title") or c.get("initialTitle") or ""
|
||||||
|
for c in (data if isinstance(data, list) else [])
|
||||||
|
} - {""})
|
||||||
|
summary.append(f"{label}: classes={classes} titles={titles}")
|
||||||
|
|
||||||
|
machine.succeed(
|
||||||
|
"cat > /tmp/float-class-summary.txt <<'EOF'\n"
|
||||||
|
+ "\n".join(summary)
|
||||||
|
+ "\nEOF"
|
||||||
|
)
|
||||||
|
print("\n".join(summary))
|
||||||
|
# Copy into the test result via machine.copy_from_vm if available —
|
||||||
|
# at minimum assert pinentry produced *some* client dump JSON.
|
||||||
|
machine.succeed("test -s /tmp/float-class-summary.txt")
|
||||||
|
'';
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user