diff --git a/docs/plans/workflow-system.md b/docs/plans/workflow-system.md index 06784e3..caf23b9 100644 --- a/docs/plans/workflow-system.md +++ b/docs/plans/workflow-system.md @@ -2,499 +2,114 @@ Source: [`../workflow-discovery.md`](../workflow-discovery.md) -Apply the global and repository-local `AGENTS.md` files and the ponytail skill -at full intensity throughout. Preserve user work, prefer native capabilities, -and add no abstraction without a demonstrated repeated need. +The declarative workflow implementation is complete. This file is the living +handoff for operational constraints and external gates; implementation history +is available in Git. -## Non-negotiable activation boundary +## Activation boundary -Only the user may switch or activate configurations. The agent must never run -`nixos-rebuild switch`, a Home Manager activation or switch command, or any -equivalent operation that changes the active generation. Building and -evaluating configurations is allowed; after a successful build, give the user -the exact activation command and wait for confirmation. +Only the user may switch or activate configurations. Agents must not run +`nixos-rebuild switch`, Home Manager activation, or an equivalent command that +changes the active generation. Evaluation and builds are allowed; after a +successful build, provide the exact activation command and wait for the user. -On 2026-09-05 the agent violated this boundary by directly running a built Home -Manager activation package. It restarted the Kitty service and closed the -user's terminal session. Do not repeat this, including for changes described as -user-level, harmless, temporary, or needed only for live verification. +This boundary includes changes described as user-level, harmless, temporary, or +necessary for live verification. A direct Home Manager activation previously +restarted Kitty and closed the user's terminal session. ## Execution discipline -Advance every safe independent lane before asking the user to act. Batch -declarative changes behind one user-controlled activation and consolidate -empirical, credential, hardware, and approval gates into one checklist only -after no autonomous work remains. A single pilot reaching its user gate does -not make it the global next step while another pilot can still progress. - -Never place repositories or worktrees under `~/sync`; synchronize authored -artifacts instead. The folder's sole ignore pattern is the local `.zk` state. +- Advance all safe independent work before asking the user to act. +- Batch declarative changes behind user-controlled activation. +- Consolidate empirical, credential, hardware, and approval gates only after no + autonomous work remains. +- Keep repositories and worktrees outside `~/sync`; synchronize authored + artifacts instead. +- Keep GitHub and other repository remotes strictly read-only. ## Current checkpoint -- Tranche: autonomous consolidation across repositories, tools, and pilots -- State: autonomous implementation exhausted; remaining gates are external or - explicitly deferred -- Branch: `chore/workflow-system` -- Baseline: `nix flake check --no-build --quiet` and no-link Zenith and Orion - system builds passed on 2026-09-06 after making Orion the sole transcription - owner; only the expected dirty-tree and unknown `modules` warnings were - emitted. The generated project menu finds 30 live repositories - without `.archive` or `.worktrees` entries, and the generated daily-note - configuration passes an isolated same-day idempotence test. The managed Actual - and WhatsApp entries are active in the current user session, Evolution's - source registry activates over D-Bus, and the VPN reconnects successfully. - The user activated the terminal root fix: `xdg-terminal-exec` now selects - Kitty correctly and Yazi - launches from Walker. After restarting the stale Elephant and Walker - processes, repository launch also succeeds and exiting Neovim leaves a shell - in the repository. The daily-note launcher and migrated notebook contents - also pass their live sanity check. The attempted Elephant menu restart trigger - stopped its dependent Walker during activation without bringing Walker back, - so it has been removed in favor of applying rare menu changes at login. After - activating that removal and relogging, Walker opens normally and repository - selection starts Neovim in Oil at the project root. Five fresh local clones - pass their flake checks, expose their tracked command catalogs, and remain - clean after shell entry. The dashboard contract additionally passes its full - check and a bounded two-process development smoke test. The dashboard and - traceability PostgreSQL stacks also ran concurrently while both repositories' - checks passed; all test processes and containers were then stopped. The - retired workstation tools are absent and `rclone` is live. Herdr preserves - its pane across detach and reattach. The data-science contract and a real - read-only PR review pilot pass locally. The broad GitHub credentials are gone; - a mode-0600 fine-grained token now passes repository, issue, pull-request, - Actions, and organization Project reads with no stored fallback credential. - The user activated its loader and confirmed a new shell can list organization - Projects; the skill directory and durable remote boundary are also live. The - activated read-only wrapper leaves `GH_TOKEN` out of cached dev shells while - `gh` identifies the work account and Git reads a private Cargo dependency; - the complete yookr-api contract passes. The replacement Context7 key is - mode 0600, the exposed key is revoked, and a live resolve-and-query round trip - passes through the activated runtime header helper. Pinta passed its real-edit - trial, and Outlook's installed PWA exposed stable app ID - `eoficlgicibekocmfdomjbfnjmehnhcd`; after activation, GIMP is absent and the - managed Outlook launcher is the live store-backed entry. A stopped - 10.28-second Fossify M4A - transferred intact into `sync/recordings`, establishing its actual format and - path. The user selected Fossify's native `recordings/inbox` destination, and - the event-driven Orion CPU pipeline passes both an isolated transcription and - a live remote run of that recording without exposing its contents. Orion - waited for the full settlement interval, archived the byte-identical original, - published the expected fallback Markdown, and exited successfully. The user - confirmed its bilingual output, and a guarded rerun with the copied pyannote - model replaced that fallback with a successfully diarized transcript. A pathological - synchronized repository tree was removed from the Pixel. Repositories are now - explicitly outside the Syncthing boundary instead of relying on an expanding - generated-directory ignore list. -- Final audit: the complete branch diff passes `git diff --check`; removed - Helix, Foot, Zed, and stable-Nixpkgs owners have no remaining Nix references, - and the lockfile cleanup contains no replacement inputs. The current desktop, - Walker, Niri, host, and transcription owners were reviewed in full without - finding another autonomous correction to make. -- Next: continue independent external pilots as their real inputs arrive. No - configuration activation is pending. +- State: declarative implementation and autonomous verification complete. +- Branch: `chore/workflow-system`. +- Validation: `nix flake check --no-build --quiet` plus no-link Zenith and Orion + system builds passed on 2026-09-06 after Orion became the sole transcription + owner. The expected dirty-tree and unknown `modules` warnings were emitted. +- Live state: Walker project and terminal actions, daily notes, stable web apps, + Evolution D-Bus activation, the Zenith VPN, invocation-scoped GitHub and + Context7 credentials, Pinta, and Orion transcription passed their completed + pilots. -## Progress +## External or deferred gates -| Batch | State | Evidence or blocker | -| --- | --- | --- | -| Bootstrap and repository ownership | Complete | Repository reconciliation, local plaintext credential cleanup, and Context7 rotation pass | -| Declarative workstation foundation | Complete | All-host builds pass with one Nixpkgs pin, `just`, and an explicit Podman Compose provider | -| Repository contracts and workbench | Waiting externally | Five clean-checkout contracts, the full yookr-api check, and read-only Kubernetes pass; API user credentials, database, report, and S3 inputs remain externally gated | -| Desktop navigation and interaction | Waiting externally | Walker project and terminal actions plus stable Actual, WhatsApp, and Outlook launchers work; the two-output check awaits a docked session | -| Agent workflow | Waiting externally | Herdr, read-only PR review, the corrected skill owner, durable remote boundary, invocation-scoped credentials, and organization Projects pass; a real Monday ticket remains external | -| Notes | Complete | The daily launcher and migrated notebook pass live checks; the approved rollback copy is recoverable from Trash | -| PIM | Deferred | Evolution 3.60.2 with Microsoft 365 support remains available beside Thunderbird; the user deferred account setup and uses Thunderbird plus Outlook meanwhile | -| Transcription | Complete | Real Fossify transfer, settlement, bilingual CPU transcription, diarization, fallback, archive, and synchronization pass on Orion; Zenith has no transcription owner | -| Conditional retirement and completion | Active | Superseded tools and GIMP are absent and Pinta passed; Bruno, DBeaver, and PIM clients remain behind separate gates | +These gates do not block work on each other. -## Repository checkpoints - -| Repository | Branch | Commit | Purpose | -| --- | --- | --- | --- | -| NixOS configuration | `chore/workflow-system` | `41bc61b` | Discovery, paths, Git identity, and Codex defaults | -| NixOS configuration | `chore/workflow-system` | `69552b7` | Simplify flake inputs and declare workstation lifecycle tools | -| NixOS configuration | `chore/workflow-system` | `0c121a6` | Declare Garden tooling and the local workbench | -| NixOS configuration | `chore/workflow-system` | `2fb6061` | Package Garden completion declaratively | -| NixOS configuration | `chore/workflow-system` | `ad01b9b` | Enable NetworkManager's native OpenVPN plugin | -| NixOS configuration | `chore/workflow-system` | `c9ee688` | Advance VPN, desktop, agent, note, and PIM pilots | -| NixOS configuration | `chore/workflow-system` | `cfa5d6d` | Correct PWA ownership and Evolution service activation | -| NixOS configuration | `chore/workflow-system` | `c2f48df` | Restore terminal-backed Walker actions | -| NixOS configuration | `chore/workflow-system` | `6631889` | Open Walker projects at their repository root | -| NixOS configuration | `chore/workflow-system` | `ca65328` | Remove the unsafe Elephant restart trigger | -| NixOS configuration | `chore/workflow-system` | `d085512` | Advance from Walker to the agent pilot | -| NixOS configuration | `chore/workflow-system` | `ee64a7b` | Retire superseded tools and prepare the S3 pilot | -| NixOS configuration | `chore/workflow-system` | `2ef74d4` | Expose the NixOS skill as a supported directory link | -| NixOS configuration | `chore/workflow-system` | `1996411` | Load the machine-local read-only GitHub credential | -| NixOS configuration | `chore/workflow-system` | `422a1ea` | Scope GitHub and Context7 credentials to their callers | -| NixOS configuration | `chore/workflow-system` | `ec54ddd` | Own Outlook's stable launcher and select Pinta over GIMP | -| NixOS configuration | `chore/workflow-system` | `656f144` | Prepare event-driven CPU transcription on Orion | -| NixOS configuration | `chore/workflow-system` | `01c8855` | Make Orion the sole transcription owner after its live pass | -| NixOS configuration | `chore/workflow-system` | `76e9d03` | Declare ADB for the Android sync recovery | -| NixOS configuration | `chore/workflow-system` | `fa17d77` | Exclude generated devenv state from Syncthing | -| NixOS configuration | `chore/workflow-system` | `b38fb9e` | Keep repository trees outside the Syncthing boundary | -| NixOS configuration | `chore/workflow-system` | `5fa85c5` | Complete the bilingual and diarized transcription pilot | -| `yookr-dashboard-new` | `feat/gogv2` | `934605d` | Remove obsolete dashboard converter | -| `yookr-dashboard-new` | `feat/gogv2` | `8e6e4bc` | Establish its project contract and native lifecycle | -| `yookr-dashboard-new` | `feat/gogv2` | `c99f8b6` | Move runtime configuration to its machine-local owner | -| `rules-service` | `chore/workflow-contract` | `555d9cd` | Establish the first common repository contract | -| `yookr-api` | `chore/workflow-contract` | `a76b2b7` | Establish the HTTP and OpenAPI contract | -| `traceability-data-model` | `chore/workflow-contract` | `33cb7ff` | Establish the database and schema contract | -| `traceability-data-model` | `chore/workflow-contract` | `774e710` | Remove Mathesar and simplify the local stack to PostgreSQL | -| `yookr-data-science` | `chore/workflow-contract` | `fa410e1` | Establish its non-mutating project contract | -| `yookr-workbench` | `main` | `badc61c` | Create the workbench and isolate its Garden session | - -## Deferred or externally blocked actions - -None of these block work on independent pilots. Surface the exact applicable -step to the user only when it blocks the current task. - -- During a docked Zenith session, tell the agent the external display is - attached so its stable identity and the two-output `Super+A` pilot can be - completed. The current session exposes only `eDP-1`. -- When the user resumes the deferred Evolution pilot, add accounts without removing any - existing client or changing authority: personal and old-personal through - their existing IMAP/SMTP service, university and work through the - `Microsoft 365` account type, and Radicale through native CalDAV, CardDAV, - and VTODO. Complete tenant consent/sign-in when prompted, then tell the agent - which accounts connected; the full mail/calendar/contact/task gate remains - a daily-use pilot rather than a one-time setup claim. -- On Android, complete the DAVx5/Radicale and Tasks.org pilots described in the - discovery plan, including correct default accounts, recurrence, reminders, - and Microsoft To Do within its documented limits. -- Supply one real Yookr S3 task plus a least-privileged remote and authorize one - bounded upload. `rclone` is prepared, but no remote or wrapper is guessed. -- Deploy a GET image that exposes `/full-api-docs/openapi.json`, then rerun - `just openapi-refresh test`; the current test deployment returns 404. -- Provide current API user credentials for the ignored private HTTP environment, - then perform the planned login and harmless administrator read. The recovered - legacy service UUID/password returned 401 from production and was not copied - into the user-login schema. +- During a docked Zenith session, capture the external display's stable identity + and test the two-output `Super+A` exchange. The current recorded session exposed + only `eDP-1`. +- Resume Evolution only when the user chooses to configure the personal, + old-personal, university, work, and Radicale accounts. Keep Thunderbird and + Outlook until the daily-use gate passes. +- On Android, complete the DAVx5/Radicale and Tasks.org pilots, including default + accounts, recurrence, reminders, and Microsoft To Do within documented limits. +- Supply one real Yookr S3 task, a least-privileged remote, and explicit permission + for any upload. `rclone` is installed; no remote or wrapper is preconfigured. +- Deploy a GET image exposing `/full-api-docs/openapi.json`, then rerun + `just openapi-refresh test`. +- Provide current API user credentials before the planned login and harmless + administrator read. The recovered legacy service credentials returned 401 and + were not repurposed. +- Add workbench database connections and a Marimo-to-HTML report only when a real + cross-system investigation gives them durable value. +- Keep Bruno, DBeaver, and PIM clients until their separate pilots decide them. +- Wait for a real Monday ticket before adding anything beyond native GitHub, + Herdr, Git, and worktree workflows. ## Deferred Polaris replication -Do not access or test Polaris during this implementation. Treat a Zenith pass -as representative, but retain these machine-local steps for later execution on -Polaris: +Do not access or test Polaris unless the user resumes this deferred replication. +Zenith's result remains representative meanwhile. 1. Activate this NixOS configuration so `networkmanager-openvpn` is installed. -2. Run `chmod 600 ~/sync/yookr-vpn.ovpn`. -3. Run `nmcli connection import type openvpn file ~/sync/yookr-vpn.ovpn`. -4. Rename the imported `yookr-vpn` connection to `Yookr VPN`, disable - autoconnect, and add the required legacy cipher: - `nmcli connection modify yookr-vpn connection.id "Yookr VPN" connection.autoconnect no +vpn.data data-ciphers=AES-256-CBC`. +2. Restrict `~/sync/yookr-vpn.ovpn` to mode 0600. +3. Import it with `nmcli connection import type openvpn file ~/sync/yookr-vpn.ovpn`. +4. Rename the connection to `Yookr VPN`, disable autoconnect, and add + `data-ciphers=AES-256-CBC`. 5. Connect with `nmcli connection up "Yookr VPN"`. -6. Create `~/.config/yookr/github-read-only.token` without a trailing newline, - containing the fine-grained `yookrdev` token, and set its mode to 0600. Open - a new Zsh after activation and verify `gh project list --owner yookrdev`. -7. Create `~/.config/context7/api-key` without a trailing newline, containing a - current Context7 key, and set its mode to 0600. Restart Codex after activation - and run one Context7 documentation lookup. +6. Create mode-0600 `~/.config/yookr/github-read-only.token` without a trailing + newline, then verify a new shell can run + `gh project list --owner yookrdev`. +7. Create mode-0600 `~/.config/context7/api-key` without a trailing newline, + restart Codex after activation, and run one Context7 lookup. -## Decisions and discoveries +## Durable implementation decisions -- Use `/home/kiri/work/repos/yookr-dashboard-new` as the reconciliation base; - its `feat/gogv2` head is 17 commits newer than the sync copy and matches its - remote-tracking branch. -- The user confirmed the legacy BSON migration is no longer needed and the - former work copy is authoritative. The sync copy was explicitly deleted; - its removal may propagate through Syncthing. -- Active repositories now live under `/home/kiri/dev/work`; inactive material - is preserved intact under `.archive`, and the registered dashboard sensor - worktree is under `.worktrees`. -- Removed the obsolete dashboard converter app and Python dependency. The - dashboard flake evaluates, while the pre-existing feature work remains - uncommitted and unstaged. -- Pruned the absent `feat/echarts-domain` worktree metadata; its branch and - commit remain. The registered sensor worktree is valid at its new path. -- Shared Codex defaults and MCP endpoints are generated at - `/etc/codex/config.toml`. Context7's credential is supplied at connection time - by Codex's native HTTP-header helper reading `~/.config/context7/api-key`; no - secret enters Nix or the process-wide environment. The unused generic - `~/.config/mcp/mcp.json` is absent. -- Removed redundant legacy-path trust entries from the current Codex config; - the existing trusted home scope covers the moved repositories. -- Bruno's transitional OpenAPI reference now follows the archived management - repository until the HTTP pilot retires or retains Bruno. -- Post-move repository IDs, remotes, dirty counts, and the registered sensor - worktree match the pre-move inventory. The Nix flake still evaluates. -- No-link system builds for Zenith, Polaris, and Orion pass with the migrated - paths and declarative Codex defaults. -- Removed the unused stable Nixpkgs input and made Noctalia follow the root - package-set pin. Added `just` to the workstation tools and selected the - store-backed `podman-compose` provider through native `containers.conf`. -- The resulting flake check and no-link builds for all three hosts pass. -- The verified-empty legacy directories `~/work/.data/mongodb`, `~/work/.data`, - and `~/work/repos` are removed. The stale `~/.config/codex` tree was moved to - desktop Trash after explicit approval; the active `~/.codex` tree is intact. -- `Sync/uni/algorithms` remained on the Pixel after its Zenith and Orion copies - were deleted. Android Files and MTP crashed, while recursive ADB operations - appeared to hang because `.devenv/state/venv` contained at least one thousand - generated files and deeply nested package data behind Android's FUSE layer; - no malformed top-level entry or filesystem failure was found. With the - Syncthing-Fork folder paused and explicit approval, ADB atomically renamed and - then permanently deleted only that exact tree. The live and temporary paths - are absent. A later Files hang was isolated to the remaining stale `Sync` - tree, which included a repository with a very large `node_modules`; that - 1.7 GiB quarantined tree was permanently removed with explicit approval, and - both its visible and hidden paths are absent. The durable rule is that - repositories and worktrees never enter `~/sync`; the - shared folder therefore keeps only its intentional `**/.zk` ignore. -- `rules-service` now has a non-mutating direnv shell, minimal Nix toolchain and - formatting check, native Cargo recipes, factual repository instructions, - one `.http` request collection, explicit test/live deployment paths, and a - corrected test-oriented environment example. The full locked Cargo check - passes; Clippy reports 18 pre-existing advisory warnings. -- Private Cargo Git dependencies remain at their upstream HTTPS URLs and use - Git's existing `gh auth git-credential` integration. They are not vendored or - rewritten. -- `yookr-api` now owns its reviewed OpenAPI 3.1 contract, local/test/live HTTP - metadata, curated login/health/admin-read requests, a non-mutating shell, and - one explicit snapshot-and-diff recipe. `oasdiff` 1.31.0 is pinned with source - and vendor hashes because Nixpkgs does not provide it. -- The API flake evaluation, complete locked Rust test suite, and OpenAPI - validation pass. The fine-grained token reads the exact locked - `constant-store` commit and two other private Yookr crates through the - invocation-scoped `gh` credential helper; no dependency URL changed. The - validator retains two pre-existing informational example findings. The test - service is reachable, but its currently deployed GET image returns 404 for - the full OpenAPI route already present in local source and ingress - configuration. -- `traceability-data-model` preserves the existing domain/schema redesign while - replacing automatic schema recreation with explicit `just reset`. Ordinary - `just dev` retains its Compose volumes, and the flake check loads fixtures in - text-ID mode plus a fresh schema in UUID-ID mode. Both the built check and the - pinned `podman-compose` configuration validation pass. -- Mathesar is removed from `traceability-data-model`; the development stack now - owns only PostgreSQL plus the explicit schema-reset job. The active named - volume and its historical local role are preserved to avoid stranding data; - the obsolete `mathesar_django` database and orphaned secrets volume were - explicitly approved and deleted. `just reset`, `just dev`, the PostGIS - fixture query, Compose validation, and the flake check pass. -- `yookr-dashboard-new` now has the same minimal project contract: a - non-mutating flake shell, `just dev` over its existing Process Compose graph, - `just check`, factual `AGENTS.md` and README guidance, a flake formatter and - formatting check, and a complete public environment example. Its full type, - lint, and format checks pass; a bounded smoke test reached ready MongoDB and - Vite processes and stopped cleanly. The commit contains only contract files; - the user's existing dashboard source work remains unstaged and untouched. -- The active ignored `rules-service/.env` is moved unchanged to - `~/.config/yookr/rules-service.env`; its existing `.envrc` now loads that - file successfully. The dashboard environment is likewise moved unchanged to - `~/.config/yookr/yookr-dashboard-new.env`, and its `.envrc`, instructions, - and README now name that owner. Both files are mode 0600, both shells expose - their expected variables without recreating repository `.env` files, and the - dashboard commit contains only those three contract files. The unrelated - dashboard source work remains unstaged. -- The unreferenced `/home/kiri/work/SECRETS` file overlapped an older - rules-service environment but contained different credentials, so it was not - merged or discarded. It is preserved byte-for-byte as the mode-0600 - `~/.config/yookr/rules-service-legacy.env`; the original path is absent. Its - production Yookr service UUID/password returned 401 from `/serviceLogin`; no - token was retained and the credentials were not repurposed as a user login. -- Fresh local clones of `rules-service`, `yookr-api`, - `traceability-data-model`, `yookr-dashboard-new`, and `yookr-data-science` - pass their flake and project checks, expose their tracked `just` catalogs, - and remain clean after shell entry. Credential-bearing and externally blocked - checks remain separate gates. -- `yookr-data-science` now has a non-mutating shell and explicit `just setup`, - `check`, `run`, and `fetch-live` boundaries. Locked local checks and the - archived notebook use repository-relative paths; live Yookr access remains - explicit and was not invoked. Stale path-bound caches were replaced through - the explicit setup command, local credentials are mode 0600, and a fresh - clone passes the full contract without shell-created files. -- Gardenctl 2.14.0 and Gardenlogin 0.10.0 are pinned from their official release - assets; the hashes match the existing binaries byte-for-byte. All three host - builds pass, the OIDC kubectl plugin is declarative, and Zsh consumes the - native `gardenctl rc zsh` output with its completion packaged declaratively. -- The local-only `yookr-workbench` has no remote and initially owns only - `just k8s test`. Each invocation creates an isolated Garden session, rejects - every environment except test, and opens K9s read-only. Its flake check and - command catalog pass, and the live rejection path exits before Garden access. -- The user activated and rebooted into the Garden tooling, then confirmed that - `just k8s test` opens the test shoot in read-only K9s. The initial shell error - came from Garden writing completion into an absent Flatpak `$fpath` entry; the - initial target warning came from the fresh session inheriting its parent's - `KUBECONFIG`. Completion is now store-owned, and the child recipe initializes - Garden's session-local `KUBECONFIG` before targeting instead of unsetting it. - The former mutable Garden binaries and generic MCP output are already absent, - so their stale cleanup gate is closed without further user work. -- NetworkManager's native OpenVPN plugin is declared on Zenith and Polaris; - no profile is embedded in Nix or imported automatically. -- Zenith's initial NetworkManager activation never established a tunnel: - OpenVPN 2.6 preserved the profile's legacy `cipher=AES-256-CBC` setting but - excluded it from cipher negotiation. Adding `data-ciphers=AES-256-CBC` to - the imported connection fixes the negotiation; NetworkManager then installs - the pushed tunnel address, default route, private routes, and DNS servers. - Company database access, disconnect/reconnect, suspend, and reboot pass on - Zenith, so OpenVPN 3 is retired. -- For the entire workflow implementation, do not access or test Polaris. - Zenith results are accepted for both workstations; preserve exact deferred - Polaris replication steps instead. -- OpenVPN 3 is removed from the configuration. A full Zenith system build - passes, and the first password-authorized switch removed it from the live - generation. NetworkManager reconnected the VPN successfully afterward. -- Walker's native cached Lua menu scans the four identity roots and refreshes - when their top level changes. Its default action pipes the selected path to - one Nix-owned `open-project` command. Elephant supplies that path without a - terminating newline, so the launcher now accepts a successful partial read - instead of exiting under `errexit`. It opens Neovim through the configured - `xdg-terminal-exec` owner and leaves an interactive shell after Neovim exits. - Elephant reads menu actions only at startup, so rare declarative menu changes - take effect at the next login. The generated Walker configuration extends, - rather than copies or replaces, the pinned default provider list and prefixes. -- The shared terminal-launch failure came from the Kitty package rewrite: - unanchored `sed` changed both `Exec=kitty` and `TryExec=kitty` to include - `-1`. Arguments are invalid in `TryExec`, causing `xdg-terminal-exec` to - reject the configured terminal. The rewrite now changes only `Exec`; live - resolver output selects `kitty.desktop` and retains `kitty -1`, and Yazi - launches successfully from Walker. -- Restarting the pre-change Elephant and Walker processes loaded the corrected - project action. The user confirmed that repository launch works and exiting - Neovim leaves a usable shell in that repository. Passing the project root to - Neovim now opens the configured Oil default directory explorer immediately. -- A custom restart trigger for Elephant proved unsafe: Walker requires Elephant, - so activation stopped Walker with its backend but restarted only Elephant. - The trigger is removed; relogging is the deliberately simple refresh path - unless real usage proves that inconvenient. The user activated the removal, - relogged, and confirmed both Walker and the project-root Oil action work. -- Removed the decided Niri arrow/Page Up/Down/numbered duplicates, direct quit - and monitor-power bindings, extra screenshot modes, first/last and centering - actions, repeated resize families, and their four shortcut-only scripts. The - existing `Super+A` helper remains until a two-output session can validate its - bounded name-based replacement. -- Home Manager owns the existing Vivaldi launcher filenames for Actual and - WhatsApp at the higher-precedence user-data location, so both invoke Vivaldi - 8.1.4087.70 with the `Default` profile and stable app ID. Direct activation - replaced both generated files with managed links and retained the originals - as adjacent `.bak` files. The entries use native forced ownership because - Vivaldi may regenerate those exact paths. The installed Outlook PWA identified - itself as `eoficlgicibekocmfdomjbfnjmehnhcd`; its prepared managed launcher - preserves mailto handling and the calendar, compose, and new-event actions. -- The first `nixos-maintainer` skill is instruction-only, Nix-owned, and passes - the skill-creator validator. It encodes repository-specific ownership, - research, formatting, no-switch, and affected-host validation behavior - without duplicating generic agent guidance or adding scripts. -- Removed Zed, the inactive Helix modules and note integration, Helix's cache - settings and complete lock subgraph, and inactive Foot modules. Neovim and - Kitty remain the sole declared editor and terminal. A repository-wide search - found no configured npm caller, so global Node.js and Bun are also removed; - project shells retain language runtimes where needed. -- Postman, HTTPie, Posting, and Resterm are removed after their valuable request - definitions moved to tracked HTTP owners; Bruno remains through its own - pilot. Rainfrog, dblab, Harlequin and its profile, Tabularis, DbGate, pgAdmin, - MongoDB Compass, pgModeler, tbls, global `usql`, and global MariaDB are - removed; Dadbod and wrapper-local clients remain, while DBeaver stays through - its gate. Obsidian, Planify, and Calcure are removed after note and task - authority was established. Pinta replaces GIMP; the Evolution-era PIM clients - remain independently gated. -- `rclone` is installed as the only preparation for the selected S3 pilot. No - remote, wrapper, or interactive browser is added before a real task and - least-privileged credentials exist. -- GitHub remote state is absolutely read-only; local files, commits, fetches, - clones, and worktrees remain available. The generated global agent guidance - records this boundary. The former broad `jellespr` credential and stale, - invalid `kirigar` credential are removed locally. The only working API route - is now a fine-grained `github_pat_` token in the mode-0600 machine-local - `~/.config/yookr/github-read-only.token`. The Nix-owned `gh` package wrapper - reads it only when `GH_TOKEN` is otherwise absent, so `gh` and its Git - credential helper work inside Nix dev shells without copying the secret into - Nix, exporting it process-wide, or adding per-repository configuration. -- The fine-grained token passes identity, repository contents, issues, pull - requests, Actions, and organization Projects reads with no stored fallback. - The organization exposes 219 open issues, five open pull requests, and 60 - Projects. Project 52 is a 38-issue high-level overview; Project 8 is an - 82-item software backlog with the native ticket fields needed for the planned - workflow. Its only three items assigned to `jellespr` are Done, while the two - open issues assigned across the organization are not present there. Keep the - native `gh` and Git workflow and wait for the first real Monday ticket; add no - dashboard or wrapper before usage exposes a concrete gap. -- The user activated the initial GitHub token loader, whole-directory skill - owner, and durable remote boundary. A fresh shell contained the fine-grained - `GH_TOKEN`, `nixos-maintainer` resolved to a store-backed directory, the - generated global `AGENTS.md` contained the strict remote-state policy, and - the user confirmed `gh project list --owner yookrdev` succeeds. A cached - nix-direnv shell then proved it strips inherited `GH_TOKEN`; this concrete gap - prompted the invocation-scoped wrapper above. After activation, a cached dev - shell contains no `GH_TOKEN`, while `gh api user` identifies `jellespr` and - Git reads the private `constant-store` remote through the wrapper-backed - credential helper. -- The former Context7 declaration put a literal key in - `env_http_headers`, whose values Codex interprets as environment-variable - names; it therefore exposed the secret without supplying the documented - authentication. The static key is absent from the current worktree. Official - Codex configuration and Context7 documentation instead support the built - `http_headers_helper`, which emits the required `Authorization: Bearer` header. - The user replaced and revoked the historical key, the machine-local file is - mode 0600, and a restarted Codex session successfully resolves Nixpkgs and - queries its documentation through Context7. -- The PR gate used `prediction-api#125`: native `gh pr view`, `checks`, and - `diff` reads work, while an isolated exact-base reconstruction passes patch, - whitespace, and Rust formatting checks and leaves no durable checkout. The PR - has no CI, review, linked issue, or concurrency/retry regression test for its - deadlock change; private dependency and MySQL requirements prevented a safe - full test. Native GitHub and Git tooling suffice without another review UI. -- Five authorized ephemeral read-only Codex evaluations respected the no-switch - boundary and generally selected the right owners, but exposed that the live - `nixos-maintainer` file was absent from the model-visible skill catalog. Home - Manager now links the whole skill directory, the shape documented as supported - by Codex, rather than linking only `SKILL.md`. The flake and generated Home - Manager build pass, and an isolated Codex discovery probe returns - `DISCOVERED`; the live store-backed directory is now discoverable. -- Herdr reports the agent lifecycle correctly and preserves its pane and output - across detach and reattach. Its native defaults suffice; no configuration or - wrapper was added. -- In zsh, never use lowercase `path` as a loop or local variable: it is tied to - `PATH`. A diagnostic command temporarily hid normal executables by doing so; - the declarative session environment was correct and needed no patch. -- Pinta passed the bounded real-edit trial and is selected; GIMP is absent from - the active workstation package set. -- `sync/notes` is now the sole declarative notebook owner, with `daily`, - `meetings`, and `transcripts` directories, a native `zk daily` alias, and a - `Super+N` dedicated Kitty/Neovim launcher. The five unique authored work notes - were copied byte-for-byte, and both distinct todo lists were merged without - dropping an item. Syncthing returned to idle with no needed files, pull - errors, or new conflicts. The user confirmed the daily launcher and migrated - contents, then explicitly approved moving the intact `sync/work/notes` - rollback copy to desktop Trash. The pre-merge todo copies in `/tmp` may now - expire normally. -- The Fossify pilot produced a 10.28-second, mono 48 kHz AAC/M4A named - `20260906_004200.m4a`. Zenith's Syncthing log records its completed transfer at - 00:44:12, after the recording stopped, and the received file remains stable - and media-readable. Fossify will write future recordings directly into - `recordings/inbox`; this is a native one-time producer setting, not a manual - filing step. Orion now owns the CPU `whisper-cpp` consumer through a systemd - path unit. The scanner waits for five minutes of unchanged size, nanosecond - mtime, and inode, validates media duration, archives without clobbering, and - publishes an unlabeled transcript when diarization is unavailable. A copied - sample passed that complete CPU fallback path in an isolated sync tree. The - user deployed Orion with - `nixos-rebuild switch --target-host orion --flake ~/.config/nixos#orion`, - activated Zenith's desktop changes, and stopped Zenith's old timer before the - shared-inbox test. Moving only the observed sample into Orion's inbox triggered - the path unit; after the full settlement interval, roughly two minutes of CPU - inference produced a 563-byte fallback transcript and archived the original - at its unchanged 126741-byte size with no failure artifact. The results synced - back to Zenith, and both current generations match the simplified - configuration: Orion is the sole declarative transcription owner. The user - confirmed the sample's bilingual output. Zenith's already accepted 33 MB - pyannote model contained no credential files and was copied byte-for-byte to - Orion; a guarded isolated rerun then completed diarization and atomically - replaced the fallback transcript with the user's approval while preserving - the archived audio hash. All five temporary audio/transcript test directories - were removed from Zenith and Orion; the canonical synchronized archive and - transcript remain. -- Evolution 3.60.2 is composed through Nixpkgs with `evolution-ews` 3.60.2, - whose pinned source contains the separate native Microsoft 365 mail, - calendar, contacts, recurrence, and OAuth backends. Live inspection caught - that overriding `evolutionWithPlugins.plugins` replaced its default Evolution - package, and that a Home Manager package alone did not register the data - server on D-Bus. The pilot now uses NixOS's native `programs.evolution` - integration with the EWS plugin beside Thunderbird. The follow-up activation - succeeded, its source-registry service activates through D-Bus, no account is - preconfigured, and no authority is changed. The user deferred account setup; - Thunderbird and the Outlook PWA remain the working clients meanwhile. -- Named database connections and a Marimo-to-HTML report remain workbench - evaluation evidence, not scaffolding requirements. Add them only for a real - cross-system investigation that gives the queries and report durable value. -- Run independent pilots in parallel, but gate every retirement separately. +- Active work repositories live under `~/dev/work`, archives under `.archive`, + and worktrees under `.worktrees`. Repository trees never enter Syncthing. +- Participating repositories expose non-mutating direnv shells, minimal flake + checks, native commands through `just`, concise `AGENTS.md`, and machine-local + credential inputs. +- `yookr-workbench` starts with cross-system work only; isolation and additional + tools wait for observed needs. +- Walker's native Elephant menu scans repository roots and calls one Nix-owned + `open-project` command. Rare menu changes take effect at login. +- Kitty and Neovim are the sole declared terminal and editor. Retired duplicate + applications and global language runtimes stay absent unless a concrete owner + appears. +- The fine-grained GitHub token and Context7 key stay in mode-0600 machine-local + files and are exposed only to their callers. Secrets do not enter Nix or broad + process environments. +- `sync/notes` is the single notebook owner. Daily notes, meetings, and raw + transcripts have separate paths, and agents do not create issues or ingest + transcripts automatically. +- Orion is the sole transcription owner. It waits for file settlement, validates + media, archives originals, and publishes an unlabeled transcript when + diarization fails. +- Evolution remains a deferred presentation-layer pilot. Microsoft 365 and + Radicale remain the data authorities; Thunderbird and Outlook remain available. +- GitHub and every other repository remote stay read-only. Local files, commits, + fetches, clones, and worktrees remain available. + +## Next step + +Continue an external pilot only when its real input arrives. Otherwise no workflow +implementation action remains. diff --git a/docs/workflow-discovery.md b/docs/workflow-discovery.md index b6764b4..909dc76 100644 --- a/docs/workflow-discovery.md +++ b/docs/workflow-discovery.md @@ -1,818 +1,191 @@ # Workflow system blueprint -Status: authoritative design; discovery complete, implementation tracked in -[`plans/workflow-system.md`](plans/workflow-system.md) +Status: implemented. Current external gates and operational handoff live in +[`plans/workflow-system.md`](plans/workflow-system.md). -Last revised: 2026-09-05 - -This document is the source of truth for streamlining the workstation and the -surrounding Yookr workflow. It records the intended system, the changes needed -to reach it, the trials that must precede conditional decisions, and work that -is deliberately deferred. Historical exploration and rejected alternatives are -not retained unless they prevent a likely wrong turn. - -Remaining uncertainty is empirical rather than conceptual. Every unsettled -choice below has a bounded pilot, a pass condition, and a fallback; it is not an -invitation to resume general tool discovery. +This document is the durable design for the workstation and surrounding Yookr +workflow. Implementation history belongs in Git. ## Governing intent Build a quiet, keyboard-first control plane over development, investigation, -operations, agents, and personal knowledge. The goal is a small coherent set of -interfaces, not one application pretending to do every job. +operations, agents, and personal knowledge. Keep the system small and give each +intention one canonical owner. -The governing rules are: - -- Give each intention one canonical owner. A native CLI may remain as a - troubleshooting substrate without becoming a second documented workflow. -- Standardize vocabulary, entry points, and lifecycle boundaries. Do not force - unlike workloads onto the same implementation merely for visual uniformity. -- Put frequent actions on direct keys and make less frequent actions - discoverable through the surface that owns them. -- Keep stable packages, services, integration, and durable preferences under - Nix. Keep application databases, caches, sessions, trust, credentials, and - other mutable state with the application that owns them. -- Prefer supported application layers and native file formats. Add adapters or - wrappers only for a demonstrated gap. +- Standardize vocabulary and entry points without forcing unlike workloads onto + one implementation. +- Put frequent actions on direct keys and less frequent actions on their owning + discovery surface. +- Keep stable packages, services, integration, and durable preferences in Nix. + Keep credentials, sessions, databases, caches, and other mutable state with + their owning application. +- Prefer native file formats and supported application layers. Add wrappers only + for demonstrated gaps. - Repositories own reproducible development context, executable project - documentation, and artifacts specific to their code or schema. Global state - is reserved for genuinely cross-project or personal material. -- Select an environment per operation. Do not rely on a long-lived ambient + documentation, and code-specific artifacts. +- Select `local`, `test`, or `live` per operation; do not depend on an ambient production context. -- Keep destructive or production actions more deliberate than navigation and +- Make production and destructive actions more deliberate than navigation and read-only inspection. -- Promote automation only after the underlying manual workflow has repeated and - can be verified. A pilot is not yet a permanent abstraction. -- Prefer maintained free and open-source software. A paid tool is acceptable - only when it has an unusually strong, non-reproducible advantage. +- Promote automation only after the manual workflow repeats and can be verified. - Optimize conceptual complexity and review burden, not package count or line count in isolation. -The principal vocabulary is: +The main coordinates are scope (`work`, `own`, `ext`, or `forks`), Yookr domain, +environment (`local`, `test`, or `live`), and intention (develop, inspect, query, +review, operate, or deploy). -- scope: `work`, `own`, `ext`, or `forks`; -- Yookr domain: a service/product repository, data science, - `traceability-data-model`, or the cross-system `yookr-workbench`; -- environment: exactly `local`, `test`, or `live`; -- intention: develop, inspect, query, review, operate, or deploy; -- status: **selected**, **pilot**, **retire after migration**, or **deferred**. - -These coordinates compose. They are not stages in a mandatory linear process. - -### Explicit exception - -Credentials and `.env` files normally stay outside Git, the Nix store, and -broad synchronization. The existing secret-bearing `yookr-vpn.ovpn` is a -user-accepted exception: it may remain in Syncthing for use on Zenith and -Polaris. Restrict the source file to the user where Syncthing permits, never -print or commit its contents, and import it into each machine's root-owned -NetworkManager connection store. Do not generalize this exception. - -## Target ownership model +## Ownership model -| Intention | Canonical surface | Owner and status | -| --- | --- | --- | -| Find applications, projects, or open windows | Walker/Elephant | **Selected**; applications plus an Elephant Lua project menu | -| Search personal passwords globally | Walker with `rbw` | **Selected** for personal Vaultwarden; company Bitwarden remains in its browser extension | -| Navigate tabs, history, bookmarks, and browser commands | Vivaldi Quick Commands (`F2`) | **Selected**; Vivaldi remains the only bookmark authority | -| Arrange windows and displays | Niri | **Selected**; dynamic workspaces and no application-specific display roles | -| Work in a terminal | Kitty | **Selected**; no routine Kitty tiling or second terminal implementation | -| Edit related files and views | Neovim | **Selected**; buffers and temporary splits, without automatic session restoration | -| Enter a repository environment | `direnv` with `nix-direnv` | **Selected**; tracked `.envrc` and a default flake development shell | -| Discover non-obvious project actions | `just --list` | **Selected** for participating repositories; recipes delegate to native tools | -| Run actively edited first-party code | Native processes, optionally Process Compose | **Selected** for fast reload and debugger access | -| Run a container-only repository stack | Rootless Podman Compose | **Selected**; foreground, explicit, and scoped to the work session | -| Run a persistent containerized user service | Rootless Podman Quadlet | **Selected**; user systemd owns lifecycle and logs | -| Run a durable host service | NixOS module and systemd | **Selected** when a maintained native module fits | -| Exercise APIs | Kulala and `xh` | **Selected**; JetBrains-compatible `.http` files are the durable artifact | -| Query databases | Dadbod and native driver CLIs | **Selected**; named machine-local connections and repository-owned queries | -| Investigate across Yookr services | `yookr-workbench` | **Selected**, initially local to Zenith | -| Connect the Yookr VPN | Noctalia Network control center | **Pilot** NetworkManager/OpenVPN migration; OpenVPN 3 remains the fallback until both hosts pass | -| Inspect test Kubernetes | `just k8s test` to Gardenctl and `k9s` | **Selected**; isolated session and unchanged parent shell | -| Inspect or operate RabbitMQ | Official management UI | **Selected**; `rabbitmqadmin` is the terminal fallback | -| Browse S3-compatible storage | `rclone` | **Pilot**; explicit read/list/copy commands before interactive mutation surfaces | -| Capture and find notes | `zk` through Neovim | **Selected**; one synchronized Markdown notebook and one daily intake path | -| Present work/personal PIM on desktop | Evolution | **Pilot**; server authorities do not change during evaluation | -| Present personal PIM on Android | DAVx5, aCalendar+, Google Contacts, Tasks.org | **Pilot**; the server authorities remain unchanged | -| Give agents durable project context | `AGENTS.md` and project commands | **Selected**; concise facts, invariants, architecture pointers, and checks | -| Run top-level interactive agents | Herdr | **Pilot** for persistence, attention state, notifications, and worktrees | -| Maintain conversation state and internal subagents | Each native agent, primarily Codex | **Selected**; Herdr does not mirror internal subagents | -| Review local changes | LazyGit in the task checkout | **Selected**; Git remains branch/worktree truth | -| Review a pull request | `gh` for metadata/diff, then a review worktree when needed | **Pilot**; do not add another review application yet | +| Intention | Canonical owner | +| --- | --- | +| Find applications, projects, or windows | Walker/Elephant and direct Niri bindings | +| Search personal passwords | Walker with `rbw`; company Bitwarden stays in its browser extension | +| Navigate browser state | Vivaldi Quick Commands | +| Arrange windows and displays | Niri dynamic workspaces | +| Work in a terminal | Kitty | +| Edit related files and views | Neovim | +| Enter a repository environment | `direnv` with `nix-direnv` | +| Discover project actions | `just --list` | +| Run edited first-party code | Native processes, optionally Process Compose | +| Run a container-only project stack | Rootless Podman Compose | +| Run a persistent containerized user service | Rootless Podman Quadlet | +| Run a durable host service | NixOS modules and systemd | +| Exercise APIs | Kulala and `xh`, with tracked `.http` files | +| Query databases | Dadbod and native driver CLIs | +| Investigate across Yookr systems | `yookr-workbench` | +| Connect the Yookr VPN | NetworkManager/OpenVPN through Noctalia | +| Inspect test Kubernetes | `just k8s test` using Gardenctl and read-only K9s | +| Inspect RabbitMQ | Official management UI; `rabbitmqadmin` for exact terminal operations | +| Browse S3-compatible storage | Native `rclone` commands | +| Capture and find notes | `zk` through Neovim in one synchronized Markdown notebook | +| Present work and personal PIM | Evolution pilot beside Thunderbird and Outlook | +| Present personal PIM on Android | DAVx5, aCalendar+, Google Contacts, and Tasks.org | +| Give agents durable project context | `AGENTS.md` and project commands | +| Run top-level agents | Herdr on demand | +| Maintain conversation state and subagents | Each native agent, primarily Codex | +| Review local changes | LazyGit in the task checkout | +| Review pull requests | `gh`, then a review worktree when local checks are useful | -## Change program - -The phases below are dependency ordered. Each change appears once; trial details -live in the evaluation section rather than being restated throughout the plan. - -### Phase 1: reconcile state and ownership - -#### Repositories and paths - -- Before moving any repository, inspect its dirty state, linked worktrees, - destination collisions, and authored path consumers. Move registered - worktrees with `git worktree move` where possible and run - `git worktree repair` after changing a repository root. Do not apply a broad - filesystem move to unchecked repositories. -- Reconcile the divergent dirty copies of `yookr-dashboard-new` under `~/sync` - and `~/work/repos` before moving or deleting either. Establish the - authoritative commit and preserve unique tracked, untracked, ignored, local - data, and secret inputs deliberately. A general archive operation must not - choose between these copies implicitly. -- Consolidate source repositories under the existing XDG projects root: - `~/dev/work/`. Do not keep the accidental `~/work` root, the - intermediate `repos/` directory, or a permanent compatibility symlink. -- Keep these work repositories active: `aggregation-api`, `prediction-api`, - `rules-service`, `yookr-api`, `yookr-dashboard`, `yookr-dashboard-new`, - `yookr-data-science`, `yookr-management-svelte-redo`, and - `traceability-data-model` (currently `schemas`). Add `yookr-workbench` when - its pilot begins. -- Rename `schemas` to `traceability-data-model` during the move. Update authored - path consumers; generated Codex, shell, and Herdr histories may remain - historical. -- Move every other repository from the current work inventory intact to - `~/dev/work/.archive`. This includes `yookr-management-new`, - `yookr-management-svelte`, `yookr-dashboard-new.old`, `pipelines.old`, - `yookr-dashboard-new.zip`, `rules-service-hex`, - `yookr-data-science-no-kedro`, and tutorial repositories. Do not infer active - status from a product-looking name. -- Archive the small `~/work/rabbitmq` scratch project. Remove the empty legacy - `.data/mongodb` directory only after a final emptiness check; do not move the - global `.data` directory into the source tree. -- Make Git identity rules explicit and non-overlapping: work identity below - `~/dev/work`, personal identity below `~/dev/own`, `~/dev/ext`, and - `~/dev/forks`. Update the old `~/work` conditional include. -- Treat work/personal labels as identity and navigation boundaries, not privacy - boundaries. Non-secret work material may synchronize to the Pixel and Orion; - source checkouts, credentials, and runtime state do not belong in the broad - `~/sync` tree. -- Remove stale path-bound Codex trust and obsolete approval entries after the - move. Update the transitional Bruno workspace path only if Bruno still needs - it during migration. - -#### Nix and mutable application state - -- Preserve the existing host/profile/capability structure, typed facts, - flake-parts composition, Home Manager integration, and primary-tool - indirection. No repository-wide architecture migration is justified. -- Keep flakes and `nh os switch` as the normal activation path. Keep Neovim's - Lua, wrapper, plugins, language servers, and formatters store-backed; accept - the measured rebuild cost rather than adding an out-of-store fast path. -- Do not prototype Hjem/Rum, replace flakes with `npins`, or add a separate Home - Manager activation wrapper. These do not address the accepted ownership - model. -- Remove the unused `nixpkgs-stable` input and its dead local binding. Let - Noctalia follow the root Nixpkgs input if the pinned configuration evaluates - unchanged; describe this as dependency-graph hygiene, not a latency fix. -- Retire or rewrite the stale root `PLAN.md` after checking whether it contains - unfinished Neovim work. It must not continue to claim that Helix is active. -- Keep Flatpak enabled as an intentionally available installation facility, but - add no declarative Flatpak manager, remotes, or applications while it has no - consumers. -- Continue declaring Noctalia's stable policy through Nix. When behavior differs - from the declaration, inspect its writable XDG state as a second supported - application layer; keep only genuinely local runtime state there rather than - adding another synchronization mechanism. - -#### Codex configuration ownership - -- Rotate the exposed Context7/MCP credential and replace plaintext headers with - an environment-backed header or supported credential mechanism. -- Generate stable Codex defaults at `/etc/codex/config.toml`: MCP endpoints, - approval defaults, notifications when Codex owns them, and other durable - cross-machine preferences. Official precedence makes this a lower-precedence - default layer. -- Leave `~/.codex/config.toml` writable and machine-local for project trust and - unavoidable local settings. Review non-trust keys occasionally: promote a - deliberate cross-machine preference to Nix, retain generated/machine state, - and remove accidental overrides. Do not add a drift daemon. -- Keep global `AGENTS.md`, intentional rules, and user-authored skills under - Nix/Home Manager. Keep authentication, conversations, databases, caches, - downloaded plugins/skills, and generated Herdr integrations untracked. -- Remove the stale `~/.config/codex` tree; its preservation audit found no unique - authored material worth migrating. - -### Phase 2: establish the project contract and Yookr workbench - -#### Common repository contract - -Participating repositories expose the same concepts without copying an empty -directory taxonomy: - -- A tracked `.envrc` enters the default flake development shell and selects only - non-secret local defaults. Activation must not perform `uv sync`, start - services, fetch data, or do other mutating/network work. -- `flake.nix` owns reproducible dependencies, packages, checks, and the - formatter. A flake app remains only when it is an actual runnable or - distributable program. -- `just --list` is the documented index of non-obvious human and agent tasks. - Recipes delegate to native commands and omit trivial one-off operations; the - native CLI remains available for diagnosis without becoming a second catalog. -- Install `just` as a workstation bootstrap tool and include it in participating - development shells so the repository remains portable. -- `AGENTS.md` records architecture pointers, invariants, project commands, and - required checks. It stays factual and concise; it does not duplicate generic - global behavior. -- Service-owned `.http` examples and schema-owned `.sql` files live beside the - code or schema they describe. Generated views, caches, and scratch results are - not durable sources. -- Environment selection is per operation. Kulala selects a buffer environment, - Dadbod selects a visibly named connection, and recipes accept an explicit - argument such as `just k8s test`. -- Secrets enter at runtime from the service's approved source or a user-only - machine file outside all checkouts, initially - `~/.config/yookr/.env`. A tracked `.envrc` may load that stable - path with `dotenv_if_exists`. -- A new worktree behaves like a fresh clone. Recreate dependencies, caches, - generated files, and build output; do not copy ignored state from a main - checkout. Require an explicit `direnv allow` for each worktree. -- Do not pre-allocate branch-derived ports, databases, Compose projects, or - container names. Add targeted isolation only after a real task exposes a - collision. - -First prove this contract in `rules-service`, paired with the API workflow in -`yookr-api` and the database workflow in `traceability-data-model`. Only after -two repositories reveal genuinely repeated files should a small native Nix -flake-template repository be extracted. Language-native initializers continue -to own language source layout. - -Repository-specific corrections are part of the pilots: - -- In `rules-service`, add a README, flake, `.envrc`, `justfile`, and concise - `AGENTS.md`; document the local run path; correct the obsolete environment - example; use `test`, not `dev`, for the deployed test environment. Do not - encode the transitional rollout as a durable `deploy` recipe. -- In `traceability-data-model`, rename the repository and make destructive - schema recreation an explicitly named reset action rather than the ordinary - `dev` path. -- In `yookr-data-science`, default ordinary fetching away from live, remove - `uv sync` from shell activation, keep the deployable soil-moisture pipeline, - and move only generic cross-system exploration to the workbench. -- In `yookr-dashboard-new`, preserve the useful visible Process Compose - lifecycle. Expose it through `just dev` when the project adopts the common - contract instead of maintaining a workflow-only flake app in parallel. - -#### Workload lifecycle - -- Run actively edited first-party code natively. -- Use Process Compose only for two or more native or mixed foreground processes - that need one visible lifecycle, dependency graph, and log view. -- Use rootless Podman Compose for a wholly containerized repository stack. Keep - support stacks manual, foreground, and scoped to a work session; preserve - named volumes between runs and make running state visible to Podman tools. -- Use Quadlet for persistent containerized user services and NixOS modules for - durable host services when a good module exists. Do not nest lifecycle tools - merely to obtain another status screen. -- Configure Podman's external Compose provider deterministically. Do not use - Compose dotenv behavior as the secret contract. - -#### `yookr-workbench` - -Create a minimal `yookr-workbench` on Zenith before adding workbench-owned API, -database, or Kubernetes commands. Keep it without a remote while proving its -contents. It owns only cross-system work: - -- curated cross-service `.http` requests; -- operational queries and MongoDB scripts that do not belong to one service; -- explicit local/test investigation commands; -- the Garden/Kubernetes entry point; -- uv-managed Marimo analyses, disposable experiments, and report exports. - -It does not own service OpenAPI specifications, schema migrations, deployable -model code, general bookmarks, RabbitMQ URLs, or copied application state. -Scratch work is promoted to its nearest durable owner or deleted. Unrelated -personal Dadbod scratch stays under XDG data. - -Use Marimo's Python files as the analysis source. Ignore raw/local data and -scratch output. Share source notebooks with technical readers, self-contained -HTML as the faithful general report, browser-produced PDF when the delivery -channel favors it, and individual SVG/PNG files only when a figure is itself the -deliverable. Do not add a notebook hub or report server. - -Production data may be used on a developer machine; this streamline adds no -separate anonymization or local-retention policy. Raw datasets and -secret-bearing output still remain outside Git and broad synchronization. - -#### API and database workflow - -- Make Kulala and `xh` the editor/CLI pair. Keep hand-curated `.http` files as - durable examples; use `kulala-fmt` OpenAPI conversion only to seed a migration - or produce a disposable view. -- Commit non-secret `local`, `test`, and `live` metadata in - `http-client.env.json`. Keep credentials in ignored - `http-client.private.env.json` or runtime environment variables. Make - `local` the default environment; keep test/live selection explicit and - visible per buffer. Kubernetes being test-only does not remove explicit live - HTTP access. -- Use Yookr API as the first proof. Support the sibling service-owned - `openapi.yaml` and ignored snapshots fetched from test/live - `/full-api-docs/openapi.json`. Authenticate through ordinary login and run a - harmless administrator-authorized read. -- Compare local and network specifications semantically with `oasdiff`. A - network refresh writes and validates an ignored cache; it never overwrites a - checked-in contract automatically. Do not add timestamped archives or polling. -- Add `just openapi` to a service only when that repository has a reproducible - generation command. A service repository remains authoritative for its - specification; the workbench does not collect copied specs. -- Use Dadbod UI's machine-local named connection store, normally - `~/.local/share/db_ui/connections.json`, instead of the current single - `DATABASE_URL` assumption. Name entries - `--`, for example - `test-rules-mysql`, `test-prediction-mysql`, and - `local-traceability-postgres`. -- Support PostgreSQL, MySQL/MariaDB, and MongoDB while the migration requires all - three. Keep relational artifacts as `.sql` and MongoDB artifacts in their - native JavaScript format. Durable queries belong in repositories; DBUI saved - queries are personal scratch. -- Prefer read-only credentials for workbench inspection where available. - Dadbod is not itself a safety boundary. Intentional schema or data mutations - belong in an owning repository or an explicitly reviewed operation. -- Populate connection URLs manually. Do not extract Kubernetes Secrets or build - a Bitwarden-to-Dadbod translation layer before manual connection management - demonstrates a real cost. - -#### Kubernetes, RabbitMQ, S3, and SharePoint - -- Install `networkmanager-openvpn` on Polaris and Zenith, import the accepted - VPN profile separately as a root-owned connection with autoconnect disabled, - give it the same visible name on both machines, and use Noctalia's built-in - Network control center. Retain OpenVPN 3 until the migration gate passes on - both hosts. -- Package official `gardenctl` and `gardenlogin` releases through Nix, use the - nixpkgs OIDC plugin, remove duplicate binaries from `~/.garden` and - `~/.local/bin`, and replace handwritten Zsh initialization with - `gardenctl rc zsh`. Keep Garden configuration and kubeconfigs mutable and - private outside the store. -- Implement `just k8s test` in the workbench. It creates a fresh - `GCTL_SESSION_ID`, targets Garden `leafcloud-test`, project `ryxtdg8ydl`, shoot - `test`, derives `KUBECONFIG`, and opens `k9s` in the foreground. The parent - shell remains unchanged. Use explicit `kubectl --namespace` for exceptional - terminal work; do not guess a namespace. -- Do not expose live Kubernetes or a deploy command. Image construction and GHCR - publication remain in GitHub Actions while the team release process is - unresolved. -- Use the official RabbitMQ management UI for inspection, publishing/getting, - and purge. Put `https://test.rabbitmq.yookr.org` and - `https://rabbitmq.yookr.org` in a Vivaldi operations bookmark folder with - explicit test/live labels rather than duplicating them as workbench recipes or - Walker actions. Keep `rabbitmqadmin` for exact terminal operations; purge - remains visibly interactive. -- Pilot one machine-local `rclone` S3 remote using the least-privileged available - credentials. Begin with `lsd`, `lsjson`, `tree`, and explicit read/copy - operations. Do not expose `rclone ncdu` initially because its interface also - deletes and has no read-only mode. -- Reuse `rclone`'s OneDrive backend for a later SharePoint browse/download pilot - before considering a second SharePoint tool. Do not introduce continuous sync - or a mount until the real access pattern and company consent are known. - -#### Pull-request review - -- Use `gh pr view` for metadata/checks and `gh pr diff` for a quick read-only - review. -- Create a dedicated review worktree when local navigation, tests, or agent - assistance are needed, then use LazyGit in that checkout. -- Add no PR dashboard or wrapper until repeated reviews expose a specific gap. - -### Phase 3: simplify global navigation and desktop interaction - -#### Walker and project opening - -- Keep Walker/Elephant as the global discovery surface and Niri direct bindings - for the most frequent intentions. Keep `z ` as the shell fast path; - it is another invocation surface over the filesystem, not another catalog. -- Use Elephant's native dynamic Lua menu support as `menus:projects`. Scan Git - roots across `~/dev/work`, `~/dev/own`, `~/dev/ext`, and `~/dev/forks`; exclude - `.archive` and every worktree root. Show one flat result set with scope labels. -- Have each result invoke one Nix-owned `open-project` command. It opens an - interactive Kitty shell at the repository root, starts Neovim, and returns to - a usable prompt after Neovim exits. It restores no buffers, layouts, or - services. -- Treat `yookr-workbench` as an ordinary project result. Do not generate a - desktop entry per repository or maintain a separate project registry. -- Retain Elephant's shipped provider defaults/prefixes instead of copying them - into Nix. Keep the current `?` Bitwarden prefix as the local exception. - Clipboard and credentials remain prefixed; quiet navigation providers may - participate in unprefixed search. -- Do not include bookmarks in Walker until a maintained provider can read - Vivaldi live. Elephant's one-time CSV import would create a second authority. - -#### Browser ownership - -- Keep one neutral Vivaldi profile. Work/personal cookie and history isolation - has no demonstrated value; rename the misleading `Work` profile if visible. -- Use Vivaldi Quick Commands for tabs, history, bookmarks, and browser commands. - Keep vertical tabs. Do not initially add Vivaldi workspaces, pinned tabs, Web - Panels, command chains, or another Chromium browser. -- Keep Actual, WhatsApp, and Outlook as web apps. Vivaldi owns installation, - identity, authentication, permissions, and sync. After capturing each app ID, - Home Manager owns one stable desktop entry invoking current Vivaldi with - `--profile-directory=Default --app-id=`; suppress the duplicate - Vivaldi-generated launcher. Reuse the known Actual ID - `ahoihnifhakgijcmjlhbcgehigihinlo` and WhatsApp ID - `hnpfjngllnobngcgfapefoaidbinmjnm`; capture Outlook's ID after installing it. - This prevents stale Nix-store paths. -- Keep a small Speed Dial beginning with ChatGPT and the Yookr GitHub - organization. Google remains the search engine rather than a bookmark. -- Sync durable bookmarks/Speed Dials, supported settings, extensions, and web - apps between Zenith and Polaris. Keep open tabs and history local. Passwords - remain Bitwarden-owned. -- Use Bitwarden's multi-account browser extension for personal Vaultwarden and - company Bitwarden. Walker's `rbw` provider remains personal-only; do not copy - company secrets, create another browser profile, or build a private adapter. - -#### Niri vocabulary - -- Design for two general-purpose displays with dynamic workspaces. Do not pin - applications, roles, or content to a monitor. On Zenith, place the external - widescreen to the right and focus it initially once its stable identity has - been captured during a docked session. -- Keep `Super+H/J/K/L` for focus and `Super+Alt+H/J/K/L` for frequent - window/column movement. Keep `Super+U/I` for workspace navigation, the - corresponding deliberate move/reorder operations, useful mouse-wheel - bindings, and touchpad gestures. -- Remove arrow-key duplicates, Page Up/Down aliases, numbered-workspace - bindings, first/last-column jumps, centering, maximize-to-edges, direct - compositor quit, monitor power-off, and the unused hotkey-overlay binding. -- Remove the NixOS-config terminal, NixOS-switch, edit-secrets, and color-picker - shortcuts and their shortcut-only scripts. Underlying terminal commands stay. -- Consolidate screenshots on Niri's interactive `Super+S` UI. Keep - `Super+O` overview, `Super+Q` close, and `Super+Ctrl+L` lock. -- Keep `Super+R` preset width cycling, one incremental resize family, column - maximize, and fullscreen. Retain stacking/consume-expel actions, tabbed - columns, toggle floating, and focus switching between floating and tiling as - secondary capabilities. -- Replace the fragile mutable-index `Super+A` helper only through the bounded - pilot below. Niri 26.04 actions accept a workspace index or name, not stable - IPC workspace IDs, and have no atomic active-workspace swap. - -### Phase 4: make the agent workflow coherent - -#### Context, skills, and client boundaries - -- Keep Codex primary and Claude Code/Pi as experimental fallbacks without fixed - specializations or forced feature parity. -- Share repository `AGENTS.md`, concise global guidance, project commands, - user-authored skills where natively supported, and verified MCP endpoint - facts. Keep client-specific model/UI settings, approvals, authentication, - trust, and runtime state native. -- Centralize MCP endpoint facts in the Nix AI capability only after Codex and - Claude both consume them, then render thin native configurations. Pi has no - native MCP owner and should not receive a wrapper for cosmetic parity. Remove - generic Home Manager `programs.mcp` output if it remains unused. -- Do not create a prompt-template menu or demonstration skills. Promote a - procedure only after it has stable inputs, outputs, and verification. -- Create the instruction-only `nixos-maintainer` skill as the first demonstrated - candidate. It inspects repository instructions and the complete current - owner, prefers native options and existing patterns, researches - version-sensitive choices, keeps mutable state outside the store, makes - focused idiomatic edits, formats them, and performs the smallest meaningful - no-switch validation for affected hosts. Switching a generation requires a - separate explicit request. Add scripts only after a deterministic repeated - operation appears. - -#### Top-level agents and worktrees - -- Pilot Herdr as owner of top-level Codex, Claude, and Pi processes, cross-agent - attention state, detach/reattach, desktop notifications, and the normal - create/open/list/remove worktree flow. -- Start or attach Herdr on demand. Do not add a supervised login service: the - client already starts a persistent background session. Stop it deliberately - with `herdr server stop`. Keep ordinary terminals, Neovim, dev processes, and - LazyGit outside Herdr. -- Configure Herdr as the sole notification owner during the pilot. If it fails, - use Codex's built-in TUI notifications before an external notify command. -- Use one workspace per active checkout or cross-repository investigation and - one pane per top-level agent. Internal Codex subagents remain in Codex. -- Let Herdr use Git worktrees as its substrate. For the first work-only pilot, - place them under `~/dev/work/.worktrees//` and exclude - that tree from Walker. Use an explicit path for other identity scopes until a - scope-preserving convention is proven. -- Do not install Worktrunk initially. Herdr 0.8.2 covers the proposed basic - lifecycle and leaves branches intact for human review. Revisit Worktrunk only - for demonstrated needs such as richer cross-worktree state, shell switching, - CI/PR views, process cleanup, or integration-aware branch removal. Keep native - `git worktree list`, `repair`, and `prune` as maintenance escape hatches. -- Defer `codex agents`. Codex 0.153.4 exposes it, but local diagnostics show - ephemeral app-server mode and daemon bootstrap that assumes a standalone - managed install rather than the Nix-store binary. Do not add a second mutable - Codex installation or custom daemon workaround. - -#### Task lifecycle - -- Use one primary writer per checkout. It may delegate bounded independent - read-heavy work; concurrently running internal subagents do not edit the same - checkout. Confirmed read-only investigations may share a checkout and need no - branch or worktree. Sequence tasks expected to overlap in files or behavior. -- Use `feat/-`, `fix/-`, or - `chore/-`, omitting the issue when none exists. Reuse the slug for - the checkout, Herdr label, and optional execution plan. -- Keep setup explicit: create the worktree, review and authorize `.envrc`, then - start the agent there. Do not add hooks, copy ignored files, launch agents from - Walker, or hide these state changes in a wrapper. -- Use Codex `/plan` when design is uncertain, then one `/goal` containing the - outcome, constraints, and verification for substantial execution. A goal is a - persistent target, not a milestone hierarchy. -- For genuinely substantial multi-session work, keep one living - `docs/plans/.md` in the branch and reference it from the goal. Record - scope/non-goals, architectural facts, verifiable milestones, progress, - discoveries, decisions, evidence, and the exact next step. Link to source - instead of copying logs. Move lasting knowledge to normal docs/ADR at the end - and delete a transient plan when it has no continuing value. -- Let the primary agent make local commits at meaningful verified or resumption - boundaries, not mechanically after every milestone. It does not push, merge, - open a pull request, or mutate GitHub without an explicit request. -- End on a checked, locally committed branch with a handoff covering outcome, - verification, limitations, and unresolved decisions. Review with LazyGit in - the task worktree. The user chooses publication, integration, revision, or - abandonment; remove the checkout only afterward. - -### Phase 5: consolidate notes, PIM, and transcription - -#### Notes and meeting knowledge - -- Consolidate both notebooks into `~/sync/notes` on one fully synchronized node - with no concurrent writers. Preserve authored notes/images and merge the two - `todo.md` files deliberately. Stop generating `~/sync/work/notes`. -- Use `daily/YYYY-MM-DD.md` as the unclassified intake path, with no title prompt - or required frontmatter. Use a `zk` group/alias for idempotent daily-note - creation. Keep ordinary titled notes at the notebook root until use justifies - another collection. -- Bind unused `Mod+N` to a small Nix-owned command that opens today's note at the - append position in a dedicated Kitty/Neovim window and closes with Neovim. -- Store planned meetings as dated files under `meetings/` and raw transcripts - separately under `transcripts/`. A person curates decisions/context into the - meeting note. During the Monday development meeting, the team cooperatively - decides and creates issues, which the meeting note then links. Agents do not - create issues or automatically ingest transcripts into daily notes. -- Keep Markdown/zk as notes authority. Personal actionable tasks belong in - Radicale VTODO, private/email-derived work follow-ups in Microsoft To Do, and - shared development work in GitHub issues. Link Markdown context instead of - duplicating it into another task database. - -#### PIM authority and presentation - -- The PIM foundation must remain free of charge, with open-source components - preferred. Paid services such as Morgen are excluded from this role. -- Keep Microsoft 365 authoritative for work/university mail, calendar, people, - and Microsoft To Do. Keep Radicale authoritative for personal CalDAV - calendars/VTODO and CardDAV contacts. Clients may present both but do not copy - data between authorities or create a bidirectional bridge. -- Pilot Evolution with all four active mail identities: personal, old personal, - university, and work. Use its Microsoft 365 account type for Microsoft - identities, not legacy Exchange/EWS. Connect personal accounts and Radicale - through native IMAP/SMTP, CalDAV, CardDAV, and VTODO. -- Keep Thunderbird and the Outlook Vivaldi app throughout the pilot. Change no - authority until consent and daily behavior pass. If Evolution passes its - gate, make it the canonical desktop PIM and retire Thunderbird. Keep Outlook - as the Microsoft-native fallback, not a second general PIM workflow. -- Keep Radicale rather than adding Baikal or Nextcloud. A larger groupware stack - does not solve Microsoft federation and no personal web UI is required. -- On Android, use DAVx5 as the sole Radicale adapter. aCalendar+ and Google - Contacts consume Android providers; Tasks.org consumes DAVx5/OpenTasks and may - separately connect to Microsoft To Do. Do not configure Radicale twice. -- Use free/F-Droid DAVx5 and Tasks.org builds. Account for Tasks.org's Microsoft - To Do limits--partial recurrence, one reminder, no attachments, and no manual - ordering--and give each collection one notification owner. Test Outlook's - optional calendar/contact export and correct default accounts. -- Before removing the last independent PIM copy, test restoration of Radicale - collections and authentication data. Keep broader Orion backup redesign out - of scope. - -#### Meeting transcription - -- Use Fossify Voice Recorder on the Pixel and Syncthing directly to Orion. Orion - becomes the always-on transcription owner; this does not reopen general - backup, CI, builder, or agent-offload design. -- First observe a real recording transfer. Fossify writes directly to its final - filename while recording, so a Syncthing/path event alone does not prove the - meeting ended. Prefer an automatic post-record move to a dedicated inbox if - available; otherwise require an unchanged size/mtime settlement period plus - media readability/duration validation. -- Once completion detection is proven, refactor the Zenith/Vulkan module for - Orion's CPU `whisper-cpp` runtime and one inbox/processing/archive/failed tree. - Use an event-triggered path/service pair to wake an idempotent scanner instead - of polling every two minutes. -- Keep the multilingual model and automatic language handling. Run diarization - by default, but publish an unlabeled transcript when Whisper succeeds and - diarization fails; record the partial failure instead of withholding it. -- Write transcripts under `~/sync/notes/transcripts` and preserve originals in - the recordings archive. The transcript is the quiet success signal; - systemd state/logs expose failures. Add no desktop notification. -- Validate with a Dutch/English meeting, preferably with code switching, and - require completion by the next morning. Consider a GPU worker only if Orion - misses that service level. Syncthing replication is not backup. - -### Phase 6: retire duplicate applications and dead configuration - -Perform removals after the corresponding migration or pilot. - -- After valuable definitions are migrated, remove Postman, HTTPie, Posting, and - Resterm. Keep Kulala and `xh`; keep Bruno only through the HTTP pilot and - remove it if no indispensable capability appears. -- Remove Rainfrog, dblab, Harlequin, Tabularis, DbGate, pgAdmin, Beekeeper - Studio, MongoDB Compass, pgModeler, and tbls. Remove the Harlequin profile. - Keep Dadbod and wrapper-local clients; keep DBeaver only through the database - pilot and remove it if no GUI-only need emerges. -- Remove global `usql` and MariaDB. Keep PostgreSQL, MariaDB, and `mongosh` only - in the Neovim wrapper or repository environments that own them. -- Search once for an application/plugin that shells out to npm. If found, - declare Node with that owner; otherwise remove global Node.js and Bun and keep - language runtimes in repository flakes. -- Remove Zed and the inactive Helix module, cache settings, and flake input. - Keep Neovim. Retain Jujutsu as a **deferred** experiment with no assigned - workflow until a concrete use case exists. -- Remove inactive Foot modules and keep Kitty as the only terminal. -- Remove Obsidian, Planify, and Calcure after note/PIM ownership is in place; - none owns data that needs migration. -- If Evolution passes, remove Khal, Todoman, and local `pimsync`. Keep Noctalia - as a read-oriented calendar glance surface. Add no terminal PIM client without - a demonstrated operation. -- Install Pinta beside GIMP for its bounded trial. Remove GIMP only if Pinta - covers the workload; otherwise remove Pinta and retain GIMP. -- Retain Spotify, OnlyOffice, Nautilus, imv, Sioyek, mpv, qBittorrent, and - Discord. Keep Nautilus as graphical file manager/directory default and - `xdg-desktop-portal-gnome` with GTK fallback as file chooser. Do not add - Thunar, Dolphin, Nemo, or another file manager without a concrete gap. - -## Evaluation gates - -### Project contract and workbench - -Pass when a fresh checkout of each pilot repository can enter its environment, -discover relevant commands, run checks, and reach a useful local state using -only tracked configuration plus documented machine-local inputs. Confirm no -shell activation mutates state and a new worktree reads no ignored files from -another checkout. Extract templates only after two pilots share real boilerplate. - -Run two representative development servers and test suites concurrently using -their native behavior. Continue deferring branch-derived ports, databases, and -container-project names only if endpoints remain discoverable and no actual -resource collision appears; otherwise add isolation only for the observed -conflict. - -For the workbench, prove one authenticated Yookr API read, one named connection -for every needed database engine, isolated `just k8s test`, and one -Marimo-to-HTML report. Publish or expand it only after those artifacts confirm -the boundary. - -### Herdr and worktrees - -Exercise short and long Codex sessions, an approval-blocked session, -detach/reattach, one Claude or Pi fallback, and two independently writing task -worktrees. Evaluate runtime/attention, notifications, and worktree handling as -separate components, retaining only those that pass. Attention state must be -accurate, exactly one notification must be delivered, paths must be predictable, -dirty removal must be refused, branches must remain for review, and layout -overhead must cost less than the polling/navigation it replaces. If Herdr's -runtime or notification role fails, retain native agent sessions and Codex TUI -notifications. If its worktree role fails, use native -`git worktree add/list/remove`; compare Worktrunk only after that ceremony -becomes repeated friction. - -### Agent conventions - -Test the instruction-only `nixos-maintainer` skill with representative prompts -that add, change, remove, and diagnose a capability, plus unrelated prompts that -must not trigger it. Pass when it follows repository ownership, makes focused -edits, performs appropriate no-switch validation, and leaves switching to an -explicit request. - -Separately, give a fresh Codex session only the repository, its `AGENTS.md`, and -a living execution plan from a substantial paused task. Promote the convention -only if it can identify the exact next step and resume correctly without chat -history or copied logs. - -### S3 and SharePoint - -On one real Yookr task, configure a machine-local, least-privileged S3 remote and -prove target selection, listing, download, and an explicitly authorized upload -without accidental mutation. Pass when native `rclone` commands are clear and -auditable enough that no interactive browser is needed; otherwise remove the -pilot configuration and package. Keep SharePoint deferred until company consent -and a real browse/download case exist. - -### Pull-request review gate - -Exercise one `gh pr view`/`gh pr diff` review and one review worktree with local -navigation or tests. Pass when metadata, checks, local verification, LazyGit, -and optional agent assistance are reachable without another review UI. If it -fails, record the exact missing operation before evaluating one maintained -alternative. - -### Evolution and Android PIM - -Pass Evolution only after all four accounts prove send/receive, folder behavior, -search, offline mail, invitations, recurrence exceptions, contacts/GAL, -Microsoft To Do recurrence/reminders, and acceptable daily UX. Confirm tenant -consent and one notification owner per collection. If it fails, retain -Thunderbird plus Outlook PWA and keep Radicale clients native. - -On Android, prove two-way Radicale calendar/contact/task synchronization, -Microsoft To Do within documented limits, clear collection labels, correct -default accounts, recurrence, reminders, and Outlook export where permitted. -If DAVx5 fails, keep personal Radicale data in the existing desktop/native -clients and Outlook as the Microsoft 365 Android client. If only Tasks.org's -Microsoft integration fails, keep Tasks.org for personal VTODO through DAVx5 -and use Outlook for Microsoft tasks. Neither failure changes server authority. - -### NetworkManager VPN - -Install `networkmanager-openvpn`, import the profile separately on Polaris and -Zenith with autoconnect disabled, and use Noctalia's existing Network control -center as the only desktop toggle. Pass after route, DNS, test-service access, -suspend/resume, reconnect, and reboot work on both machines. Remove OpenVPN 3 -only after both pass. - -### Niri active-workspace exchange - -Pilot a two-output-only helper that assigns collision-free temporary names to -both active workspaces, preserves original names, moves each named workspace to -the other output, makes both arrivals active, restores names, and returns focus -to the original physical output. It remains non-atomic and must restore names -on failure where possible. Keep it only if repeated swaps are reliable and the -visible transition is acceptable; otherwise retain the current shortcut -temporarily and track a native Niri action rather than growing the wrapper. - -Capture Zenith's external display identity before encoding right-hand placement -and initial focus. - -### Pinta - -Reproduce recent edits covering clipboard/open, selection, crop/resize/canvas, -text/drawing/shapes, layers/transparency, basic adjustments, and PNG/JPEG export. -If every required operation works, select Pinta and remove GIMP. Otherwise -remove Pinta and keep GIMP; do not retain both indefinitely. - -### Transcription - -Observe whether Syncthing transfers a growing Fossify recording and prove the -completion protocol before automation. Then validate one real Dutch/English -meeting end to end: recording, completed-file detection, transfer, CPU -transcription, diarization fallback, archive, and Markdown by the next morning. -Remove Zenith's timer only after Orion passes. - -### Stable web-app launchers - -Capture the Outlook app ID, replace stale generated launchers for Actual and -WhatsApp, and verify every Walker result starts current Vivaldi with the intended -app/profile after a NixOS upgrade and Vivaldi Sync cycle. Expose one launcher per -app. +## Repository contract + +Active work repositories live under `~/dev/work`; inactive repositories live +under `~/dev/work/.archive`, and worktrees under `~/dev/work/.worktrees`. +Repositories and worktrees never live under `~/sync`. + +Participating repositories use the following small contract: + +- A tracked `.envrc` enters the default flake development shell without starting + services, fetching data, or mutating the checkout. +- `flake.nix` owns reproducible dependencies, checks, packages, and formatting. +- `just --list` indexes non-obvious tasks and delegates to native commands. +- `AGENTS.md` contains concise project facts, invariants, architecture pointers, + and checks without duplicating global guidance. +- Service-owned `.http` and schema-owned `.sql` files stay beside their owner; + generated views, caches, and scratch output do not become durable sources. +- Secrets enter at runtime from an approved source or a user-only file outside + checkouts, initially `~/.config/yookr/.env`. +- A new worktree behaves like a fresh clone and requires its own explicit + `direnv allow`. +- Branch-derived ports, databases, and container names are added only after a + real collision. + +Use Process Compose only when multiple foreground processes benefit from one +visible lifecycle. Use Podman Compose for wholly containerized project stacks, +Quadlet for persistent user containers, and NixOS modules for durable host +services. Do not nest lifecycle tools for another status screen. + +`yookr-workbench` owns cross-system API requests, operational queries, the +Garden/Kubernetes entry point, and Marimo investigations. It does not collect +service OpenAPI specifications, schema migrations, deployable model code, or +copied application state. + +## Interaction and knowledge boundaries + +- Walker scans the four repository roots, excludes `.archive` and `.worktrees`, + and invokes one Nix-owned `open-project` command. The command opens Kitty at + the repository root, starts Neovim, and leaves a shell after Neovim exits. +- Vivaldi remains the bookmark and web-app authority. Home Manager owns stable + launchers for Actual, WhatsApp, and Outlook without duplicating browser state. +- Niri keeps dynamic general-purpose workspaces and a compact navigation and + movement vocabulary. The two-output active-workspace exchange remains a + bounded pilot. +- Codex is primary; Claude Code and Pi are fallbacks. Herdr owns persistence and + attention for top-level sessions, while each agent owns its internal state. +- Use one primary writer per checkout. Subagents may perform bounded independent + read-heavy work, but overlapping changes are sequenced. +- Substantial multi-session work may keep one living + `docs/plans/.md`; lasting knowledge moves to normal documentation + and transient execution history is removed when finished. +- `~/sync/notes` is the Markdown/zk authority. Daily intake uses + `daily/YYYY-MM-DD.md`; planned meetings and raw transcripts use `meetings/` + and `transcripts/` respectively. +- Microsoft 365 remains authoritative for work/university PIM and Radicale for + personal CalDAV, VTODO, and CardDAV. Clients present those authorities without + bridging or copying them. +- Fossify records on Android and Syncthing delivers completed recordings to + Orion. Orion owns event-driven CPU transcription, diarization fallback, + archival, and Markdown publication. + +## Safety boundaries + +GitHub and all other remotes are read-only. Local commits, clones, fetches, and +worktrees are allowed; remote mutation requires a separate change to policy. + +Credentials and `.env` files normally stay outside Git, the Nix store, and broad +synchronization. The accepted `yookr-vpn.ovpn` is a narrow exception for Zenith +and Polaris: keep it user-restricted and import it into NetworkManager without +printing or committing its contents. + +Only the user activates a NixOS or Home Manager generation. Agents may evaluate +and build configurations, then provide an activation command. + +## Remaining pilots + +- Capture Zenith's external display identity during a docked session and test the + two-output `Super+A` exchange. +- Resume Evolution and Android PIM only when the user chooses to run those daily + use pilots. +- Add workbench database connections and a Marimo report only for a real + cross-system investigation. +- Configure an S3 remote only for a real task with least-privileged credentials + and explicit upload authorization. +- Rerun the Yookr OpenAPI refresh after the deployed image exposes + `/full-api-docs/openapi.json`; run authenticated API checks only with current + user credentials. +- Keep Bruno, DBeaver, and existing PIM clients until their independent gates are + resolved. ## Deferred work -- Design the team release process: intentional releases, immutable - commit-addressed images, explicit revision promotion, rollout verification, - rollback evidence, and Kubernetes-native base/overlay composition before a - heavier GitOps platform. Do not encode mutable `:latest` rollout as a - canonical workstation action. -- Establish service-owned OpenAPI generation, publication, provenance, and - consumer-refresh policy. Existing local/network drift calls for review, not - automatic overwrite. -- Adopt a company-wide infrastructure secret source when Yookr chooses one. - Until then, keep manually supplied machine-local inputs and do not assume that - Bitwarden Secrets Manager is available merely because Password Manager is. -- Publish `yookr-workbench` privately when sharing becomes useful; do not create - a temporary synchronization path. -- Pilot SharePoint browse/download through `rclone` after Microsoft consent is - known. Continuous sync and mounts require a separate demonstrated need. -- Revisit Walker bookmark search only with a live Vivaldi reader, and company - credential search only with maintained multi-vault support or repeated cost. -- Revisit Worktrunk only after a concrete Herdr worktree gap. Revisit - `codex agents` after Nix-managed daemon startup works without a second mutable - installation. -- Build a formal agent evaluation harness only after representative tasks and - concrete configurations exist. Compare repeated runs on correctness, human - intervention, review burden, conceptual complexity, wall time, and token use; - structured agent output is telemetry, not the quality judgment. Controlled - Codex evaluations consume `codex exec --json`; they never parse the - interactive TUI or mutable internal databases. -- Add Walker-to-agent launching only after the explicit Herdr/worktree sequence - becomes repetitive enough to define a stable contract. -- Keep remote Herdr, builders, CI runners, agent offload, and general Orion - backup redesign out of scope until a concrete bottleneck justifies them. -- Add a report server, notebook hub, Nextcloud groupware/Notes, terminal PIM - client, or meeting-summary skill only after a missing capability is proven. -- Add branch-derived ports, databases, container projects, or other concurrency - isolation only after an observed resource collision. -- Add LocalSend, OCR, dictation, global clipboard remapping, a second hotkey-help - surface, or other distribution-inspired conveniences only after repeated use - gives them a clear owner. +- Define the team release process and OpenAPI publication policy before encoding + deployment automation. +- Adopt a company-wide infrastructure secret source only after Yookr selects one. +- Publish `yookr-workbench` only when sharing becomes useful. +- Pilot SharePoint browse/download through `rclone` after Microsoft consent. +- Revisit Worktrunk, `codex agents`, Walker-to-agent launching, formal agent + evaluation, remote builders, CI runners, and agent offload only after a concrete + gap appears. +- Add report servers, notebook hubs, groupware, terminal PIM, meeting summaries, + concurrency isolation, OCR, dictation, clipboard remapping, or additional + convenience surfaces only after repeated use establishes an owner. ## Reference baseline -Implementation must recheck fast-moving interfaces against the pinned version. +Recheck fast-moving interfaces against their pinned versions before changing +them. - Projects: [Nix flake templates](https://nix.dev/manual/nix/latest/command-ref/new-cli/nix3-flake-init), [nix-direnv](https://github.com/nix-community/nix-direnv), [just manual](https://just.systems/man/en/), [Podman Compose](https://docs.podman.io/en/latest/markdown/podman-compose.1.html), [Quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html), [Marimo](https://docs.marimo.io/). -- APIs/data: [Kulala environments](https://kulala.app/usage/environments), [Kulala](https://github.com/mistweaverco/kulala.nvim), [Dadbod](https://github.com/tpope/vim-dadbod), [Dadbod UI](https://github.com/kristijanhusak/vim-dadbod-ui/blob/master/doc/dadbod-ui.txt), [RabbitMQ management](https://www.rabbitmq.com/docs/management), [rclone S3](https://rclone.org/s3/), [rclone OneDrive/SharePoint](https://rclone.org/onedrive/). -- Desktop: [Walker providers](https://walkerlauncher.com/docs/providers), [Elephant menus](https://github.com/abenz1267/elephant/tree/master/internal/providers/menus), [Vivaldi web apps](https://help.vivaldi.com/desktop/miscellaneous/progressive-web-apps/), [Vivaldi Sync](https://help.vivaldi.com/desktop/tools/sync/), [Niri IPC](https://niri-wm.github.io/niri/IPC.html), [Niri actions](https://niri-wm.github.io/niri/niri_ipc/enum.Action.html), [Pinta guide](https://www.pinta-project.com/user-guide/). -- Agents: [Codex configuration precedence](https://learn.chatgpt.com/docs/config-file/config-basic#configuration-precedence), [Codex long-running work](https://learn.chatgpt.com/docs/long-running-work), [Codex subagents](https://learn.chatgpt.com/docs/agent-configuration/subagents), [Codex notifications](https://learn.chatgpt.com/docs/config-file/config-advanced#notifications), [Herdr configuration](https://herdr.dev/docs/configuration/), [Herdr CLI](https://herdr.dev/docs/cli-reference/), [Git worktrees](https://git-scm.com/docs/git-worktree), [Worktrunk](https://worktrunk.dev/). -- PIM/knowledge: [Evolution account types](https://help.gnome.org/evolution/intro-account-types.html), [Evolution Microsoft 365 implementation](https://gitlab.gnome.org/GNOME/evolution-ews/-/tree/master/src/Microsoft365), [Radicale](https://radicale.org/v3.html), [DAVx5](https://manual.davx5.com/introduction.html), [Tasks.org with DAVx5](https://tasks.org/docs/sync_davx5/), [Tasks.org Microsoft To Do](https://tasks.org/docs/sync_microsoft/), [`zk` daily journal](https://github.com/zk-org/zk/blob/main/docs/tips/daily-journal.md). -- Infrastructure: [Gardenctl v2](https://gardener.cloud/docs/gardenctl-v2/), [NetworkManager `nmcli`](https://networkmanager.pages.freedesktop.org/NetworkManager/NetworkManager/nmcli.html), [NetworkManager keyfiles](https://networkmanager.dev/docs/api/latest/nm-settings-keyfile.html), [Syncthing synchronization](https://docs.syncthing.net/users/syncing), [Fossify recorder implementation](https://github.com/FossifyOrg/Voice-Recorder/blob/main/app/src/main/kotlin/org/fossify/voicerecorder/services/RecorderService.kt). +- APIs/data: [Kulala](https://kulala.app/), [Dadbod](https://github.com/tpope/vim-dadbod), [RabbitMQ management](https://www.rabbitmq.com/docs/management), [rclone S3](https://rclone.org/s3/), [rclone OneDrive/SharePoint](https://rclone.org/onedrive/). +- Desktop: [Walker](https://walkerlauncher.com/docs/providers), [Elephant menus](https://github.com/abenz1267/elephant/tree/master/internal/providers/menus), [Vivaldi web apps](https://help.vivaldi.com/desktop/miscellaneous/progressive-web-apps/), [Niri IPC](https://niri-wm.github.io/niri/IPC.html). +- Agents: [Codex configuration](https://learn.chatgpt.com/docs/config-file/config-basic), [Codex long-running work](https://learn.chatgpt.com/docs/long-running-work), [Herdr](https://herdr.dev/docs/configuration/), [Git worktrees](https://git-scm.com/docs/git-worktree). +- PIM/knowledge: [Evolution account types](https://help.gnome.org/evolution/intro-account-types.html), [Radicale](https://radicale.org/v3.html), [DAVx5](https://manual.davx5.com/introduction.html), [Tasks.org](https://tasks.org/docs/), [`zk` daily journal](https://github.com/zk-org/zk/blob/main/docs/tips/daily-journal.md). +- Infrastructure: [Gardenctl](https://gardener.cloud/docs/gardenctl-v2/), [NetworkManager](https://networkmanager.pages.freedesktop.org/NetworkManager/NetworkManager/nmcli.html), [Syncthing](https://docs.syncthing.net/users/syncing). diff --git a/flake.lock b/flake.lock index 75743bb..b920c0b 100644 --- a/flake.lock +++ b/flake.lock @@ -57,9 +57,13 @@ "elephant": { "inputs": { "nixpkgs": [ + "walker", "nixpkgs" ], - "systems": "systems" + "systems": [ + "walker", + "systems" + ] }, "locked": { "lastModified": 1784365424, @@ -156,7 +160,7 @@ "nixpkgs": [ "nixpkgs" ], - "systems": "systems_2", + "systems": "systems", "treefmt-nix": "treefmt-nix" }, "locked": { @@ -215,7 +219,9 @@ }, "nixos-hardware": { "inputs": { - "nixpkgs": "nixpkgs" + "nixpkgs": [ + "nixpkgs" + ] }, "locked": { "lastModified": 1788335262, @@ -234,15 +240,15 @@ }, "nixpkgs": { "locked": { - "lastModified": 1767892417, - "narHash": "sha256-8bW3q88CEg2u4hSP66Vf4lpbLonHz7hqDNBMcCY7E9U=", - "rev": "3497aa5c9457a9d88d71fa93a4a8368816fbeeba", + "lastModified": 1788400875, + "narHash": "sha256-v4ZEET1Ah1K9+wMaIwPH/JTQK3MwW/gZC/SyjnlBX74=", + "rev": "5545adfad2e98de106a5544ca7067e03010410bd", "type": "tarball", - "url": "https://releases.nixos.org/nixos/unstable/nixos-26.05pre924538.3497aa5c9457/nixexprs.tar.xz" + "url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.11pre1066699.5545adfad2e9/nixexprs.tar.xz" }, "original": { "type": "tarball", - "url": "https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz" + "url": "https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz" } }, "nixpkgs-lib": { @@ -260,19 +266,6 @@ "type": "github" } }, - "nixpkgs_2": { - "locked": { - "lastModified": 1788400875, - "narHash": "sha256-v4ZEET1Ah1K9+wMaIwPH/JTQK3MwW/gZC/SyjnlBX74=", - "rev": "5545adfad2e98de106a5544ca7067e03010410bd", - "type": "tarball", - "url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.11pre1066699.5545adfad2e9/nixexprs.tar.xz" - }, - "original": { - "type": "tarball", - "url": "https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz" - } - }, "noctalia": { "inputs": { "nixpkgs": [ @@ -296,7 +289,6 @@ "root": { "inputs": { "disko": "disko", - "elephant": "elephant", "flake-parts": "flake-parts", "home-manager": "home-manager", "import-tree": "import-tree", @@ -304,7 +296,7 @@ "nix-index-database": "nix-index-database", "nix-wrapper-modules": "nix-wrapper-modules", "nixos-hardware": "nixos-hardware", - "nixpkgs": "nixpkgs_2", + "nixpkgs": "nixpkgs", "noctalia": "noctalia", "sops-nix": "sops-nix", "walker": "walker" @@ -331,21 +323,6 @@ } }, "systems": { - "locked": { - "lastModified": 1689347949, - "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", - "owner": "nix-systems", - "repo": "default-linux", - "rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default-linux", - "type": "github" - } - }, - "systems_2": { "locked": { "lastModified": 1681028828, "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", @@ -360,7 +337,7 @@ "type": "github" } }, - "systems_3": { + "systems_2": { "locked": { "lastModified": 1689347949, "narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=", @@ -398,13 +375,11 @@ }, "walker": { "inputs": { - "elephant": [ - "elephant" - ], + "elephant": "elephant", "nixpkgs": [ "nixpkgs" ], - "systems": "systems_3" + "systems": "systems_2" }, "locked": { "lastModified": 1784217994, diff --git a/flake.nix b/flake.nix index a0c6e31..9b14c35 100644 --- a/flake.nix +++ b/flake.nix @@ -20,7 +20,10 @@ url = "github:BirdeeHub/nix-wrapper-modules"; inputs.nixpkgs.follows = "nixpkgs"; }; - nixos-hardware.url = "github:NixOS/nixos-hardware/master"; + nixos-hardware = { + url = "github:NixOS/nixos-hardware/master"; + inputs.nixpkgs.follows = "nixpkgs"; + }; nixpkgs.url = "https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz"; noctalia = { url = "github:noctalia-dev/noctalia"; @@ -35,13 +38,8 @@ inputs.nixpkgs.follows = "nixpkgs"; }; - elephant = { - url = "github:abenz1267/elephant"; - inputs.nixpkgs.follows = "nixpkgs"; - }; walker = { url = "github:abenz1267/walker"; - inputs.elephant.follows = "elephant"; inputs.nixpkgs.follows = "nixpkgs"; }; }; diff --git a/modules/capabilities/_transcribe-recordings.sh b/modules/capabilities/_transcribe-recordings.sh index 8307fe5..ce6f692 100644 --- a/modules/capabilities/_transcribe-recordings.sh +++ b/modules/capabilities/_transcribe-recordings.sh @@ -232,9 +232,7 @@ process_scope() { local source processing_target recording_status signature index scope_status=0 local -a sources signatures - if ! mkdir -p "$inbox_dir" "$processing_dir" "$archive_dir" "$failed_dir" "$transcript_dir"; then - return 1 - fi + mkdir -p "$inbox_dir" "$processing_dir" "$archive_dir" "$failed_dir" "$transcript_dir" || return 1 while IFS= read -r -d '' source; do if is_recording "$source"; then @@ -291,4 +289,3 @@ mkdir -p "$runtime_dir" set +o errexit process_scope "$TRANSCRIBE_SYNC_ROOT/recordings" "$TRANSCRIBE_SYNC_ROOT/notes/transcripts" -exit $? diff --git a/modules/capabilities/audio.nix b/modules/capabilities/audio.nix index cba7e43..b6f6aed 100644 --- a/modules/capabilities/audio.nix +++ b/modules/capabilities/audio.nix @@ -6,7 +6,6 @@ alsa.enable = true; pulse.enable = true; jack.enable = true; - wireplumber.enable = true; }; }; } diff --git a/modules/capabilities/desktop-defaults.nix b/modules/capabilities/desktop-defaults.nix index 8792ca2..ac68edf 100644 --- a/modules/capabilities/desktop-defaults.nix +++ b/modules/capabilities/desktop-defaults.nix @@ -1,4 +1,3 @@ -{ ... }: { flake.modules.homeManager.desktop-commands = { lib, ... }: @@ -35,56 +34,6 @@ actualId = "ahoihnifhakgijcmjlhbcgehigihinlo"; outlookId = "eoficlgicibekocmfdomjbfnjmehnhcd"; whatsappId = "hnpfjngllnobngcgfapefoaidbinmjnm"; - actualDesktop = pkgs.makeDesktopItem { - name = "vivaldi-${actualId}-Default"; - desktopName = "Actual"; - exec = appCommand actualId; - icon = "vivaldi-${actualId}-Default"; - extraConfig.StartupWMClass = "crx_${actualId}"; - actions = { - Accounts = { - name = "Accounts"; - exec = "${appCommand actualId} --app-launch-url-for-shortcuts-menu-item=${config.facts.services.actual.origin}/accounts"; - }; - "Add-Transaction" = { - name = "Add Transaction"; - exec = "${appCommand actualId} --app-launch-url-for-shortcuts-menu-item=${config.facts.services.actual.origin}/transactions/new"; - }; - Reports = { - name = "Reports"; - exec = "${appCommand actualId} --app-launch-url-for-shortcuts-menu-item=${config.facts.services.actual.origin}/reports"; - }; - }; - }; - whatsappDesktop = pkgs.makeDesktopItem { - name = "vivaldi-${whatsappId}-Default"; - desktopName = "WhatsApp Web"; - exec = appCommand whatsappId; - icon = "vivaldi-${whatsappId}-Default"; - extraConfig.StartupWMClass = "crx_${whatsappId}"; - }; - outlookDesktop = pkgs.makeDesktopItem { - name = "vivaldi-${outlookId}-Default"; - desktopName = "Outlook"; - exec = "${appCommand outlookId} %U"; - icon = "vivaldi-${outlookId}-Default"; - mimeTypes = [ "x-scheme-handler/mailto" ]; - extraConfig.StartupWMClass = "crx_${outlookId}"; - actions = { - Calendar = { - name = "Open calendar"; - exec = "${appCommand outlookId} --app-launch-url-for-shortcuts-menu-item=https://outlook.cloud.microsoft/calendar"; - }; - Compose = { - name = "New message"; - exec = "${appCommand outlookId} --app-launch-url-for-shortcuts-menu-item=https://outlook.cloud.microsoft/mail/deeplink/compose"; - }; - Event = { - name = "New event"; - exec = "${appCommand outlookId} --app-launch-url-for-shortcuts-menu-item=https://outlook.cloud.microsoft/calendar/deeplink/compose"; - }; - }; - }; in { desktop.commands.browser = [ command ]; @@ -93,18 +42,53 @@ sessionVariables.BROWSER = command; }; xdg = { - dataFile = { - "applications/vivaldi-${actualId}-Default.desktop" = { - force = true; - source = "${actualDesktop}/share/applications/vivaldi-${actualId}-Default.desktop"; + desktopEntries = { + "vivaldi-${actualId}-Default" = { + name = "Actual"; + exec = appCommand actualId; + icon = "vivaldi-${actualId}-Default"; + settings.StartupWMClass = "crx_${actualId}"; + actions = { + Accounts = { + name = "Accounts"; + exec = "${appCommand actualId} --app-launch-url-for-shortcuts-menu-item=${config.facts.services.actual.origin}/accounts"; + }; + "Add-Transaction" = { + name = "Add Transaction"; + exec = "${appCommand actualId} --app-launch-url-for-shortcuts-menu-item=${config.facts.services.actual.origin}/transactions/new"; + }; + Reports = { + name = "Reports"; + exec = "${appCommand actualId} --app-launch-url-for-shortcuts-menu-item=${config.facts.services.actual.origin}/reports"; + }; + }; }; - "applications/vivaldi-${whatsappId}-Default.desktop" = { - force = true; - source = "${whatsappDesktop}/share/applications/vivaldi-${whatsappId}-Default.desktop"; + "vivaldi-${whatsappId}-Default" = { + name = "WhatsApp Web"; + exec = appCommand whatsappId; + icon = "vivaldi-${whatsappId}-Default"; + settings.StartupWMClass = "crx_${whatsappId}"; }; - "applications/vivaldi-${outlookId}-Default.desktop" = { - force = true; - source = "${outlookDesktop}/share/applications/vivaldi-${outlookId}-Default.desktop"; + "vivaldi-${outlookId}-Default" = { + name = "Outlook"; + exec = "${appCommand outlookId} %U"; + icon = "vivaldi-${outlookId}-Default"; + mimeType = [ "x-scheme-handler/mailto" ]; + settings.StartupWMClass = "crx_${outlookId}"; + actions = { + Calendar = { + name = "Open calendar"; + exec = "${appCommand outlookId} --app-launch-url-for-shortcuts-menu-item=https://outlook.cloud.microsoft/calendar"; + }; + Compose = { + name = "New message"; + exec = "${appCommand outlookId} --app-launch-url-for-shortcuts-menu-item=https://outlook.cloud.microsoft/mail/deeplink/compose"; + }; + Event = { + name = "New event"; + exec = "${appCommand outlookId} --app-launch-url-for-shortcuts-menu-item=https://outlook.cloud.microsoft/calendar/deeplink/compose"; + }; + }; }; }; mimeApps.defaultApplicationPackages = [ pkgs.vivaldi ]; @@ -168,9 +152,6 @@ enable = true; cacheHome = "${localDir}/cache"; - configHome = "${homeDir}/.config"; - dataHome = "${localDir}/share"; - stateHome = "${localDir}/state"; userDirs = { enable = true; diff --git a/modules/capabilities/dev-tools.nix b/modules/capabilities/dev-tools.nix index 3fab898..e93e7db 100644 --- a/modules/capabilities/dev-tools.nix +++ b/modules/capabilities/dev-tools.nix @@ -1,4 +1,3 @@ -{ ... }: { flake.modules.homeManager.dev-tools = { config, pkgs, ... }: @@ -44,10 +43,7 @@ gardenlogin ]; - programs.lazygit = { - enable = true; - enableZshIntegration = true; - }; + programs.lazygit.enable = true; programs.jujutsu.enable = true; programs.jq.enable = true; diff --git a/modules/capabilities/flatpak.nix b/modules/capabilities/flatpak.nix index 3e03f62..0cbe3b0 100644 --- a/modules/capabilities/flatpak.nix +++ b/modules/capabilities/flatpak.nix @@ -1,5 +1,3 @@ { - flake.modules.nixos.flatpak = { - services.flatpak.enable = true; - }; + flake.modules.nixos.flatpak.services.flatpak.enable = true; } diff --git a/modules/capabilities/fonts.nix b/modules/capabilities/fonts.nix index c79cf3b..9b68b1d 100644 --- a/modules/capabilities/fonts.nix +++ b/modules/capabilities/fonts.nix @@ -28,21 +28,9 @@ ]; fontconfig = { - enable = true; - antialias = true; allowBitmaps = false; useEmbeddedBitmaps = false; - - hinting = { - enable = true; - autohint = false; - style = "slight"; - }; - - subpixel = { - rgba = "none"; - lcdfilter = "none"; - }; + subpixel.lcdfilter = "none"; }; }; }; diff --git a/modules/capabilities/keyring.nix b/modules/capabilities/keyring.nix index 691fea9..c57bf26 100644 --- a/modules/capabilities/keyring.nix +++ b/modules/capabilities/keyring.nix @@ -1,5 +1,3 @@ { - flake.modules.homeManager.keyring = { - services.gnome-keyring.enable = true; - }; + flake.modules.homeManager.keyring.services.gnome-keyring.enable = true; } diff --git a/modules/capabilities/limine.nix b/modules/capabilities/limine.nix index 4d55e55..bfe8dfd 100644 --- a/modules/capabilities/limine.nix +++ b/modules/capabilities/limine.nix @@ -1,4 +1,4 @@ -{ config, lib, ... }: +{ lib, ... }: let palette = (import ./_kanagawa.nix).palette; hex = lib.removePrefix "#"; @@ -9,9 +9,9 @@ in flake.modules.nixos.limine = { config, lib, ... }: let - displayValues = builtins.attrValues config.facts.machine.displays; - primaryDisplays = lib.filter (display: display.primary) displayValues; - primaryDisplay = if primaryDisplays == [ ] then null else builtins.head primaryDisplays; + primaryDisplay = lib.findFirst (display: display.primary) null ( + builtins.attrValues config.facts.machine.displays + ); interfaceResolution = if primaryDisplay != null && primaryDisplay.width != null then "${toString primaryDisplay.width}x${toString primaryDisplay.height}" @@ -24,6 +24,7 @@ in limine = { enable = true; maxGenerations = 10; + resolution = interfaceResolution; style = { backdrop = hex palette.secondaryBackground; @@ -64,13 +65,9 @@ in interface = { branding = config.networking.hostName; brandingColor = hex palette.accents.blue; - helpHidden = false; resolution = interfaceResolution; }; }; - } - // lib.optionalAttrs (interfaceResolution != null) { - resolution = interfaceResolution; }; }; }; diff --git a/modules/capabilities/mpv.nix b/modules/capabilities/mpv.nix index b5093c6..fdb82c2 100644 --- a/modules/capabilities/mpv.nix +++ b/modules/capabilities/mpv.nix @@ -4,9 +4,7 @@ { programs.mpv = { enable = true; - bindings = { - D = "cycle deband"; - }; + bindings.D = "cycle deband"; config = { profile = "high-quality"; diff --git a/modules/capabilities/neovim/default.nix b/modules/capabilities/neovim/default.nix index 3c2c59a..4eabf81 100644 --- a/modules/capabilities/neovim/default.nix +++ b/modules/capabilities/neovim/default.nix @@ -1,7 +1,6 @@ { config, ... }: let account = config.facts.account; - homeModules = config.flake.modules.homeManager; in { flake.modules.homeManager.neovim = @@ -214,15 +213,13 @@ in kulalaCorePath = "${pkgs.kulala-core}/bin/kulala-core"; }; - - binName = "nvim"; }; }; flake.modules.homeManager.primary-editor-neovim = { pkgs, ... }: { - imports = [ homeModules.neovim ]; + imports = [ config.flake.modules.homeManager.neovim ]; home.sessionVariables = { EDITOR = "nvim"; diff --git a/modules/capabilities/neovim/lua-config/lua/keymaps.lua b/modules/capabilities/neovim/lua-config/lua/keymaps.lua index bbdecde..0d01565 100644 --- a/modules/capabilities/neovim/lua-config/lua/keymaps.lua +++ b/modules/capabilities/neovim/lua-config/lua/keymaps.lua @@ -5,8 +5,7 @@ for _, lhs in ipairs({ "grn", "gra", "grx", "grr", "gri", "grt", "gO" }) do vim.keymap.del("n", lhs) end vim.keymap.del("x", "gra") -vim.keymap.del("i", "") -vim.keymap.del("s", "") +vim.keymap.del({ "i", "s" }, "") vim.keymap.set("n", "", "", { desc = "Focus left window" }) vim.keymap.set("n", "", "", { desc = "Focus lower window" }) diff --git a/modules/capabilities/neovim/lua-config/lua/plugins/completion.lua b/modules/capabilities/neovim/lua-config/lua/plugins/completion.lua index f7570e5..cc71a2e 100644 --- a/modules/capabilities/neovim/lua-config/lua/plugins/completion.lua +++ b/modules/capabilities/neovim/lua-config/lua/plugins/completion.lua @@ -15,7 +15,6 @@ require("lz.n").load({ after = function() require("blink.cmp").setup({ keymap = { - preset = "default", [""] = { "select_next", "fallback" }, [""] = { "select_prev", "fallback" }, [""] = { "select_and_accept", "fallback" }, diff --git a/modules/capabilities/neovim/lua-config/lua/plugins/editing.lua b/modules/capabilities/neovim/lua-config/lua/plugins/editing.lua index 9a5b45f..1df4779 100644 --- a/modules/capabilities/neovim/lua-config/lua/plugins/editing.lua +++ b/modules/capabilities/neovim/lua-config/lua/plugins/editing.lua @@ -44,7 +44,6 @@ require("lz.n").load({ after = function() require("nvim-treesitter-textobjects").setup({ select = { lookahead = true }, - move = { set_jumps = true }, }) local select = require("nvim-treesitter-textobjects.select") diff --git a/modules/capabilities/neovim/lua-config/lua/plugins/formatting.lua b/modules/capabilities/neovim/lua-config/lua/plugins/formatting.lua index 7e85aa7..57264bc 100644 --- a/modules/capabilities/neovim/lua-config/lua/plugins/formatting.lua +++ b/modules/capabilities/neovim/lua-config/lua/plugins/formatting.lua @@ -34,8 +34,7 @@ require("lz.n").load({ }, }, after = function() - local conform = require("conform") - conform.setup({ + require("conform").setup({ formatters_by_ft = formatters_by_ft, format_on_save = function(bufnr) if formatters_by_ft[vim.bo[bufnr].filetype] then diff --git a/modules/capabilities/neovim/lua-config/lua/plugins/lsp.lua b/modules/capabilities/neovim/lua-config/lua/plugins/lsp.lua index fcd21a6..4c5fbf3 100644 --- a/modules/capabilities/neovim/lua-config/lua/plugins/lsp.lua +++ b/modules/capabilities/neovim/lua-config/lua/plugins/lsp.lua @@ -16,12 +16,10 @@ require("lz.n").load({ "nvim-lspconfig", event = { "BufReadPre", "BufNewFile" }, after = function() - local attach_group = vim.api.nvim_create_augroup("lux-lsp-attach", { clear = true }) - local detach_group = vim.api.nvim_create_augroup("lux-lsp-detach", { clear = true }) local highlight_group = vim.api.nvim_create_augroup("lux-lsp-highlight", { clear = true }) vim.api.nvim_create_autocmd("LspAttach", { - group = attach_group, + group = vim.api.nvim_create_augroup("lux-lsp-attach", { clear = true }), callback = function(args) local client = assert(vim.lsp.get_client_by_id(args.data.client_id)) local bufnr = args.buf @@ -85,7 +83,7 @@ require("lz.n").load({ }) vim.api.nvim_create_autocmd("LspDetach", { - group = detach_group, + group = vim.api.nvim_create_augroup("lux-lsp-detach", { clear = true }), callback = function(args) local supports_highlights = vim .iter(vim.lsp.get_clients({ diff --git a/modules/capabilities/neovim/lua-config/lua/plugins/navigation.lua b/modules/capabilities/neovim/lua-config/lua/plugins/navigation.lua index ab85250..fb59a12 100644 --- a/modules/capabilities/neovim/lua-config/lua/plugins/navigation.lua +++ b/modules/capabilities/neovim/lua-config/lua/plugins/navigation.lua @@ -103,21 +103,15 @@ require("lz.n").load({ actions.smart_send_to_qflist(prompt_bufnr) vim.cmd("Trouble qflist open") end + local picker_mappings = { + [""] = actions.move_selection_next, + [""] = actions.move_selection_previous, + [""] = promote, + } require("telescope").setup({ defaults = { - mappings = { - i = { - [""] = actions.move_selection_next, - [""] = actions.move_selection_previous, - [""] = promote, - }, - n = { - [""] = actions.move_selection_next, - [""] = actions.move_selection_previous, - [""] = promote, - }, - }, + mappings = { i = picker_mappings, n = picker_mappings }, }, extensions = { ["ui-select"] = require("telescope.themes").get_dropdown(), diff --git a/modules/capabilities/neovim/lua-config/lua/plugins/ui.lua b/modules/capabilities/neovim/lua-config/lua/plugins/ui.lua index cef56e4..6c84a5b 100644 --- a/modules/capabilities/neovim/lua-config/lua/plugins/ui.lua +++ b/modules/capabilities/neovim/lua-config/lua/plugins/ui.lua @@ -60,29 +60,22 @@ require("lz.n").load({ change = { text = "│" }, delete = { text = "_" }, topdelete = { text = "‾" }, - changedelete = { text = "~" }, - untracked = { text = "┆" }, }, on_attach = function(bufnr) local gs = require("gitsigns") - vim.keymap.set("n", "]h", function() - if vim.wo.diff then - return "]h" - end - vim.schedule(function() - gs.nav_hunk("next") - end) - return "" - end, { buffer = bufnr, desc = "Next git hunk", expr = true }) - vim.keymap.set("n", "[h", function() - if vim.wo.diff then - return "[h" - end - vim.schedule(function() - gs.nav_hunk("prev") - end) - return "" - end, { buffer = bufnr, desc = "Previous git hunk", expr = true }) + local map_hunk = function(lhs, direction, desc) + vim.keymap.set("n", lhs, function() + if vim.wo.diff then + return lhs + end + vim.schedule(function() + gs.nav_hunk(direction) + end) + return "" + end, { buffer = bufnr, desc = desc, expr = true }) + end + map_hunk("]h", "next", "Next git hunk") + map_hunk("[h", "prev", "Previous git hunk") end, }) end, diff --git a/modules/capabilities/niri/_bindings.nix b/modules/capabilities/niri/_bindings.nix index 9f27ed6..3c97fcc 100644 --- a/modules/capabilities/niri/_bindings.nix +++ b/modules/capabilities/niri/_bindings.nix @@ -1,9 +1,19 @@ { browser, launcher, - shortcutCommands, + swapMonitorWorkspaces, terminal, }: +let + spawnLocked = command: { + spawn-sh = command; + _props.allow-when-locked = true; + }; + withCooldown = action: { + ${action} = { }; + _props.cooldown-ms = 150; + }; +in { # Application and utility launchers "Mod+Return" = { @@ -25,46 +35,16 @@ }; # Hardware media, volume, and brightness keys - "XF86AudioPlay" = { - spawn-sh = "playerctl play-pause"; - _props.allow-when-locked = true; - }; - "XF86AudioStop" = { - spawn-sh = "playerctl stop"; - _props.allow-when-locked = true; - }; - "XF86AudioPrev" = { - spawn-sh = "playerctl previous"; - _props.allow-when-locked = true; - }; - "XF86AudioNext" = { - spawn-sh = "playerctl next"; - _props.allow-when-locked = true; - }; - "XF86AudioRaiseVolume" = { - spawn-sh = "wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+"; - _props.allow-when-locked = true; - }; - "XF86AudioLowerVolume" = { - spawn-sh = "wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"; - _props.allow-when-locked = true; - }; - "XF86AudioMute" = { - spawn-sh = "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"; - _props.allow-when-locked = true; - }; - "XF86AudioMicMute" = { - spawn-sh = "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"; - _props.allow-when-locked = true; - }; - "XF86MonBrightnessUp" = { - spawn-sh = "brightnessctl s 5%+"; - _props.allow-when-locked = true; - }; - "XF86MonBrightnessDown" = { - spawn-sh = "brightnessctl s 5%-"; - _props.allow-when-locked = true; - }; + "XF86AudioPlay" = spawnLocked "playerctl play-pause"; + "XF86AudioStop" = spawnLocked "playerctl stop"; + "XF86AudioPrev" = spawnLocked "playerctl previous"; + "XF86AudioNext" = spawnLocked "playerctl next"; + "XF86AudioRaiseVolume" = spawnLocked "wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%+"; + "XF86AudioLowerVolume" = spawnLocked "wpctl set-volume @DEFAULT_AUDIO_SINK@ 5%-"; + "XF86AudioMute" = spawnLocked "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle"; + "XF86AudioMicMute" = spawnLocked "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle"; + "XF86MonBrightnessUp" = spawnLocked "brightnessctl s 5%+"; + "XF86MonBrightnessDown" = spawnLocked "brightnessctl s 5%-"; # Screenshots "Mod+S".screenshot = { }; @@ -105,7 +85,7 @@ "Mod+Tab".focus-monitor-next = { }; "Mod+A" = { _props.repeat = false; - spawn = shortcutCommands.swapMonitorWorkspaces; + spawn = swapMonitorWorkspaces; _props.hotkey-overlay-title = "Swap Monitor Workspaces"; }; "Mod+Alt+Tab".move-column-to-monitor-next = { }; @@ -124,22 +104,10 @@ "Mod+Shift+I".move-workspace-up = { }; # Mouse workspace navigation - "Mod+WheelScrollDown" = { - focus-workspace-down = { }; - _props.cooldown-ms = 150; - }; - "Mod+WheelScrollUp" = { - focus-workspace-up = { }; - _props.cooldown-ms = 150; - }; - "Mod+Alt+WheelScrollDown" = { - move-column-to-workspace-down = { }; - _props.cooldown-ms = 150; - }; - "Mod+Alt+WheelScrollUp" = { - move-column-to-workspace-up = { }; - _props.cooldown-ms = 150; - }; + "Mod+WheelScrollDown" = withCooldown "focus-workspace-down"; + "Mod+WheelScrollUp" = withCooldown "focus-workspace-up"; + "Mod+Alt+WheelScrollDown" = withCooldown "move-column-to-workspace-down"; + "Mod+Alt+WheelScrollUp" = withCooldown "move-column-to-workspace-up"; # Mouse column navigation and movement "Mod+WheelScrollRight".focus-column-or-monitor-right = { }; diff --git a/modules/capabilities/niri/_shortcuts.nix b/modules/capabilities/niri/_shortcuts.nix index e7b4363..ed25555 100644 --- a/modules/capabilities/niri/_shortcuts.nix +++ b/modules/capabilities/niri/_shortcuts.nix @@ -1,46 +1,37 @@ -{ - lib, - pkgs, -}: -rec { - scripts = { - swapMonitorWorkspaces = pkgs.writeShellApplication { - name = "niri-shortcut-swap-monitor-workspaces"; - runtimeInputs = [ - pkgs.jq - pkgs.niri - ]; - text = '' - focused_output="$(niri msg --json focused-output | jq -r '.name // empty')" - if [ -z "$focused_output" ]; then - exit 0 - fi +{ pkgs }: +pkgs.writeShellApplication { + name = "niri-shortcut-swap-monitor-workspaces"; + runtimeInputs = [ + pkgs.jq + pkgs.niri + ]; + text = '' + focused_output="$(niri msg --json focused-output | jq -r '.name // empty')" + if [ -z "$focused_output" ]; then + exit 0 + fi - niri msg action focus-monitor-next + niri msg action focus-monitor-next - target_output="$(niri msg --json focused-output | jq -r '.name // empty')" - if [ -z "$target_output" ] || [ "$target_output" = "$focused_output" ]; then - exit 0 - fi + target_output="$(niri msg --json focused-output | jq -r '.name // empty')" + if [ -z "$target_output" ] || [ "$target_output" = "$focused_output" ]; then + exit 0 + fi - target_workspace_idx="$( - niri msg --json workspaces \ - | jq -r --arg output "$target_output" ' - first(.[] | select(.output == $output and .is_active) | .idx) // empty - ' - )" - if [ -z "$target_workspace_idx" ]; then - niri msg action focus-monitor "$focused_output" - exit 0 - fi + target_workspace_idx="$( + niri msg --json workspaces \ + | jq -r --arg output "$target_output" ' + first(.[] | select(.output == $output and .is_active) | .idx) // empty + ' + )" + if [ -z "$target_workspace_idx" ]; then + niri msg action focus-monitor "$focused_output" + exit 0 + fi - niri msg action focus-monitor "$focused_output" - niri msg action move-workspace-to-monitor "$target_output" - niri msg action focus-workspace "$target_workspace_idx" - niri msg action move-workspace-to-monitor "$focused_output" - ''; - }; - }; - - commands = lib.mapAttrs (_: lib.getExe) scripts; + niri msg action focus-monitor "$focused_output" + niri msg action move-workspace-to-monitor "$target_output" + niri msg action focus-workspace "$target_workspace_idx" + niri msg action move-workspace-to-monitor "$focused_output" + ''; } diff --git a/modules/capabilities/niri/default.nix b/modules/capabilities/niri/default.nix index 0579711..8af5212 100644 --- a/modules/capabilities/niri/default.nix +++ b/modules/capabilities/niri/default.nix @@ -29,12 +29,7 @@ in }: let palette = kanagawa.palette; - shortcuts = import ./_shortcuts.nix { - inherit - lib - pkgs - ; - }; + swapMonitorWorkspaces = import ./_shortcuts.nix { inherit pkgs; }; machine = config.facts.machine; in { @@ -47,7 +42,7 @@ in brightnessctl wireplumber ] - ++ lib.attrValues shortcuts.scripts; + ++ [ swapMonitorWorkspaces ]; }; dconf.settings."org/gnome/desktop/interface".color-scheme = "prefer-dark"; @@ -140,7 +135,7 @@ in launcher terminal ; - shortcutCommands = shortcuts.commands; + swapMonitorWorkspaces = lib.getExe swapMonitorWorkspaces; }; }; }; diff --git a/modules/capabilities/nix.nix b/modules/capabilities/nix.nix index c612c94..4ca4e47 100644 --- a/modules/capabilities/nix.nix +++ b/modules/capabilities/nix.nix @@ -3,28 +3,25 @@ let account = config.facts.account; in { - flake.modules.nixos.nix = - { ... }: - { - nixpkgs.config.allowUnfree = true; + flake.modules.nixos.nix = { + nixpkgs.config.allowUnfree = true; - nix = { - gc.automatic = true; - optimise.automatic = true; - registry.nixpkgs.flake = inputs.nixpkgs; - channel.enable = false; + nix = { + gc.automatic = true; + optimise.automatic = true; + channel.enable = false; - settings = { - trusted-users = [ "@wheel" ]; - use-xdg-base-directories = true; + settings = { + trusted-users = [ "@wheel" ]; + use-xdg-base-directories = true; - experimental-features = [ - "nix-command" - "flakes" - ]; - }; + experimental-features = [ + "nix-command" + "flakes" + ]; }; }; + }; flake.modules.homeManager.nix = { pkgs, ... }: diff --git a/modules/capabilities/noctalia.nix b/modules/capabilities/noctalia.nix index 01ccc19..09f78a6 100644 --- a/modules/capabilities/noctalia.nix +++ b/modules/capabilities/noctalia.nix @@ -61,17 +61,7 @@ }; }; - desktop_widgets = { - enabled = false; - schema_version = 2; - widget_order = [ ]; - grid = { - cell_size = 16; - major_interval = 4; - visible = true; - }; - widget = { }; - }; + desktop_widgets.enabled = false; location.address = "Meterik, The Netherlands"; @@ -98,7 +88,6 @@ font_family = "Lexend"; polkit_agent = true; screen_time_enabled = true; - settings_show_advanced = true; animation.speed = 1.5; screen_corners = { enabled = true; @@ -127,7 +116,6 @@ widget = { bluetooth.hide_when_no_connected_device = true; - clock.format = "{:%H:%M}"; media = { art_size = 90.0; max_length = 800; diff --git a/modules/capabilities/passwords.nix b/modules/capabilities/passwords.nix index cd30533..e5e4ba9 100644 --- a/modules/capabilities/passwords.nix +++ b/modules/capabilities/passwords.nix @@ -12,16 +12,13 @@ in pkgs, ... }: - let - pinentryPackage = pkgs.pinentry-gnome3; - in { programs.rbw = { enable = true; settings = { base_url = facts.services.vaultwarden.origin; email = account.emails.${account.primaryEmail}.address; - pinentry = pinentryPackage; + pinentry = pkgs.pinentry-gnome3; }; }; }; diff --git a/modules/capabilities/pim.nix b/modules/capabilities/pim.nix index 10f0f8d..54037ee 100644 --- a/modules/capabilities/pim.nix +++ b/modules/capabilities/pim.nix @@ -21,7 +21,6 @@ in { config, lib, - pkgs, ... }: let @@ -31,7 +30,6 @@ in ... }: { - enable = true; inherit address; realName = account.realName; userName = address; @@ -50,7 +48,6 @@ in ... }: { - enable = true; inherit address; realName = account.realName; userName = address; @@ -59,13 +56,11 @@ in authentication = "plain"; host = "taylor.mxrouting.net"; port = 993; - tls.enable = true; }; smtp = { authentication = "plain"; host = "taylor.mxrouting.net"; port = 465; - tls.enable = true; }; }; mkEmailAccount = @@ -103,9 +98,6 @@ in pkgs, ... }: - let - calendarsPath = "${config.xdg.dataHome}/calendars"; - in { imports = [ homeModules.sops ]; @@ -136,16 +128,11 @@ in }; accounts.calendar = { - basePath = calendarsPath; + basePath = "${config.xdg.dataHome}/calendars"; accounts.radicale = { primary = true; primaryCollection = "personal"; - local = { - type = "filesystem"; - fileExt = ".ics"; - }; - remote = { url = "${facts.services.radicale.origin}/"; type = "caldav"; diff --git a/modules/capabilities/podman.nix b/modules/capabilities/podman.nix index fb03e43..188be81 100644 --- a/modules/capabilities/podman.nix +++ b/modules/capabilities/podman.nix @@ -1,12 +1,9 @@ { config, ... }: -let - homeModules = config.flake.modules.homeManager; -in { flake.modules.nixos.podman = { lib, pkgs, ... }: { - home-manager.sharedModules = [ homeModules.podman ]; + home-manager.sharedModules = [ config.flake.modules.homeManager.podman ]; virtualisation.podman.enable = true; virtualisation.containers.containersConf.settings.engine.compose_providers = [ diff --git a/modules/capabilities/printing.nix b/modules/capabilities/printing.nix index ff9963a..48b75ff 100644 --- a/modules/capabilities/printing.nix +++ b/modules/capabilities/printing.nix @@ -7,9 +7,7 @@ printing = { enable = true; browsed.enable = true; - drivers = with pkgs; [ - cnijfilter2 - ]; + drivers = [ pkgs.cnijfilter2 ]; }; avahi = { diff --git a/modules/capabilities/sddm.nix b/modules/capabilities/sddm.nix index 9f29665..8d09c02 100644 --- a/modules/capabilities/sddm.nix +++ b/modules/capabilities/sddm.nix @@ -13,9 +13,7 @@ theme = "${ pkgs.sddm-astronaut.override { embeddedTheme = "purple_leaves"; } }/share/sddm/themes/sddm-astronaut-theme"; - extraPackages = with pkgs; [ - kdePackages.qtmultimedia - ]; + extraPackages = [ pkgs.kdePackages.qtmultimedia ]; settings.Theme = { CursorTheme = "phinger-cursors-light"; CursorSize = 24; diff --git a/modules/capabilities/services/actual.nix b/modules/capabilities/services/actual.nix index b8390ab..b6e5da0 100644 --- a/modules/capabilities/services/actual.nix +++ b/modules/capabilities/services/actual.nix @@ -1,21 +1,17 @@ { config, ... }: -let - service = config.facts.services.actual; - nixosModules = config.flake.modules.nixos; -in { flake.modules.nixos.actual = { - imports = [ nixosModules.caddy ]; + imports = [ config.flake.modules.nixos.caddy ]; services.actual = { enable = true; - openFirewall = false; settings = { hostname = "127.0.0.1"; port = 3000; }; }; - services.caddy.virtualHosts.${service.domain}.extraConfig = "reverse_proxy 127.0.0.1:3000"; + services.caddy.virtualHosts.${config.facts.services.actual.domain}.extraConfig = + "reverse_proxy 127.0.0.1:3000"; }; } diff --git a/modules/capabilities/services/gitea.nix b/modules/capabilities/services/gitea.nix index f299cb9..6fba92b 100644 --- a/modules/capabilities/services/gitea.nix +++ b/modules/capabilities/services/gitea.nix @@ -19,9 +19,6 @@ in ROOT_URL = "${service.origin}/"; HTTP_PORT = 3001; HTTP_ADDR = "127.0.0.1"; - - START_SSH_SERVER = false; - SSH_PORT = 22; }; service.DISABLE_REGISTRATION = true; diff --git a/modules/capabilities/services/openssh.nix b/modules/capabilities/services/openssh.nix index 4977709..69983bb 100644 --- a/modules/capabilities/services/openssh.nix +++ b/modules/capabilities/services/openssh.nix @@ -1,11 +1,9 @@ { config, lib, ... }: let account = config.facts.account; - personalPublicKeys = - machines: - map (machine: machine.sshKeys.personal.publicKey) ( - lib.filter (machine: machine.sshKeys ? personal) (builtins.attrValues machines) - ); + personalPublicKeys = map (machine: machine.sshKeys.personal.publicKey) ( + lib.filter (machine: machine.sshKeys ? personal) (builtins.attrValues config.facts.machines) + ); in { flake.modules.nixos.sudo-ssh-agent-auth = { @@ -15,18 +13,16 @@ in }; }; - flake.modules.nixos.openssh = - { ... }: - { - services.openssh = { - enable = true; - settings = { - PermitRootLogin = "no"; - PasswordAuthentication = false; - AllowUsers = [ account.name ]; - }; + flake.modules.nixos.openssh = { + services.openssh = { + enable = true; + settings = { + PermitRootLogin = "no"; + PasswordAuthentication = false; + AllowUsers = [ account.name ]; }; - - users.users.${account.name}.openssh.authorizedKeys.keys = personalPublicKeys config.facts.machines; }; + + users.users.${account.name}.openssh.authorizedKeys.keys = personalPublicKeys; + }; } diff --git a/modules/capabilities/services/radicale.nix b/modules/capabilities/services/radicale.nix index 6a46047..3b4bdde 100644 --- a/modules/capabilities/services/radicale.nix +++ b/modules/capabilities/services/radicale.nix @@ -1,11 +1,7 @@ { config, ... }: -let - service = config.facts.services.radicale; - nixosModules = config.flake.modules.nixos; -in { flake.modules.nixos.radicale = { - imports = [ nixosModules.caddy ]; + imports = [ config.flake.modules.nixos.caddy ]; services.radicale = { enable = true; @@ -17,15 +13,12 @@ in htpasswd_filename = "/var/lib/radicale/users"; htpasswd_encryption = "bcrypt"; }; - - storage.filesystem_folder = "/var/lib/radicale/collections"; }; }; - services.caddy.virtualHosts.${service.domain}.extraConfig = '' + services.caddy.virtualHosts.${config.facts.services.radicale.domain}.extraConfig = '' reverse_proxy 127.0.0.1:5232 { header_up X-Script-Name / - header_up X-Forwarded-For {remote} header_up X-Remote-User {http.auth.user.id} } ''; diff --git a/modules/capabilities/services/vaultwarden.nix b/modules/capabilities/services/vaultwarden.nix index b4245e8..cf6a1f6 100644 --- a/modules/capabilities/services/vaultwarden.nix +++ b/modules/capabilities/services/vaultwarden.nix @@ -1,11 +1,10 @@ { config, ... }: let service = config.facts.services.vaultwarden; - nixosModules = config.flake.modules.nixos; in { flake.modules.nixos.vaultwarden = { - imports = [ nixosModules.caddy ]; + imports = [ config.flake.modules.nixos.caddy ]; services.vaultwarden = { enable = true; diff --git a/modules/capabilities/services/vps-insights.nix b/modules/capabilities/services/vps-insights.nix deleted file mode 100644 index 9440516..0000000 --- a/modules/capabilities/services/vps-insights.nix +++ /dev/null @@ -1,222 +0,0 @@ -{ ... }: -{ - flake.modules.nixos.vps-insights = - { - config, - lib, - pkgs, - ... - }: - let - localAddress = "127.0.0.1"; - grafanaDataDir = config.services.grafana.dataDir; - lokiDataDir = config.services.loki.dataDir; - lokiUrl = "http://${localAddress}:3100"; - prometheusUrl = "http://${localAddress}:9090"; - hasCaddy = config.services.caddy.enable; - hasOpenSSH = config.services.openssh.enable; - in - { - environment = { - etc."alloy/config.alloy".text = '' - loki.relabel "journal" { - forward_to = [] - - rule { - source_labels = ["__journal__systemd_unit"] - target_label = "unit" - } - - rule { - source_labels = ["__journal_syslog_identifier"] - target_label = "syslog_identifier" - } - - rule { - source_labels = ["__journal_priority_keyword"] - target_label = "level" - } - } - - loki.source.journal "system" { - forward_to = [loki.write.local.receiver] - relabel_rules = loki.relabel.journal.rules - max_age = "24h" - labels = { - host = "${config.networking.hostName}", - } - } - - loki.write "local" { - endpoint { - url = "${lokiUrl}/loki/api/v1/push" - } - } - ''; - - systemPackages = with pkgs; [ - goaccess - lynis - ]; - }; - - services = { - # Keep local system logs available for Loki and manual inspection. - journald.extraConfig = '' - Storage=persistent - SystemMaxUse=1G - MaxRetentionSec=30day - ''; - - # Detect and block common attacks against SSH and Caddy. - crowdsec = { - enable = true; - hub.collections = [ "crowdsecurity/linux" ] ++ lib.optional hasCaddy "crowdsecurity/caddy"; - localConfig.acquisitions = - lib.optional hasOpenSSH { - source = "journalctl"; - journalctl_filter = [ "_SYSTEMD_UNIT=sshd.service" ]; - labels.type = "syslog"; - } - ++ lib.optional hasCaddy { - filenames = [ "/var/log/caddy/*.log" ]; - labels.type = "caddy"; - }; - }; - - crowdsec-firewall-bouncer = { - enable = true; - registerBouncer.bouncerName = "${config.networking.hostName}-firewall-bouncer"; - }; - - # Keep Grafana local and separate from Actual's port. - grafana = { - enable = true; - settings = { - analytics.reporting_enabled = false; - server = { - http_addr = localAddress; - http_port = 3002; - }; - security = { - admin_password = "$__file{${grafanaDataDir}/admin-password}"; - secret_key = "$__file{${grafanaDataDir}/secret-key}"; - }; - }; - provision.datasources.settings = { - prune = true; - datasources = [ - { - name = "Prometheus"; - type = "prometheus"; - uid = "prometheus"; - url = prometheusUrl; - isDefault = true; - } - { - name = "Loki"; - type = "loki"; - uid = "loki"; - url = lokiUrl; - } - ]; - }; - }; - - # Store local logs in Loki and feed them from journald through Alloy. - loki = { - enable = true; - configuration = { - analytics.reporting_enabled = false; - auth_enabled = false; - - server = { - http_listen_address = localAddress; - http_listen_port = 3100; - grpc_listen_address = localAddress; - grpc_listen_port = 9096; - }; - - common = { - path_prefix = lokiDataDir; - replication_factor = 1; - instance_interface_names = [ "lo" ]; - ring = { - instance_addr = localAddress; - kvstore.store = "inmemory"; - }; - }; - - schema_config.configs = [ - { - from = "2025-01-01"; - store = "tsdb"; - object_store = "filesystem"; - schema = "v13"; - index = { - prefix = "index_"; - period = "24h"; - }; - } - ]; - - storage_config.filesystem.directory = "${lokiDataDir}/chunks"; - - compactor = { - working_directory = "${lokiDataDir}/compactor"; - retention_enabled = true; - delete_request_store = "filesystem"; - }; - - limits_config.retention_period = "720h"; - }; - }; - - alloy = { - enable = true; - extraFlags = [ "--server.http.listen-addr=${localAddress}:12345" ]; - }; - - # Collect basic VPS health metrics for Grafana. - prometheus = { - enable = true; - listenAddress = localAddress; - retentionTime = "30d"; - scrapeConfigs = [ - { - job_name = "prometheus"; - static_configs = [ - { - targets = [ "${localAddress}:9090" ]; - } - ]; - } - { - job_name = "node"; - static_configs = [ - { - targets = [ "${localAddress}:9100" ]; - } - ]; - } - ]; - exporters.node = { - enable = true; - listenAddress = localAddress; - }; - }; - }; - - systemd.services.grafana.preStart = '' - umask 077 - - if [ ! -s ${grafanaDataDir}/admin-password ]; then - ${pkgs.openssl}/bin/openssl rand -base64 32 > ${grafanaDataDir}/admin-password - fi - - if [ ! -s ${grafanaDataDir}/secret-key ]; then - ${pkgs.openssl}/bin/openssl rand -hex 32 > ${grafanaDataDir}/secret-key - fi - ''; - }; -} diff --git a/modules/capabilities/shell.nix b/modules/capabilities/shell.nix index b09189b..c496413 100644 --- a/modules/capabilities/shell.nix +++ b/modules/capabilities/shell.nix @@ -22,7 +22,6 @@ enable = true; dotDir = "${config.xdg.configHome}/zsh"; - enableCompletion = true; completionInit = '' autoload -U compinit compinit -C @@ -50,16 +49,7 @@ historySubstringSearch.enable = true; - history = { - ignoreDups = true; - save = 10000; - size = 10000; - path = "${config.xdg.dataHome}/zsh_history"; - }; - - profileExtra = lib.optionalString (config.home.sessionPath != [ ]) '' - export PATH="$PATH''${PATH:+:}${lib.concatStringsSep ":" config.home.sessionPath}" - ''; + history.path = "${config.xdg.dataHome}/zsh_history"; initContent = '' source <(gardenctl rc zsh --no-completion) @@ -118,19 +108,8 @@ programs.starship = { enable = true; - enableZshIntegration = true; settings = { - add_newline = true; - - format = lib.concatStrings [ - "$nix_shell" - "$hostname" - "$directory" - "$git_branch" - "$git_state" - "$line_break" - "$character" - ]; + format = "$nix_shell$hostname$directory$git_branch$git_state$line_break$character"; directory = { truncation_length = 99; @@ -147,8 +126,6 @@ format = "([$state( $progress_current/$progress_total)]($style)) "; style = "bright-black"; }; - - line_break.disabled = false; }; }; @@ -157,15 +134,9 @@ extraOptions = [ "--group-directories-first" ]; }; - programs.zoxide = { - enable = true; - enableZshIntegration = true; - }; + programs.zoxide.enable = true; - programs.fzf = { - enable = true; - enableZshIntegration = true; - }; + programs.fzf.enable = true; programs.bottom.enable = true; diff --git a/modules/capabilities/terminal.nix b/modules/capabilities/terminal.nix index 4bcdbbf..4cdee4e 100644 --- a/modules/capabilities/terminal.nix +++ b/modules/capabilities/terminal.nix @@ -1,30 +1,24 @@ { lib, config, ... }: let - kanagawa = import ./_kanagawa.nix; homeModules = config.flake.modules.homeManager; - - kittySingleInstance = - pkgs: - pkgs.symlinkJoin { - name = "kitty-single"; - paths = [ pkgs.kitty ]; - meta = pkgs.kitty.meta; - buildInputs = [ pkgs.makeWrapper ]; - postBuild = '' - wrapProgram $out/bin/kitty --add-flags "-1" - rm $out/share/applications/kitty.desktop - sed 's/^Exec=kitty/Exec=kitty -1/' ${pkgs.kitty}/share/applications/kitty.desktop > $out/share/applications/kitty.desktop - ''; - }; - in { flake.modules.homeManager.terminal-kitty = { pkgs, ... }: let - theme = kanagawa; + theme = import ./_kanagawa.nix; palette = theme.palette; - kitty = kittySingleInstance pkgs; + kitty = pkgs.symlinkJoin { + name = "kitty-single"; + paths = [ pkgs.kitty ]; + meta = pkgs.kitty.meta; + buildInputs = [ pkgs.makeWrapper ]; + postBuild = '' + wrapProgram $out/bin/kitty --add-flags "-1" + rm $out/share/applications/kitty.desktop + sed 's/^Exec=kitty/Exec=kitty -1/' ${pkgs.kitty}/share/applications/kitty.desktop > $out/share/applications/kitty.desktop + ''; + }; in { programs.kitty = { diff --git a/modules/capabilities/theme.nix b/modules/capabilities/theme.nix index f1e47fe..9d9aea6 100644 --- a/modules/capabilities/theme.nix +++ b/modules/capabilities/theme.nix @@ -1,28 +1,21 @@ { config, ... }: let - hmModules = config.flake.modules.homeManager; cursor = { name = "phinger-cursors-light"; - packageName = "phinger-cursors"; size = 24; }; - mkCursor = pkgs: cursor // { package = pkgs.${cursor.packageName}; }; in { flake.modules.nixos.theme = { pkgs, ... }: - let - cursorTheme = mkCursor pkgs; - in { - home-manager.sharedModules = [ hmModules.theme ]; - environment.systemPackages = [ cursorTheme.package ]; + home-manager.sharedModules = [ config.flake.modules.homeManager.theme ]; + environment.systemPackages = [ pkgs.phinger-cursors ]; }; flake.modules.homeManager.theme = { pkgs, ... }: let - cursorTheme = mkCursor pkgs; version = "unstable-2025-10-23"; src = pkgs.fetchFromGitHub { owner = "Fausto-Korpsvart"; @@ -52,13 +45,9 @@ in }; in { - home.pointerCursor = { + home.pointerCursor = cursor // { enable = true; - inherit (cursorTheme) - name - package - size - ; + package = pkgs.phinger-cursors; dotIcons.enable = false; gtk.enable = true; }; diff --git a/modules/capabilities/transcription.nix b/modules/capabilities/transcription.nix index 5daf6d6..9de4345 100644 --- a/modules/capabilities/transcription.nix +++ b/modules/capabilities/transcription.nix @@ -1,7 +1,4 @@ { config, ... }: -let - homeModules = config.flake.modules.homeManager; -in { flake.modules.homeManager.transcription = { @@ -111,7 +108,6 @@ in "TRANSCRIBE_DIARIZATION_MODEL=${diarizationModel}" "TRANSCRIBE_DIARIZATION_MODEL_NAME=${diarizationModelName}" "TRANSCRIBE_DIARIZATION_MODEL_REVISION=${diarizationModelRevision}" - "TRANSCRIBE_SETTLE_SECONDS=300" "TRANSCRIBE_SYNC_ROOT=${home}/sync" ]; Restart = "on-failure"; @@ -124,18 +120,13 @@ in systemd.user.paths.transcribe-recordings = { Unit.Description = "Watch for synchronized recordings"; - Path = { - PathChanged = "${home}/sync/recordings/inbox"; - Unit = "transcribe-recordings.service"; - }; + Path.PathChanged = "${home}/sync/recordings/inbox"; Install.WantedBy = [ "paths.target" ]; }; }; - flake.modules.nixos.transcription = - { ... }: - { - home-manager.sharedModules = [ homeModules.transcription ]; - }; + flake.modules.nixos.transcription = { + home-manager.sharedModules = [ config.flake.modules.homeManager.transcription ]; + }; } diff --git a/modules/capabilities/walker.nix b/modules/capabilities/walker.nix index bbc35dd..9613d46 100644 --- a/modules/capabilities/walker.nix +++ b/modules/capabilities/walker.nix @@ -1,10 +1,7 @@ { config, inputs, ... }: -let - homeModules = config.flake.modules.homeManager; -in { flake.modules.nixos.walker = { - home-manager.sharedModules = [ homeModules.walker ]; + home-manager.sharedModules = [ config.flake.modules.homeManager.walker ]; nix.settings = { extra-substituters = [ diff --git a/modules/capabilities/yookr-dev.nix b/modules/capabilities/yookr-dev.nix index 4f97ed7..f671267 100644 --- a/modules/capabilities/yookr-dev.nix +++ b/modules/capabilities/yookr-dev.nix @@ -1,20 +1,18 @@ { - flake.modules.nixos.yookr-dev = - { config, ... }: - { - security.pki.certificateFiles = [ - ./yookr-dev/zenith-rootCA.pem - ./yookr-dev/polaris-rootCA.pem - ]; + flake.modules.nixos.yookr-dev = { + security.pki.certificateFiles = [ + ./yookr-dev/zenith-rootCA.pem + ./yookr-dev/polaris-rootCA.pem + ]; - networking.firewall.allowedTCPPorts = [ - 5173 - 5174 - ]; + networking.firewall.allowedTCPPorts = [ + 5173 + 5174 + ]; - networking.extraHosts = '' - 127.0.0.1 local.management.yookr.org - 127.0.0.1 local.dashboard.yookr.org - ''; - }; + networking.extraHosts = '' + 127.0.0.1 local.management.yookr.org + 127.0.0.1 local.dashboard.yookr.org + ''; + }; } diff --git a/modules/facts.nix b/modules/facts.nix index a5e27c2..7238216 100644 --- a/modules/facts.nix +++ b/modules/facts.nix @@ -266,9 +266,7 @@ in machines topology ; - services = lib.mapAttrs (_: service: { - inherit (service) scheme domain; - }) osConfig.facts.services; + services = lib.mapAttrs (_: service: removeAttrs service [ "origin" ]) osConfig.facts.services; }; }; }; diff --git a/modules/flake-parts.nix b/modules/flake-parts.nix index cb08dc3..61e5028 100644 --- a/modules/flake-parts.nix +++ b/modules/flake-parts.nix @@ -7,15 +7,10 @@ let mkHost = name: machine: - { pkgs, ... }: + hostArgs@{ pkgs, ... }: let account = config.facts.account; - machineFacts = machine // { - inherit name; - }; - services = lib.mapAttrs (_: service: { - inherit (service) scheme domain; - }) config.facts.services; + services = lib.mapAttrs (_: service: removeAttrs service [ "origin" ]) config.facts.services; in { imports = [ @@ -30,7 +25,9 @@ let topology ; inherit services; - machine = machineFacts; + machine = machine // { + inherit name; + }; }; networking.hostName = name; @@ -38,9 +35,12 @@ let programs.zsh.enable = true; + sops.secrets.hashed-password.neededForUsers = true; + users.users.${account.name} = { isNormalUser = true; home = account.homeDirectory; + hashedPasswordFile = hostArgs.config.sops.secrets.hashed-password.path; extraGroups = [ "wheel" "networkmanager" diff --git a/modules/hosts/orion/_disk.nix b/modules/hosts/orion/_disk.nix index f9ddc32..8f4af0e 100644 --- a/modules/hosts/orion/_disk.nix +++ b/modules/hosts/orion/_disk.nix @@ -10,7 +10,6 @@ type = "gpt"; partitions = { boot = { - name = "boot"; size = "1M"; type = "EF02"; }; @@ -25,7 +24,6 @@ }; }; root = { - name = "root"; size = "100%"; content = { type = "lvm_pv"; @@ -43,7 +41,6 @@ type = "filesystem"; format = "ext4"; mountpoint = "/"; - mountOptions = [ "defaults" ]; }; }; }; diff --git a/modules/hosts/orion/_hardware.nix b/modules/hosts/orion/_hardware.nix index f8e495b..04c0f0a 100644 --- a/modules/hosts/orion/_hardware.nix +++ b/modules/hosts/orion/_hardware.nix @@ -7,16 +7,9 @@ boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" - "virtio_pci" - "virtio_scsi" "sd_mod" "sr_mod" ]; - boot.initrd.kernelModules = [ ]; - boot.kernelModules = [ ]; - boot.extraModulePackages = [ ]; - - networking.useDHCP = lib.mkDefault true; nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; } diff --git a/modules/hosts/orion/default.nix b/modules/hosts/orion/default.nix index a0c6d6b..6f5a504 100644 --- a/modules/hosts/orion/default.nix +++ b/modules/hosts/orion/default.nix @@ -1,7 +1,6 @@ { config, ... }: let nixosModules = config.flake.modules.nixos; - account = config.facts.account; in { facts.machines.orion = { @@ -11,29 +10,26 @@ in stateVersion = "24.05"; }; - flake.modules.nixos.orion = - { ... }: - { - imports = [ - nixosModules.host-base + flake.modules.nixos.orion = { + imports = [ + nixosModules.host-base - nixosModules.sops-host-ssh-key - nixosModules.openssh - nixosModules.caddy - nixosModules.server-firewall - nixosModules.sudo-ssh-agent-auth - # nixosModules.vps-insights - nixosModules.vaultwarden - nixosModules.radicale - nixosModules.actual - nixosModules.gitea - nixosModules.transcription - ./_hardware.nix - ./_disk.nix - ]; + nixosModules.sops-host-ssh-key + nixosModules.openssh + nixosModules.caddy + nixosModules.server-firewall + nixosModules.sudo-ssh-agent-auth + nixosModules.vaultwarden + nixosModules.radicale + nixosModules.actual + nixosModules.gitea + nixosModules.transcription + ./_hardware.nix + ./_disk.nix + ]; - environment.enableAllTerminfo = true; + environment.enableAllTerminfo = true; - users.users.${account.name}.linger = true; - }; + users.users.${config.facts.account.name}.linger = true; + }; } diff --git a/modules/hosts/polaris/_hardware.nix b/modules/hosts/polaris/_hardware.nix index 6eea03a..2e21116 100644 --- a/modules/hosts/polaris/_hardware.nix +++ b/modules/hosts/polaris/_hardware.nix @@ -1,9 +1,4 @@ -{ - config, - lib, - modulesPath, - ... -}: +{ lib, modulesPath, ... }: { imports = [ (modulesPath + "/installer/scan/not-detected.nix") @@ -17,9 +12,7 @@ "usb_storage" "sd_mod" ]; - boot.initrd.kernelModules = [ ]; boot.kernelModules = [ "kvm-amd" ]; - boot.extraModulePackages = [ ]; fileSystems."/" = { device = "/dev/disk/by-uuid/bda7f8b9-2b3d-4190-8518-baa50490227e"; @@ -35,10 +28,5 @@ ]; }; - swapDevices = [ ]; - - networking.useDHCP = lib.mkDefault true; - nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; - hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware; } diff --git a/modules/hosts/polaris/default.nix b/modules/hosts/polaris/default.nix index 509c898..e1f85c6 100644 --- a/modules/hosts/polaris/default.nix +++ b/modules/hosts/polaris/default.nix @@ -9,48 +9,40 @@ in { facts.machines.polaris = { system = "x86_64-linux"; - sshKeys = { - personal.publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEVORk45HKkX7gaGGp90KsVyUy6t+fKhbWN/grjkf3cQ kiri@polaris"; - work.publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM5DMV6EQzsscgEOE0912mNglUHTEl+LPnaWYjj0y57B kiri@polaris#work"; - }; + sshKeys.personal.publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEVORk45HKkX7gaGGp90KsVyUy6t+fKhbWN/grjkf3cQ kiri@polaris"; + sshKeys.work.publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM5DMV6EQzsscgEOE0912mNglUHTEl+LPnaWYjj0y57B kiri@polaris#work"; syncthingId = "6HBAKXB-DB3B4H2-BODCAXF-KD23H5W-6X5LGLC-ZJHZHLG-7U7YMGO-BB6IXQ3"; stateVersion = "24.05"; displays = { - "LG Electronics LG ULTRAGEAR 103NTYT8R290" = { - x = 0; - y = 0; - }; + "LG Electronics LG ULTRAGEAR 103NTYT8R290" = { }; "LG Electronics LG ULTRAGEAR 103NTJJ8R332" = { primary = true; x = 2560; - y = 0; }; }; }; - flake.modules.nixos.polaris = - { ... }: - { - imports = [ - nixosModules.workstation-base - nixosModules.openssh - nixosModules.steam - ./_hardware.nix - ] - ++ (with inputs.nixos-hardware.nixosModules; [ - common-pc - common-pc-ssd - common-cpu-amd - common-gpu-amd - ]); + flake.modules.nixos.polaris = { + imports = [ + nixosModules.workstation-base + nixosModules.openssh + nixosModules.steam + ./_hardware.nix + ] + ++ (with inputs.nixos-hardware.nixosModules; [ + common-pc + common-pc-ssd + common-cpu-amd + common-gpu-amd + ]); - services.openssh.openFirewall = false; - networking.firewall.extraInputRules = '' - ip saddr 192.168.178.0/24 tcp dport 22 accept - ''; - }; + services.openssh.openFirewall = false; + networking.firewall.extraInputRules = '' + ip saddr 192.168.178.0/24 tcp dport 22 accept + ''; + }; } diff --git a/modules/hosts/zenith/_hardware.nix b/modules/hosts/zenith/_hardware.nix index 86a075e..e586bc2 100644 --- a/modules/hosts/zenith/_hardware.nix +++ b/modules/hosts/zenith/_hardware.nix @@ -10,9 +10,6 @@ "usb_storage" "sd_mod" ]; - boot.initrd.kernelModules = [ ]; - boot.kernelModules = [ "kvm-amd" ]; - boot.extraModulePackages = [ ]; fileSystems."/" = { device = "/dev/disk/by-uuid/6d8f6f33-c9d9-4e90-b496-d5b3ef5e9aeb"; @@ -28,9 +25,5 @@ ]; }; - swapDevices = [ ]; - - networking.useDHCP = lib.mkDefault true; - nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; } diff --git a/modules/hosts/zenith/default.nix b/modules/hosts/zenith/default.nix index 2eb9ef4..a89f047 100644 --- a/modules/hosts/zenith/default.nix +++ b/modules/hosts/zenith/default.nix @@ -10,10 +10,8 @@ in facts.machines.zenith = { system = "x86_64-linux"; - sshKeys = { - personal.publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIQy4k04gU7UpjBgyUQ57kUwxOdt79LvMCiCekXZeZhd kiri@zenith"; - work.publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIHJz5uHKm0/TiMNh/cmzrODHNZ8NgEEZe+47XnJwQGk kiri@zenith#work"; - }; + sshKeys.personal.publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIQy4k04gU7UpjBgyUQ57kUwxOdt79LvMCiCekXZeZhd kiri@zenith"; + sshKeys.work.publicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIHJz5uHKm0/TiMNh/cmzrODHNZ8NgEEZe+47XnJwQGk kiri@zenith#work"; syncthingId = "5VMGBZJ-NNAVCLY-YWHCJLM-EJ3SCFC-EOGCECD-DGXC7EL-J5NXAAG-4JDEMQG"; @@ -30,18 +28,14 @@ in stateVersion = "24.05"; }; - flake.modules.nixos.zenith = - { ... }: - { - imports = [ - nixosModules.workstation-base - nixosModules.laptop-power - { - hardware.enableRedistributableFirmware = true; - services.fwupd.enable = true; - } - ./_hardware.nix - inputs.nixos-hardware.nixosModules.lenovo-yoga-7-14ARH7-amdgpu - ]; - }; + flake.modules.nixos.zenith = { + imports = [ + nixosModules.workstation-base + nixosModules.laptop-power + ./_hardware.nix + inputs.nixos-hardware.nixosModules.lenovo-yoga-7-14ARH7-amdgpu + ]; + + services.fwupd.enable = true; + }; } diff --git a/modules/profiles/host-base.nix b/modules/profiles/host-base.nix index 9c79880..72b88c2 100644 --- a/modules/profiles/host-base.nix +++ b/modules/profiles/host-base.nix @@ -14,7 +14,7 @@ in inputs.home-manager.nixosModules.home-manager nixosModules.nix nixosModules.locale - nixosModules.sops-password + nixosModules.sops ]; home-manager = { diff --git a/modules/profiles/workstation-base.nix b/modules/profiles/workstation-base.nix index 1e5f593..dddbe3d 100644 --- a/modules/profiles/workstation-base.nix +++ b/modules/profiles/workstation-base.nix @@ -2,7 +2,6 @@ let nixosModules = config.flake.modules.nixos; homeModules = config.flake.modules.homeManager; - account = config.facts.account; in { flake.modules.nixos.workstation-base = { @@ -32,12 +31,10 @@ in nixosModules.yookr-dev ]; - services.dbus.implementation = "broker"; - programs.nix-ld.enable = true; environment.localBinInPath = true; - home-manager.users.${account.name}.imports = [ + home-manager.users.${config.facts.account.name}.imports = [ homeModules.workstation-base ]; }; diff --git a/modules/secrets/.sops.yaml b/modules/secrets/.sops.yaml index b573b6f..feab5a8 100644 --- a/modules/secrets/.sops.yaml +++ b/modules/secrets/.sops.yaml @@ -1,9 +1,5 @@ -keys: - - &admin age122w85pqj508ukv0rd388mahecgfckmpgnsgz0zcyec37ljae2epsdnvxpl - - &orion age1upsxyevxdv3xax8jhnffclryuve70k63cjrca69ffds4h2m3nvuql59ufw creation_rules: - path_regex: secrets.yaml$ - key_groups: - - age: - - *admin - - *orion + age: >- + age122w85pqj508ukv0rd388mahecgfckmpgnsgz0zcyec37ljae2epsdnvxpl, + age1upsxyevxdv3xax8jhnffclryuve70k63cjrca69ffds4h2m3nvuql59ufw diff --git a/modules/secrets/sops-password.nix b/modules/secrets/sops-password.nix deleted file mode 100644 index 9d4160a..0000000 --- a/modules/secrets/sops-password.nix +++ /dev/null @@ -1,16 +0,0 @@ -{ config, ... }: -let - account = config.facts.account; - nixosModules = config.flake.modules.nixos; -in -{ - flake.modules.nixos.sops-password = - { config, ... }: - { - imports = [ nixosModules.sops ]; - - sops.secrets.hashed-password.neededForUsers = true; - - users.users.${account.name}.hashedPasswordFile = config.sops.secrets.hashed-password.path; - }; -} diff --git a/modules/secrets/sops.nix b/modules/secrets/sops.nix index da6982c..e3c2334 100644 --- a/modules/secrets/sops.nix +++ b/modules/secrets/sops.nix @@ -15,23 +15,18 @@ in sops.defaultSopsFile = ./secrets.yaml; }; - flake.modules.nixos.sops-admin-key-file = - { lib, ... }: - let - adminKeyDir = builtins.dirOf sopsAdminKeyPath; - in - { - imports = [ nixosModules.sops ]; + flake.modules.nixos.sops-admin-key-file = { + imports = [ nixosModules.sops ]; - home-manager.sharedModules = [ hmModules.sops ]; + home-manager.sharedModules = [ hmModules.sops ]; - sops.age.keyFile = sopsAdminKeyPath; + sops.age.keyFile = sopsAdminKeyPath; - systemd.tmpfiles.rules = [ - "d ${adminKeyDir} 0750 root wheel -" - "z ${sopsAdminKeyPath} 0640 root wheel -" - ]; - }; + systemd.tmpfiles.rules = [ + "d ${builtins.dirOf sopsAdminKeyPath} 0750 root wheel -" + "z ${sopsAdminKeyPath} 0640 root wheel -" + ]; + }; flake.modules.nixos.sops-host-ssh-key = { imports = [ nixosModules.sops ];