Compare commits
23
Commits
9beccd31c3
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae4a4f0824 | ||
|
|
8d4aa87dd0 | ||
|
|
71aad7fc55 | ||
|
|
bd287a324a | ||
|
|
c671bf0bfe | ||
|
|
c9cd45e712 | ||
|
|
e887fcd435 | ||
|
|
98a9be3615 | ||
|
|
9c3b7c3735 | ||
|
|
5748b3409b | ||
|
|
2ef5f4af8f | ||
|
|
f164d8b76e | ||
|
|
3c022ad2f9 | ||
|
|
9275abcdae | ||
|
|
ba791e856a | ||
|
|
161311bbd0 | ||
|
|
08ae8b7882 | ||
|
|
7f37c01ef4 | ||
|
|
d3c9cbb457 | ||
|
|
78051cc24f | ||
|
|
e2513fdf00 | ||
|
|
bdc539046c | ||
|
|
78887f6e7d |
No files matched your search
@@ -0,0 +1,20 @@
|
||||
# Working in this repository
|
||||
|
||||
Read [the design](docs/design.md) and [current work](docs/current-work.md) for
|
||||
repository decisions and active work. [README.md](README.md) maps configuration owners
|
||||
and maintenance entry points. Global instructions remain applicable.
|
||||
|
||||
- Preserve unrelated changes. Coherent local commits are allowed for agreed batches.
|
||||
Do not push or make other remote changes without explicit approval. System
|
||||
activation remains user-owned.
|
||||
- Do not read secret values for discovery, source the workbench credential-loading
|
||||
environment, or access live company systems with write authority. Test writes need
|
||||
task-specific authority. Moving credentials or synchronizing company content
|
||||
requires agreement; ordinary repository work does not authorize either.
|
||||
- Do not write or run tests or perform extensive validation unless explicitly
|
||||
requested. For routine changes, use a focused review or small manual check.
|
||||
- Run `just validate` only when explicitly requested. Read its short result first;
|
||||
inspect saved logs only for failures or a concrete unresolved concern.
|
||||
- Replace superseded state in `docs/current-work.md`; keep durable rationale in
|
||||
`docs/design.md` and operational detail beside its owner. Use Git for chronology.
|
||||
Remove temporary state when it no longer serves a purpose.
|
||||
@@ -1,24 +1,97 @@
|
||||
# NixOS configuration
|
||||
|
||||
Host identity and topology live in `modules/hosts/<host>/default.nix`; shared account,
|
||||
service, external-device, and server-topology facts live in `modules/facts.nix`.
|
||||
One shared daily environment on Polaris and Zenith, with shared services on Orion.
|
||||
NixOS and Home Manager are composed through flake-parts and import-tree.
|
||||
|
||||
- Add a package used by both workstations to the relevant Home Manager capability,
|
||||
or to `workstation-apps` in `modules/capabilities/desktop-defaults.nix` when it needs
|
||||
no configuration.
|
||||
- Add a host-specific capability to that host's import list in
|
||||
`modules/hosts/<host>/default.nix`.
|
||||
- Change the selected terminal or editor by replacing the corresponding
|
||||
`primary-terminal-*` import in `modules/profiles/workstation-base.nix` or
|
||||
`primary-editor-*` import in `modules/profiles/host-base.nix`.
|
||||
- Keep native theme settings with the application. Explicit Kanagawa color mappings
|
||||
may import `modules/capabilities/_kanagawa.nix`.
|
||||
## Start here
|
||||
|
||||
Common commands:
|
||||
- [Design](docs/design.md): vocabulary, ownership and coding conventions.
|
||||
- [Agent instructions](AGENTS.md): essential collaboration boundaries.
|
||||
- [Current work](docs/current-work.md): implementation state, pending decisions and
|
||||
remaining acceptance checks.
|
||||
|
||||
## Configuration map
|
||||
|
||||
| Owner | Location | Responsibility |
|
||||
| --- | --- | --- |
|
||||
| Hosts | `modules/hosts/<host>/` | Identity, hardware, machine-specific selection |
|
||||
| Profiles | `modules/profiles/` | Combinations of capabilities |
|
||||
| Capabilities | `modules/capabilities/` | Behavior and NixOS/Home Manager integration |
|
||||
| Facts | `modules/facts.nix` | Shared identity/topology; host facts live with hosts |
|
||||
| Composition | `flake.nix`, `modules/flake-parts.nix` | Inputs, discovery, host construction |
|
||||
| Secrets | `modules/secrets/` | SOPS declarations and encrypted documents |
|
||||
|
||||
Discovery makes named modules available; imports select them. Flat owners use
|
||||
`name.nix` or `name/default.nix`. Private Nix helpers live under `_support/`, excluded
|
||||
from discovery. Scripts, assets and operational notes stay beside their owner.
|
||||
Select shared behavior through profiles; keep machine differences in hosts.
|
||||
|
||||
The primary editor is selected in `host-base.nix`, the terminal in
|
||||
`workstation-base.nix`. `capabilities/theme/default.nix` selects Kanagawa;
|
||||
application appearance lives in `theme/_support/`, interaction with capabilities.
|
||||
|
||||
Both workstations import the provisional `work` profile. See
|
||||
[workbench setup](modules/capabilities/workbench/README.md) for native devenv,
|
||||
company credentials and always-available read-only mounts. Only company-root and
|
||||
workbench environments are migrated; product/personal projects remain user-owned.
|
||||
|
||||
Orion's wardrobe service has its own
|
||||
[setup and operations guide](modules/capabilities/wardrowbe/README.md).
|
||||
Provision its SOPS secrets and two DNS records before building or activating Orion.
|
||||
|
||||
## Maintenance
|
||||
|
||||
```console
|
||||
nix fmt
|
||||
nix flake check --no-build
|
||||
nh os switch
|
||||
sops edit modules/secrets/secrets.yaml
|
||||
just validate # format, evaluate, build all hosts and workbench tools
|
||||
just validate polaris # same workflow, build only the named host(s)
|
||||
nix flake update # update inputs; selective input updates are also supported
|
||||
```
|
||||
|
||||
Validation copies current tracked and nonignored new files into a temporary snapshot,
|
||||
respecting deletions and leaving the Git index untouched. It formats included Nix
|
||||
files in the working tree, refreshes the snapshot, prepares devenv's upstream
|
||||
evaluation dependencies, evaluates configurations and builds. Avoid concurrent edits:
|
||||
results describe that snapshot. Ordinary Git-backed flake commands outside the
|
||||
wrapper still require new source files to be added to Git.
|
||||
|
||||
Read the short result first. Full logs live in the printed `/tmp/nixos-validate-*`
|
||||
directory. Failure stops the command; snapshots are removed on exit. It neither
|
||||
updates inputs nor creates result links or activates systems. The implementation is
|
||||
`scripts/validate.py`; `just` and Python are installed on both workstations.
|
||||
|
||||
Run targeted host builds or full validation only when explicitly requested. Routine
|
||||
changes need a focused review or small manual check. Build, activation and
|
||||
interactive behavior are separate evidence.
|
||||
|
||||
User-owned activation:
|
||||
|
||||
```console
|
||||
nh os switch
|
||||
nixos-rebuild switch --target-host orion --sudo --flake ~/.config/nixos
|
||||
```
|
||||
|
||||
Edit system ciphertext with `sops edit modules/secrets/secrets.yaml`; company
|
||||
provisioning is documented with workbench. Never put plaintext secrets in Nix.
|
||||
|
||||
## First-use acceptance
|
||||
|
||||
- **Notes:** Super+N and Walker's Notes open the same picker. Enter opens a note;
|
||||
Ctrl+E creates one using the search text. Daily-note commands remain available.
|
||||
- **Orientation:** Super+Shift+N names the focused workspace; submit an empty name
|
||||
to clear it. Named empty workspaces persist until cleared. Noctalia shows labels;
|
||||
terminal titles include the working directory and command.
|
||||
- **Icons/vault:** check Oil from Walker and Nautilus device/symbolic icons. Check
|
||||
Walker's `?` vault provider; upstream Elephant may query `rbw` during startup.
|
||||
- **Calendar:** in Noctalia Settings → Services → Calendar, set the Radicale
|
||||
password through the existing Personal account (GNOME Keyring). Add an ICS account
|
||||
named Work using Outlook's published URL. Keep this URL in writable Noctalia
|
||||
settings, outside this repository. Check both agendas; edit work events in Outlook.
|
||||
- **Tailnet:** run `sudo tailscale up` on each machine and enroll the phone with
|
||||
the Tailscale app. Keep Tailscale SSH disabled; use ordinary SSH keys. With
|
||||
MagicDNS enabled, verify `ssh kiri@orion.<tailnet-name>.ts.net` from a workstation.
|
||||
Existing `ssh orion` still uses the public address until the private route is
|
||||
accepted. Close public Orion SSH only in the subsequent verified transition.
|
||||
|
||||
No systems have been activated as part of this implementation. Old SharePoint
|
||||
mirror content, experimental AI data and unrelated workspace changes are preserved.
|
||||
Historical material under `docs/.archive/` is evidence, not current authority.
|
||||
@@ -1,294 +0,0 @@
|
||||
# Constellation: working state
|
||||
|
||||
Read [the design](constellation.md) and this file to resume. Keep this file short:
|
||||
replace superseded state rather than appending a chronological log.
|
||||
|
||||
## Working agreement
|
||||
|
||||
- Explore the philosophy through concrete use, discuss meaningful choices, then
|
||||
implement a small coherent change. Curiosity and alternative designs are welcome;
|
||||
an idea need not prove a defect before we explore it.
|
||||
- Write succinctly without losing important details. Distinguish accepted choices,
|
||||
proposals and observations. Ask when intent would change the design; resolve
|
||||
routine implementation details autonomously. Infer opportunities from the user's
|
||||
philosophy and actual use; contribute design ideas beyond literal backlog items.
|
||||
Continue to the next natural step when the user could only usefully say "continue";
|
||||
pause for material choices or missing authority, not routine progress.
|
||||
- Minimize input stops: plan a coherent batch of autonomous work and gather the
|
||||
material inputs needed for the next batch together. Avoid serial question/action
|
||||
loops. When pausing, state exactly what input is needed and what it will decide;
|
||||
otherwise continue with the next logical step rather than ending with an offer.
|
||||
- The main agent owns alignment, design and integration and may do small work.
|
||||
Use subagents for bounded independent investigations or implementation with clear
|
||||
ownership. Begin with at most two workers; avoid overlapping writes, duplicate
|
||||
investigations and mandatory delegation of trivial tasks.
|
||||
- Give workers the question, relevant paths, constraints and expected result.
|
||||
Prefer a compact brief and fresh context when sufficient; retain relevant prior
|
||||
context for follow-ups. Return conclusions, uncertainty and evidence pointers,
|
||||
not raw logs. Astra/Sol orchestration is a candidate to learn from, not a fixed rule.
|
||||
- Read only what can affect the current decision. Use quiet commands and bounded
|
||||
output; keep large logs in `/tmp` and inspect relevant failures. Do not repeatedly
|
||||
check settled facts. Consult current documentation for consequential or unfamiliar
|
||||
interfaces when needed for the task.
|
||||
- No tests unless explicitly requested. Keep other verification proportional;
|
||||
report only what was actually checked. Avoid turning small changes into campaigns.
|
||||
- Keep durable decisions in the design and current questions/next steps here.
|
||||
Add an area-specific document only when an active topic outgrows this arrangement.
|
||||
No separate backlog, scratchpad, audit ledger or agent-role files for now.
|
||||
|
||||
## Boundaries
|
||||
|
||||
Repository remotes are read-only. Only the user activates system generations.
|
||||
Agents must not have write access to live company systems; test writes require
|
||||
task-specific authority and care. Do not read secret values for layout discovery.
|
||||
Current exploration does not authorize relocating credentials, synchronizing
|
||||
company content or deleting existing material. Existing local changes are preserved.
|
||||
|
||||
## Git baseline — 2026-09-09
|
||||
|
||||
The accumulated NixOS changes are recorded in focused local commits on
|
||||
`chore/workflow-system`: AI tooling/inputs, editor ergonomics, query persistence
|
||||
and target context, workspace navigation, Notes, SharePoint, Zellij and TeamViewer.
|
||||
The original long handoff and archived plans are preserved in commit `4e3a3ac`;
|
||||
use Git history for chronology. `nvim.log` is retained locally and ignored.
|
||||
Workbench changes are committed on its local `main`: `eb8da6a` organizes HTTP/SQL
|
||||
material and `063157c` archives helpers and documents native tools. The ordinary
|
||||
company-root files are outside either repository and remain user-owned.
|
||||
This cleanup preserves implementation behavior; a commit does not establish runtime
|
||||
acceptance. Keep subsequent implementation and its documentation in the same
|
||||
coherent commit before starting unrelated work.
|
||||
|
||||
## Implemented state
|
||||
|
||||
- **Places:** `~/yookr` has ordinary `.envrc` and `AGENTS.md` files; its environment
|
||||
loads workbench tools only. Workbench is at `~/yookr/workbench`, company projects
|
||||
at `~/yookr/projects`, and personal groups at `~/projects/{own,ext,forks}`.
|
||||
Direct moves retained archives/worktrees; all three linked dashboard worktrees
|
||||
were repaired. Nix owns tools/integration, not these workspace files.
|
||||
- **Navigation:** Walker enters Yookr or a repository; Git identity follows the
|
||||
company root; XDG projects points to `~/projects`. Notes has a desktop entry
|
||||
opening `~/sync/notes` without creating a daily note. Existing note commands,
|
||||
project search and Niri daily-note behavior remain.
|
||||
- **Synchronization:** Bruno collections were previously moved to Trash on Zenith
|
||||
and their shared Syncthing entry removed. Other devices' files were untouched.
|
||||
Syncthing was subsequently observed running. Notes and credentials remain in place.
|
||||
- **API/database work:** workbench `http/` groups requests by service with shared
|
||||
authentication; `databases/` groups reusable queries by database. Private HTTP
|
||||
environment files are ignored at any depth. Dadbod working queries persist under
|
||||
`stdpath('data')/db_ui/working`; write them there without promoting them into Git.
|
||||
Saved-query storage also contains connection state and must stay outside Git.
|
||||
Lualine uses native Kulala environment and Dadbod connection/timing components.
|
||||
These indicators are not access controls.
|
||||
- **Editor ergonomics:** word-boundary wrapping, count-aware displayed-line `j/k`
|
||||
in ordinary normal/visual buffers, and default Escape behavior to leave insert
|
||||
mode. The Neovim smoke tests were removed; earlier smoke-check evidence is in
|
||||
the archived plan.
|
||||
- **Agents:** Herdr is selected; agent-deck and tmux remain installed alongside it.
|
||||
Codex has agent-deck notifications and hooks enabled. Shared Context7/NixOS skills
|
||||
live under `.agents/skills`.
|
||||
Zellij is also declared on workstations. Further project workflow work is deferred.
|
||||
- **SharePoint:** Zenith declares an hourly service syncing `company-sharepoint:`
|
||||
into `~/.local/share/yookr-reference/sharepoint/Algemeen` and
|
||||
`company-sharepoint-projecten:` into `Projecten`. Each invocation has
|
||||
`--max-delete 100`; new files use `UMask=0077`. The latest implementation only
|
||||
checks for `rclone.conf`; the old environment-file gate is gone. Archived setup
|
||||
instructions describe an earlier version. Consent, remote configuration and
|
||||
synchronization state were not inspected during this cleanup.
|
||||
- **Remote support:** Zenith enables TeamViewer. Temporary Xorg/Xfce settings are
|
||||
comments only. The purpose beyond remote support and runtime acceptance were
|
||||
not recorded; preserve the change without calling it a settled design.
|
||||
- **Inputs:** accumulated lock updates are retained, including an older Elephant
|
||||
revision (`376ee71c` replacing `23f37238`). No rollback rationale was recorded.
|
||||
|
||||
## Activation and verification
|
||||
|
||||
Earlier handoffs report user activation of the relocated XDG/Git/Walker paths and
|
||||
Dadbod working-query persistence. Notes and the newer target statusline were last
|
||||
recorded as pending activation. Only Zenith's filesystem was relocated; other hosts
|
||||
need their own migration before adopting the shared paths. Reopen shells or
|
||||
recreate path-dependent development environments as needed; no compatibility links
|
||||
or cache rewrites were introduced.
|
||||
|
||||
For this cleanup, `nix flake check --no-build --quiet` and diff whitespace checking
|
||||
passed in both NixOS and workbench. NixOS emitted the expected dirty-tree and unknown
|
||||
`modules` warnings; workbench emitted only the dirty-tree warning.
|
||||
No builds, tests, activation, credential reads or company-service calls were
|
||||
performed. Active-generation differences were not checked; earlier activation
|
||||
reports are not a fresh comparison with this baseline.
|
||||
|
||||
## Open choices and next discussion
|
||||
|
||||
Current focus: look and feel/theming, informed by `~/sync/notes/setup-todo.md`.
|
||||
The user accepted complete Nix theme modules with arbitrary plugin/configuration
|
||||
behavior, rather than Stylix or a palette-only abstraction. The single import in
|
||||
`modules/capabilities/theme.nix` selects `./_themes/kanagawa.nix`. `_themes` is
|
||||
excluded from automatic module discovery, so unselected themes are not loaded.
|
||||
Kanagawa owns GTK/icons/cursor, Qt integration, Kitty, Niri colors, Noctalia
|
||||
palette/font/wallpaper, Herdr colors, the native Neovim plugin and its setup, and
|
||||
Limine/SDDM/Plymouth appearance. Only Kanagawa is implemented so far; switching
|
||||
means selecting another module and rebuilding, not a live theme picker.
|
||||
Walker shares a compact layout with theme-provided colors: reduced padding,
|
||||
24px icons, clearer secondary text, and a smaller minimum window height.
|
||||
Limine's terminal background is fully transparent with no edge gradient so it
|
||||
does not obscure the wallpaper. Runtime appearance needs user verification.
|
||||
SDDM's purple-leaves theme and Plymouth's spinner are retained within Kanagawa;
|
||||
their visual redesign and the low-frame-rate boot animation remain open.
|
||||
Verification: flake evaluation and both workstation builds passed. A headless
|
||||
check of the built Neovim confirmed Kanagawa Wave, inactive-window dimming and
|
||||
the custom gutter setting. Rendered Herdr TOML, Walker XML and Limine settings
|
||||
also passed targeted checks. No system activation or live desktop changes performed.
|
||||
|
||||
Dictation: plain local transcription is selected; rambling cleanup is deferred.
|
||||
Handy preferences are application-owned again: the Nix settings generator and
|
||||
startup reset are removed. The existing settings file is left untouched.
|
||||
Nix installs Handy/wtype and runs Handy as a graphical-session user service.
|
||||
Super+D launches a short-lived Python/evdev listener: hold to record, release D
|
||||
or Super to finish. Both keys must be on the same keyboard. Niri owns the shortcut
|
||||
and disables repeat; no persistent keyboard logger or exclusive input grab runs.
|
||||
A udev uaccess rule grants the active local user access to keyboard event devices.
|
||||
Super+Shift+D toggles; Super+Ctrl+D cancels. Both stop any active hold listener
|
||||
before issuing their Handy command, preventing a later release from restarting
|
||||
recording. Begin push-to-talk from idle: Handy exposes toggles, not idempotent
|
||||
start/stop commands. Niri 26.04 itself has no key-release bindings.
|
||||
Noctalia suppresses microphone activity popups/indicators; camera and screen-sharing
|
||||
indications remain. Automatic paste and live capture need interactive checks.
|
||||
|
||||
Hang investigation, parked at the user's request: a debugger capture confirmed
|
||||
Handy's recorder waits for another audio chunk before processing its stop command.
|
||||
When capture stalls, stopping can therefore hang indefinitely; the transcription
|
||||
worker was idle. Bluetooth transport failures accompanied the hangs, and the user
|
||||
reported dictation working after disconnecting the Sony WH-1000XM6. The user wants
|
||||
its microphone when connected. Keeping the headset in a microphone-enabled HFP
|
||||
profile was proposed to avoid repeated profile transitions, but was not applied
|
||||
or verified; the reconnect attempt timed out. No source or Bluetooth configuration
|
||||
was changed. CPU and Vulkan batch inference both worked; changing inference backend
|
||||
is not an established fix. Resume this investigation only if requested.
|
||||
Synthetic listener checks cover release during startup, repeat, modifier-first
|
||||
release, device disconnect and cancellation, without live input/audio access.
|
||||
System activation and an interactive hold/release trial remain user-owned.
|
||||
Formatting, flake evaluation, both workstation builds, synthetic listener checks
|
||||
and whitespace checking passed. No live input listener or microphone was started.
|
||||
|
||||
The focus is the broader environment, not further API/database polish. Candidate
|
||||
areas remain places/ownership, navigation/interaction, human/agent collaboration,
|
||||
and system operation/maintenance; these are discussion areas, not a work queue.
|
||||
|
||||
Herdr's configured keys are accepted: the user reports liking them in practice.
|
||||
Niri workspace grouping stays informal while the user discovers what fits; Herdr
|
||||
provides enough structure for now. No workspace rules or fixed layout are needed.
|
||||
The user reopened Herdr configuration on 2026-09-10 for direct shortcuts and less
|
||||
interaction friction, using supported configuration only. Layout presets and
|
||||
launchers are explicitly unwanted; arrange panes manually. Agent evaluation is
|
||||
explicitly deferred. Do not resume replacement-tool comparisons or ask for an
|
||||
evaluation task/budget as a prerequisite for this work.
|
||||
|
||||
Read-only inspection found existing Niri overview, workspace movement and tabbed
|
||||
columns; Kitty has no declared project/session organization. Installed versions:
|
||||
Kitty 0.48.2, Niri 26.04, tmux 3.7c, Zellij 0.45.1 and Herdr 0.9.0.
|
||||
Herdr's installed default config confirms remappable navigation and agent jumps.
|
||||
Current [Herdr keyboard docs](https://herdr.dev/docs/keyboard/) describe direct
|
||||
chords; [Kitty sessions](https://sw.kovidgoyal.net/kitty/sessions/) supply native
|
||||
session creation/switching. Session layouts and window overviews do not themselves
|
||||
provide authoritative agent attention states. tmux detach keeps processes alive;
|
||||
Zellij resurrection recreates layouts/commands, not process memory. None of these
|
||||
capabilities was exercised interactively; no sessions were inspected or changed.
|
||||
|
||||
Deferred agent workflow proposal (not accepted or a Niri workspace requirement):
|
||||
one lead conversation owns each coherent change. Use bounded subagents when work
|
||||
is independent, with explicit ownership and evidence returned to the lead. Separate
|
||||
worktrees for independent writing efforts, one integration owner. Keep enduring
|
||||
repository rules in AGENTS.md, current goals/acceptance in task prompts, and durable
|
||||
decisions/current state in existing project docs. No fixed agent team is selected.
|
||||
|
||||
Herdr configuration is now declared in `modules/capabilities/ai.nix`, including
|
||||
the existing Kanagawa theme, workspace-sorted agent panel and disabled toasts.
|
||||
Home Manager owns `~/.config/herdr/config.toml` after activation; maintain these
|
||||
preferences in Nix. Its configured `.bak` mechanism preserves the previous regular
|
||||
file. Direct Ctrl+Alt bindings supplement the existing Ctrl+B prefix shortcuts:
|
||||
|
||||
| Ctrl+Alt + | Action |
|
||||
| --- | --- |
|
||||
| H / J / K / L | Focus pane left / down / up / right |
|
||||
| U / I | Next / previous workspace |
|
||||
| O / G | Workspace picker / goto picker |
|
||||
| P / N | Previous / next agent |
|
||||
| [ / ] | Previous / next tab |
|
||||
| Enter | New tab |
|
||||
| V / Minus | Split right / down |
|
||||
| F | Zoom pane |
|
||||
| Q | Detach, leaving the session running |
|
||||
| Shift+H / J / K / L | Swap panes left / down / up / right (Shift on each) |
|
||||
| Shift+arrows | Resize pane directly |
|
||||
| R | Resize mode |
|
||||
| Tab / Shift+Tab | Next / previous pane |
|
||||
| Backspace | Return to last pane, including across tabs/spaces |
|
||||
| X / Shift+X | Close pane / tab |
|
||||
| Shift+P / Shift+A | Rename pane / tab |
|
||||
| Shift+[ / Shift+] | Move tab earlier / later |
|
||||
| Shift+Enter | New space |
|
||||
| Shift+W / Shift+D | Rename / close space |
|
||||
| W / Shift+B | Open / create worktree |
|
||||
| Y / E | Copy mode / open scrollback in Neovim |
|
||||
| Shift+G | Lazygit popup |
|
||||
| B | Toggle sidebar |
|
||||
| / | Keybinding help |
|
||||
| S / Shift+R | Settings / reload configuration |
|
||||
|
||||
Inside workspace navigation, U/I moves down/up and H/J/K/L retains pane navigation.
|
||||
Niri and Kitty bindings are unchanged. Direct number bindings are removed because
|
||||
the user finds them unergonomic; built-in prefix shortcuts remain available.
|
||||
|
||||
Use one space per project or worktree, tabs for different activities, and panes
|
||||
for tools needed together. Start Neovim in a shell, split right with Ctrl+Alt+V
|
||||
for an agent shell, and optionally split that pane down with Ctrl+Alt+Minus for
|
||||
tests or a server. Choose and launch the agent manually. No preset is installed.
|
||||
Ctrl+Alt+Shift+G opens lazygit at 90% width/height in the focused pane's working
|
||||
directory. Quit lazygit to return; the popup owns input until its command exits.
|
||||
|
||||
Tabs now open immediately with generated names; rename them only when useful.
|
||||
New spaces already skip naming, and new panes inherit the current directory by
|
||||
default. Workspace-close confirmation remains enabled to protect running work.
|
||||
Direct resizing, previous/next agent navigation, last-pane switching, copy mode,
|
||||
and Neovim scrollback access avoid extra navigation or mode-entry steps.
|
||||
No documented native setting or CLI action opens Goto directly in search mode;
|
||||
that limitation remains under the configuration-only constraint. No input macros,
|
||||
plugins, or package patches are added.
|
||||
|
||||
After activating Nix, reload Herdr with Ctrl+B then Shift+R; subsequent reloads
|
||||
can use Ctrl+Alt+Shift+R. The expanded shortcuts require interactive verification
|
||||
through Kitty, including popup working directory, pane swapping/resizing, and
|
||||
scrollback opening. The earlier bindings were reported working by the user.
|
||||
Verification (2026-09-10): formatting, flake evaluation, Polaris and Zenith no-link
|
||||
system builds, and whitespace checks passed. Both hosts generate identical Herdr
|
||||
TOML, accepted by installed Herdr 0.9.0's `config check`. No activation or live
|
||||
Herdr session changes were performed.
|
||||
The earlier activation installed the Herdr config and preserved `config.toml.bak`,
|
||||
but Home Manager later timed out (below).
|
||||
|
||||
Activation fix (2026-09-09): Home Manager's service switch stopped and restarted
|
||||
the running SharePoint oneshot, then waited until Home Manager's five-minute start
|
||||
timeout. Service-state inspection identified SharePoint as the sole pending user
|
||||
job; its oneshot start timeout is unlimited. This was not a Herdr file collision.
|
||||
Set `Unit.X-SwitchMethod = "keep-old"` in `sharepoint-reference.nix` so activation
|
||||
leaves an in-flight sync running; subsequent timer runs use the updated unit.
|
||||
Verified against pinned Home Manager/sd-switch source. Formatting, flake evaluation,
|
||||
Zenith build and whitespace checks passed; the focused sd-switch dry run reported
|
||||
`Keep active sharepoint-reference-sync.service`, with no restart planned. No live
|
||||
units were changed and no company content or credentials were read. The user must
|
||||
retry activation to verify the full switch; sync health/content completeness were
|
||||
not investigated.
|
||||
|
||||
Mail search remains unresolved. The workbench's custom mail reader, inventory
|
||||
pilot, justfile and old README are archived; native Garden/kubectl/K9s now own the
|
||||
documented operations. No replacement mail helper or semantic index is selected;
|
||||
raw mailbox grep is not assumed sufficient. No new ingestion is authorized.
|
||||
|
||||
Human credential delivery remains both test/live via environment variables with
|
||||
ordinary editor startup. Workbench `.envrc` supplies aggregate credentials; agents
|
||||
must not source it. Separate read-only agent credentials and access isolation are
|
||||
explicitly deferred, not prerequisites for unrelated work. Do not resume the
|
||||
rejected per-connection launchers or per-selection decryption proposal.
|
||||
|
||||
Notes placement and further company material organization remain open. Work is
|
||||
mostly project-based and notes mostly meetings; do not impose investigation
|
||||
folders, outer project containers or company-wide editor search without a need.
|
||||
@@ -1,98 +0,0 @@
|
||||
# Constellation
|
||||
|
||||
An evolving personal computing environment. This is the current design, starting
|
||||
fresh with iteration two; decisions can change through discussion and use.
|
||||
See [working state](constellation-work.md) for how we collaborate and where to resume.
|
||||
|
||||
## Philosophy
|
||||
|
||||
Build a quiet, keyboard-driven, text-oriented environment from understandable
|
||||
tools that compose naturally. Keep information accessible through standard formats
|
||||
and interfaces, so humans and agents can work with the same underlying resources.
|
||||
Organize files and workflows consistently, making it intuitive where to look,
|
||||
where things belong and how to proceed. Let structure guide everyday work while
|
||||
leaving room for exploration and exceptions. Use graphical applications where
|
||||
they serve the task well.
|
||||
|
||||
Make the usual path obvious and exceptions understandable. Consistency should
|
||||
reduce remembering and decision-making without requiring identical bindings or
|
||||
fixed procedures everywhere. A terminal interface alone does not establish fit.
|
||||
|
||||
Nix configures installed tools and system integration. Working directories,
|
||||
checkouts, workspace `.envrc` files and context documents are ordinary user-owned
|
||||
files, maintained directly rather than generated by Home Manager. A workbench may
|
||||
use a flake to declare its tools without Nix owning the surrounding workspace.
|
||||
|
||||
## What that means so far
|
||||
|
||||
- Themes are complete Nix modules, not just palettes. Select one through
|
||||
`modules/capabilities/theme.nix`; it may install native theme plugins, configure
|
||||
their behavior and choose assets for each application. Kanagawa is the first
|
||||
implementation. Shared interaction/layout choices stay in capability modules.
|
||||
Stylix is not selected: preserving theme-specific behavior matters more than
|
||||
forcing applications through one generated color scheme.
|
||||
- Neovim, Niri, Walker and the terminal feel right: direct, keyboard-driven
|
||||
interaction with useful connections to the rest of the environment.
|
||||
- Use Handy for local plain dictation, mainly English with occasional Dutch.
|
||||
Use native Wayland automatic paste and optional live text with a streaming model.
|
||||
Handy owns its preferences, models and recordings. Nix installs and starts it
|
||||
and supplies desktop shortcuts, including a short-lived push-to-talk listener. Cleanup
|
||||
through Codex, a cloud API or a local language model is deferred.
|
||||
- Outlook is acceptable as an application, but its interaction and integration
|
||||
do not fit as naturally. Accessible mail for tools and agents matters separately
|
||||
from choosing a mail reader. Preserve meaning and source context, not just text.
|
||||
- Herdr is the chosen agent workspace manager: its spaces, running-agent overview
|
||||
and attention states fit. Its configured keybindings are accepted in daily use.
|
||||
Use Ctrl+Alt for direct Herdr actions alongside Super for Niri and Ctrl navigation
|
||||
in Neovim; retain Herdr's prefix bindings. Agent-deck's startup issues and other
|
||||
session-manager alternatives are not the current focus.
|
||||
- Keep Niri workspaces flexible. Herdr supplies enough structure that no strict
|
||||
desktop grouping, fixed workspace names or project-to-workspace mapping is
|
||||
needed now. Let actual use inform any later organization.
|
||||
- Further Herdr configuration, agent workflows and project management are deferred
|
||||
until the user chooses to revisit them. Existing configuration remains in use.
|
||||
Alignment, maintainability and human attention matter
|
||||
alongside speed/cost. Agent-workflow evaluation is deferred; no fixed delegation
|
||||
pattern has been selected.
|
||||
- Repositories should contain code and documentation needed by that code, not
|
||||
become containers for general notes and company material destined for Git remotes.
|
||||
- Company work includes repositories, reference material, notes, credentials and
|
||||
investigation tools. Its organization must accommodate those different roles.
|
||||
- Actual work is mostly project-based; notes are primarily meeting notes so far.
|
||||
Do not impose a topic/investigation workflow or a fixed future note taxonomy.
|
||||
- Yookr workbench is an investigation environment, not a product repository.
|
||||
Its placement and versioning can be considered separately.
|
||||
- Accepted direction: workbench supplies the environment at the company-workspace
|
||||
root. Its reusable requests live in `workbench/http/` and queries in
|
||||
`workbench/databases/`, grouped by service/database;
|
||||
the company root need not itself be a Git repository.
|
||||
- Expose native capabilities and useful context rather than a menu of predetermined
|
||||
investigations. The custom inventory and mail helpers are archived; native
|
||||
Garden/kubectl/K9s own the documented Kubernetes workflow. Mail retrieval and
|
||||
broader system access remain exploratory.
|
||||
- Accepted interaction direction: find HTTP requests by service; start database
|
||||
exploration from a named connection. Keep targets visible and use the existing
|
||||
execution vocabulary. Working queries should survive restart without automatically
|
||||
becoming reusable tracked material. Company-wide Neovim search is deferred: no
|
||||
concrete use case currently justifies adding it.
|
||||
- Human credential delivery uses environment variables with both test and live
|
||||
credentials available. Keep ordinary editor startup; no per-connection launcher
|
||||
or on-demand decryption workflow. Agent authority is a separate concern.
|
||||
- Separate read-only company credentials for agents are the eventual direction.
|
||||
Access isolation is explicitly deferred: new credentials cannot be made now.
|
||||
Existing agent access boundaries remain in force; this does not block unrelated
|
||||
design and organization work.
|
||||
|
||||
The company root is `~/yookr`, with repositories in `~/yookr/projects` and the
|
||||
cross-project workbench in `~/yookr/workbench`. Personal/non-company repositories
|
||||
live under `~/projects/{own,ext,forks}`. Projects currently mostly live in their
|
||||
repositories; no extra project containers are needed. Meeting notes remain in
|
||||
`~/sync/notes`. Remaining layout and credential locations are still being explored.
|
||||
|
||||
## Sources
|
||||
|
||||
`~/sync/notes/setup-todo.md` is the idea inbox, not a committed backlog.
|
||||
Existing implementation is the starting system, not a fixed future architecture.
|
||||
Documents under [.archive/](.archive/) are historical evidence; consult only the
|
||||
relevant parts when needed. Their plans, priorities and old approvals do not define
|
||||
this iteration. Preserve existing work while reconsidering its design.
|
||||
@@ -0,0 +1,167 @@
|
||||
# Current work
|
||||
|
||||
## Phase and working method
|
||||
|
||||
Implementation is authorized and underway. Do not restart the broad design pass or
|
||||
reconfirm accepted choices. `docs/design.md` owns the model, decisions and coding
|
||||
conventions; README owns navigation/maintenance; operational details live with owners.
|
||||
|
||||
Complete independent agreed work and obvious fixes autonomously. Leave consequential
|
||||
unknowns pending and collect recommendations/alternatives into one substantial review
|
||||
batch. Ask earlier only to prevent broad rework/blockage. The old 2–4-question quota
|
||||
is superseded. Keep effort focused; no speculative systems or peripheral research.
|
||||
Do not write or run tests or perform extensive validation unless explicitly requested.
|
||||
Do not activate, push, decrypt for discovery or access company systems with write
|
||||
authority. No subagents unless explicitly authorized by current instructions.
|
||||
|
||||
Keep this record current by replacing superseded state. Pass-specific procedures
|
||||
stay here, not in AGENTS. The user must agree before the overall pass is complete.
|
||||
The unedited `~/sync/notes/setup-todo.md` remains an idea inbox, not authorization.
|
||||
|
||||
## Wardrowbe on Orion: implemented, awaiting provisioning and activation
|
||||
|
||||
- Orion selects the Wardrowbe capability at `wardrobe.jelles.net`, with Tinyauth
|
||||
password access at `wardrobe-auth.jelles.net`. Trusted household members select
|
||||
their profile through Wardrowbe's development login after the gateway; profile
|
||||
identity is not independently verified.
|
||||
- Podman containers use immutable image digests, a dedicated network and persistent
|
||||
named volumes. Database/queue health gates migrations and API startup; frontend
|
||||
and workers wait for the API. Caddy protects all application routes; published
|
||||
container ports are localhost-only. Internal AI is disabled everywhere. No
|
||||
Codex bridge, Ollama or paid provider is configured.
|
||||
- Four new system SOPS keys and both DNS records remain user-provisioned. Missing
|
||||
SOPS keys must be supplied before Orion builds or activates. See
|
||||
[Wardrowbe setup](../modules/capabilities/wardrowbe/README.md) for provisioning,
|
||||
activation, operations, upgrade limits and manual acceptance.
|
||||
- Focused source/configuration review only. No tests, host evaluation/build,
|
||||
`just validate`, activation or remote changes. No existing secrets were decrypted
|
||||
or read for discovery.
|
||||
- A user-run Orion build failed compiling Contour's SIMD code, pulled in by
|
||||
`enableAllTerminfo`. Orion now selects Kitty terminfo for workstation SSH clients
|
||||
instead of the complete terminal set. The pinned NixOS module was inspected;
|
||||
formatting and focused diff review only. No rebuild was run after this fix.
|
||||
|
||||
## Implemented, reviewed and validated; awaiting user checkpoint
|
||||
|
||||
- Flat capability owners; service category flattened. AI, theme, Handy and
|
||||
transcription support colocated. Imported Nix helpers moved under `_support/`.
|
||||
File management owns Nautilus/GVfs/udisks; keyring remains separate.
|
||||
- Removed Walker project discovery, agent-deck/notify, tmux, Zellij, TeamViewer and
|
||||
direnv. Removed the uninstalled nixos-maintainer skill source. Retained Herdr,
|
||||
Ollama/grounded-docs and their data, mail/calendar tools, dictation/transcription.
|
||||
- Shared notes launcher uses Kitty's explicit directory and native zk note picker;
|
||||
the picker already supports Ctrl+E creation. Oil's mini.icons provider loads
|
||||
before initial directory buffers. Manual workspace naming/clearing and visible
|
||||
Noctalia labels; shell titles include directory/command. Garden shell initialization
|
||||
now belongs to company integration instead of every host's generic shell.
|
||||
- Kanagawa declares Adwaita/hicolor icon fallback; modest Limine branding/layout.
|
||||
Walker uses upstream Elephant without a local vault patch. It may query `rbw` at startup.
|
||||
- GitHub devenv 2.3.2 installed with native Zsh hook and upstream reload defaults.
|
||||
CLI dependencies keep upstream pins/cache; workbench tools use this repository's
|
||||
exact nixpkgs pin. Company tools are also installed through the work profile. Product and
|
||||
personal environments are untouched. No external loader was sourced or changed.
|
||||
- Workbench runtime SecretSpec export helper resolves company/live/test independently,
|
||||
clears stale owned variables, reports value-free status, and supports tools-only
|
||||
entry/retry. Source mappings/provisioning remain pending below.
|
||||
- User-run encrypted company migration helper prepared, never run. Company document
|
||||
absent until user provisioning. Test mapping and login remain deliberately
|
||||
unimplemented until the concrete context and plugin are known; the loader accepts
|
||||
only an explicit noninteractive runtime snapshot. No company credential values
|
||||
have been read.
|
||||
- Both workstations select persistent read-only OBS/SharePoint user mounts, no content
|
||||
cache; replaced Zenith-only hourly mirror. Existing data is preserved. Credentials,
|
||||
old OBS mount handover and runtime acceptance remain user-owned.
|
||||
- Tailscale enabled on all hosts; ordinary SSH keys retained. Zenith SSH is tailnet-only,
|
||||
Polaris LAN SSH unchanged, Orion public SSH unchanged pending verification.
|
||||
- Documentation consolidated; coding conventions in design, short AGENTS retained.
|
||||
Validation prepares upstream devenv's import-from-derivation dependencies before
|
||||
read-only flake checking, accepts the repository's declared official cache, and
|
||||
reports interruption concisely. Full builds still include the tools-only shell.
|
||||
The devenv cache is now in persistent NixOS Nix settings; the flake cache hint
|
||||
was removed.
|
||||
- Noctalia tracks upstream's `cachix` branch with its own nixpkgs pin; persistent
|
||||
Nix settings include the official cache and signing key. The existing 5.2.1
|
||||
commit is unchanged. Its resulting x86_64 package has a matching signed cache
|
||||
entry; the previous package with our nixpkgs pin returned 404. No build or
|
||||
activation was run. For the first activation, pass the cache URL and key from
|
||||
`modules/capabilities/nix.nix` as `extra-substituters` and
|
||||
`extra-trusted-public-keys` options to `nh os switch`; afterward Nix uses the
|
||||
persistent settings.
|
||||
|
||||
## Decisions/provisioning still needed
|
||||
|
||||
1. **Company-root activation.** Native devenv suppresses its hook inside an active
|
||||
devenv shell, including nested projects. No root environment is generated now;
|
||||
recommend globally installed company tools at root and autoactivation only in
|
||||
workbench. This avoids custom activation machinery; confirm at the user checkpoint.
|
||||
2. **Test SecretSpec mapping.** Need the explicit test context and existing application
|
||||
Secret/namespace/key coordinates (metadata only, no values). Confirm whether
|
||||
database Secrets hold complete URLs or separate components. Do not invent keys,
|
||||
silently use the current context or fall back to live/SOPS test credentials.
|
||||
Test loading reports unavailable until its runtime manifest and login snapshot exist.
|
||||
3. **Explicit-login mechanism.** Final review removed the generic ExecCredential
|
||||
interpreter: it duplicated a security-sensitive Kubernetes client protocol before
|
||||
the actual company plugin was known. Inspect the selected context's exec stanza,
|
||||
then choose its native/provider-specific cached login flow and prepare a mode-0600
|
||||
runtime snapshot. Shell entry must not launch authentication.
|
||||
4. **Live MariaDB certificate verification.** The migration helper preserves the
|
||||
old `ssl-verify-server-cert=0` behavior. Before running it, either explicitly accept
|
||||
that legacy risk or identify the correct CA/hostname settings and enable verification.
|
||||
5. **Manual provisioning/acceptance.** Company SOPS recipients/migration, rclone
|
||||
OAuth on both hosts, old OBS mount handover, devenv trust/responsiveness, Noctalia
|
||||
Radicale password and private Outlook ICS URL, tailnet enrollment/private SSH.
|
||||
See workbench README and root README. Do not close public Orion SSH yet.
|
||||
6. **Guidelines review.** Design now spells out owner naming, module selection,
|
||||
helper placement, package reuse/pinning, runtime boundaries and validation. No new
|
||||
test policy, automation framework or pass-specific repo-wide rules were added.
|
||||
|
||||
## Validation state
|
||||
|
||||
Final full validation passed before the final review deletions:
|
||||
`/tmp/nixos-validate-t7zv1zux` (session 88912,
|
||||
completed exit 0). Formatting, devenv preparation, flake check, all three host
|
||||
builds (Orion, Polaris, Zenith), and the tools-only workbench build passed.
|
||||
This includes the final notes startup ordering, FUSE wrapper paths, native Zsh retry
|
||||
function and SecretSpec package collision fix. The vault patch was removed afterward.
|
||||
No systems were activated.
|
||||
|
||||
Native devenv tools-only startup also passed in an isolated temporary home:
|
||||
`/tmp/foundation-native/startup.log`. Company/live/test loading was disabled;
|
||||
SecretSpec 0.20.0 and Python 3.14.7 were available. No company credentials loaded.
|
||||
|
||||
Clean-home manual editor checks passed: direct directory startup renders a Lua-file
|
||||
icon in Oil; opening the note picker after VimEnter reaches TelescopePrompt.
|
||||
Evidence: `/tmp/foundation-editor-check/{startup,notes}.log`. No real notebook or
|
||||
credentials loaded. Built Kanagawa index declares `Inherits=Adwaita,hicolor`.
|
||||
Python source syntax and local documentation links checked; no automated tests added.
|
||||
|
||||
The earlier final review removed the unaccepted Kubernetes login wrapper and the
|
||||
machine-wide devenv cache setting; the flake-scoped cache remains. Full builds were
|
||||
not repeated for those deletions. The later Walker patch removal has not been built.
|
||||
|
||||
Builds do not establish interactive acceptance. The user checkpoint remains; do not
|
||||
repeat full builds for documentation-only handoff edits.
|
||||
|
||||
## Review checkpoint
|
||||
|
||||
The final diff was reviewed for correctness, integration and Ponytail complexity.
|
||||
Desktop/capability moves and repository wiring had no material findings. The review
|
||||
removed the speculative Kubernetes login interpreter. The devenv cache was later
|
||||
moved from flake scope to machine-wide Nix settings at the user's request. The
|
||||
remaining Python has a concrete role: in-memory concurrent secret loading with
|
||||
completeness checks, one-time encrypted credential reshaping, dynamic OBS rclone
|
||||
configuration, or pre-existing dictation/transcription behavior.
|
||||
|
||||
The user checkpoint is still required before this pass is complete. The implementation
|
||||
was committed as `5748b34` after the initial foundation commit `f164d8b`. Later
|
||||
cleanup removed the Walker patch, Handy test and AI capability README.
|
||||
No activation was recorded during that pass. External workbench analysis/content
|
||||
changes are unrelated and must not be committed here. Previous external loader
|
||||
copies remain at `/tmp/workbench-environment-before-6k4dwzuo`; only root/workbench environment documentation and ignore entries were updated.
|
||||
Backups: `/tmp/foundation-workbench-docs-bvrs1hu9`. Old external loaders remain
|
||||
until user activation, so the currently running system is not broken. Constellation documents were already
|
||||
superseded and removed in the prior foundation commit.
|
||||
|
||||
Deliberate deferrals remain in design: backups declined, dashboard scrapped,
|
||||
Zenith Plymouth and extra Noctalia keyboard controls deferred, stronger credential
|
||||
isolation/new agent workflows/indexing and unrelated editor redesign out of scope.
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
# Repository design
|
||||
|
||||
Working decisions, open to evidence from implementation and use.
|
||||
|
||||
## Model and ownership
|
||||
|
||||
Build a quiet, keyboard-driven, text-oriented environment from understandable tools
|
||||
and standard interfaces. Humans and agents use the same underlying resources.
|
||||
Graphical applications are deliberate choices where they fit. Streamline existing
|
||||
workflows rather than requiring a new way of working.
|
||||
|
||||
- **Host:** machine identity, hardware and differences in selection.
|
||||
- **Profile:** a deliberate combination of capabilities.
|
||||
- **Capability:** behavior and necessary integration, potentially spanning several
|
||||
applications, NixOS and Home Manager.
|
||||
- **Facts:** shared identity and topology consumed in multiple places.
|
||||
- **Theme:** application-specific appearance, including plugins and assets.
|
||||
Interaction and layout belong to capabilities.
|
||||
|
||||
Polaris and Zenith have the same intended daily use. The `work` profile on both is
|
||||
a provisional ownership boundary, not a different workstation variant. Steam and
|
||||
LAN SSH on Polaris, and laptop power/firmware management on Zenith, are deliberate.
|
||||
Orion owns shared services. Tailnet administration supplements existing access;
|
||||
public Orion SSH closes only after private access is verified. Web services stay public.
|
||||
Orion installs Kitty terminfo for SSH sessions from the workstations.
|
||||
|
||||
Wardrowbe uses a password gateway for trusted household access, followed by profile
|
||||
selection inside the app; verified individual identity is deferred. Its internal AI
|
||||
is disabled. Paid API usage, a Codex bridge and local model integration are deferred.
|
||||
|
||||
Nix owns reproducible installation, integration and selected settings. Applications
|
||||
own credentials, sessions, content and deliberately interactive preferences. No
|
||||
plaintext secrets enter Git, Nix evaluation or store outputs. Company credentials
|
||||
use a separate encrypted SOPS document in this private Gitea repository and the
|
||||
existing per-machine age keys. Credential availability is not operational authority.
|
||||
|
||||
## Coding and composition conventions
|
||||
|
||||
Use a flat set of capability owners: `name.nix` when self-contained, otherwise
|
||||
`name/default.nix` with its scripts, assets and operational notes beside it. Put
|
||||
manually imported Nix helpers under that owner's `_support/` so import-tree does
|
||||
not discover them. Host hardware helpers remain private to their host.
|
||||
|
||||
Discovery publishes modules; explicit imports select behavior. Prefer existing
|
||||
NixOS/Home Manager options and direct module imports. Introduce a shared option or
|
||||
helper only for actual consumers. Use kebab-case owner/module names that describe
|
||||
purpose. NixOS and Home Manager modules may share the same capability name. Small
|
||||
application declarations may remain grouped when they need no substantial integration.
|
||||
Split independent purposes or shared integrations, not every configured package.
|
||||
|
||||
Keep package definitions with their capability and reuse them across installations
|
||||
and development environments. Flake inputs belong in `flake.nix` and are locked in
|
||||
`flake.lock`; fixed external sources need a version/revision and hash. Workbench
|
||||
tools follow the system nixpkgs pin. The GitHub devenv CLI retains its upstream
|
||||
build dependencies. Product environment definitions and pins remain product-owned.
|
||||
Use store-qualified executables or `runtimeInputs` in packaged scripts; make runtime
|
||||
paths and failures explicit. Do not duplicate package lists, generated settings or
|
||||
binding references in documentation.
|
||||
|
||||
These clarify the agreed model; they are not a one-package-per-file rule or a
|
||||
requirement for additional abstractions.
|
||||
|
||||
## Daily environment
|
||||
|
||||
Niri owns navigation and frequent shortcuts; Walker owns application/resource search;
|
||||
Noctalia owns status and system controls. Workspaces remain flexible and optionally
|
||||
named by the user. Terminal titles provide orientation. No project-based workspace
|
||||
scheme, heuristic naming or custom Walker project discovery is wanted.
|
||||
|
||||
Herdr remains an experiment, not a core dependency. Remove agent-deck, tmux and
|
||||
Zellij. Keep Ollama and grounded-docs for experimentation and preserve their data.
|
||||
Ollama's embedding model supports grounded-docs. Keep Handy for local dictation and
|
||||
Orion transcription. Handy preferences, recordings and models remain application-owned.
|
||||
Its push-to-talk release listener requires active-user keyboard access; press from
|
||||
idle, with Super and D on the same keyboard.
|
||||
|
||||
Both notes entry points open the existing zk note picker with note creation.
|
||||
Notes stay in `~/sync/notes`, mostly meeting material; no new taxonomy. HTTP and
|
||||
database targets remain independently selected and visibly named. Dadbod working
|
||||
queries and connection state stay outside Git; see the editor's `database.lua`.
|
||||
|
||||
Kanagawa remains the theme with Adwaita fallback icons. Walker uses upstream Elephant
|
||||
without a local vault patch; it may query rbw during startup. Outlook PWA owns work
|
||||
email/calendar editing. Mailspring owns personal mail; Thunderbird remains a backup. Noctalia
|
||||
provides the combined read-only Radicale/Outlook agenda. The user accepts published
|
||||
ICS link access and supplies the URL privately through runtime settings. Personal
|
||||
calendar editing stays on the phone; keep khal/todoman.
|
||||
|
||||
## Company environment
|
||||
|
||||
`~/yookr` is the company root; product repositories are in `~/yookr/projects`.
|
||||
`~/yookr/workbench` is the access point for company services, databases and files.
|
||||
This repository owns its environment; requests, queries, analysis, findings, Python
|
||||
lock, content and private context stay writable there. The workbench capability
|
||||
owns setup and credential/mount instructions.
|
||||
|
||||
Native devenv replaces direnv, initially with default reload behavior. Migration is
|
||||
limited to workbench and company-root integration. Product, personal, archived and
|
||||
learning projects are user-migrated; do not retain direnv as a transition.
|
||||
|
||||
Use SecretSpec with existing test-cluster application Secrets and SOPS-backed live
|
||||
credentials. There is no live-cluster access. Company login is explicit. Sources
|
||||
must fail independently, tools remain available, and retries must clear stale values.
|
||||
Normal human editor startup can receive both targets; per-connection decryption and
|
||||
a global environment selector are not wanted. Stronger agent credential isolation
|
||||
is deferred; existing restrictions on company operations remain in force.
|
||||
|
||||
OBS and SharePoint are continuously available as read-only mounts under the workbench
|
||||
on both workstations. User services own lifecycle, independent of shell entry.
|
||||
No persistent content cache or hourly mirror; memory buffers and metadata are accepted.
|
||||
Same-user processes, including agents, can read these mounts. Preserve old local data.
|
||||
|
||||
## Knowledge and maintenance
|
||||
|
||||
README owns navigation and maintenance; AGENTS owns essential durable collaboration
|
||||
constraints; this document owns rationale/conventions; current-work owns resumable
|
||||
state. Operational details live beside their owner. Use Git for chronology, replace
|
||||
superseded state, and remove temporary state when no longer useful.
|
||||
|
||||
Keep instructions short and authoritative. Add a rule when it prevents a concrete
|
||||
mistake, resolves recurring ambiguity or points to its owner. Prefer formatter and
|
||||
validation feedback to repeated reminders. This applies the useful principle of
|
||||
progressive disclosure from [OpenAI's harness engineering article](https://openai.com/index/harness-engineering/)
|
||||
without introducing speculative skills, orchestration or evaluation infrastructure.
|
||||
|
||||
`just validate` is available on explicit request. It includes working-tree changes
|
||||
and new nonignored files without staging, formats, evaluates and builds. Targeted
|
||||
host builds are supported; a full run also builds workbench tools. Builds do not
|
||||
prove activation or interactive behavior. Do not write or run tests or perform
|
||||
extensive validation unless explicitly requested.
|
||||
|
||||
Keep native update and activation commands. `nix flake update` is normal; selective
|
||||
updates (often `llm-agents`) are appropriate while nixpkgs breakage awaits repair.
|
||||
Activation remains user-owned. No trivial `just` aliases for native commands.
|
||||
|
||||
## Deliberately deferred
|
||||
|
||||
No backups (user declined), Orion dashboard/monitoring (scrapped), new agent
|
||||
workflows/persistence/indexing/isolation, or broader editor redesign without friction.
|
||||
Zenith's Plymouth refresh and extra Noctalia keyboard controls are deferred.
|
||||
The existing single Syncthing `~/sync` layout is retained. TeamViewer is removed.
|
||||
Generated
+397
-69
@@ -20,20 +20,97 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1788011267,
|
||||
"narHash": "sha256-mEq2kU+ljompTToZ44Afvm7d/rHJ5iMBl0tjZuyShJs=",
|
||||
"lastModified": 1790001035,
|
||||
"narHash": "sha256-1/SLBjSYBzDdDhvLQ83wrzBakyS/xNGgtxHGNuveOEQ=",
|
||||
"owner": "Mic92",
|
||||
"repo": "bun2nix",
|
||||
"rev": "5765b0614591f75ee8ba5596e81ae85c167d1071",
|
||||
"rev": "07a5bfc8ac36c5370199343fa620b69f63186e33",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "Mic92",
|
||||
"ref": "fix-structured-attrs-hook",
|
||||
"ref": "llm-agents",
|
||||
"repo": "bun2nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"cachix": {
|
||||
"inputs": {
|
||||
"devenv": [
|
||||
"devenv"
|
||||
],
|
||||
"flake-compat": [
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"git-hooks": [
|
||||
"devenv",
|
||||
"git-hooks"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1788181969,
|
||||
"narHash": "sha256-OUB6hPlFBB9FRdZgZXSye4lDOg+fbrqKe8ePv2IM7NY=",
|
||||
"owner": "cachix",
|
||||
"repo": "cachix",
|
||||
"rev": "98093e8eea6c1635ef1337a576fa76f3e8bd4f39",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"ref": "latest",
|
||||
"repo": "cachix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"crate2nix": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1772186516,
|
||||
"narHash": "sha256-8s28pzmQ6TOIUzznwFibtW1CMieMUl1rYJIxoQYor58=",
|
||||
"owner": "rossng",
|
||||
"repo": "crate2nix",
|
||||
"rev": "ba5dd398e31ee422fbe021767eb83b0650303a6e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "rossng",
|
||||
"repo": "crate2nix",
|
||||
"rev": "ba5dd398e31ee422fbe021767eb83b0650303a6e",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"devenv": {
|
||||
"inputs": {
|
||||
"cachix": "cachix",
|
||||
"crate2nix": "crate2nix",
|
||||
"flake-compat": "flake-compat",
|
||||
"flake-parts": "flake-parts",
|
||||
"ghostty": "ghostty",
|
||||
"git-hooks": "git-hooks",
|
||||
"nix": "nix",
|
||||
"nixd": "nixd",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1791388908,
|
||||
"narHash": "sha256-eqnGuVYIh6u8XU6UXueceaCi3UfSqbvopeuLYgClEDY=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"rev": "27af3b2029a6d1274db8a570fb57292dae5a2551",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"disko": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
@@ -41,11 +118,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1781152676,
|
||||
"narHash": "sha256-RxWs5ND31KzTG7wvMM+PMfUjyNpmIEr999lqNARaM5o=",
|
||||
"lastModified": 1789770686,
|
||||
"narHash": "sha256-uZkBR7yHdIKUFB5SZdfgh1qkGfI3XmYmI/lTiquxbck=",
|
||||
"owner": "nix-community",
|
||||
"repo": "disko",
|
||||
"rev": "ff8702b4de27f72b4c78573dfb89ec74e36abdf1",
|
||||
"rev": "725ea35e410ad83be4931d1bff7e090eacaf3563",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -66,11 +143,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1775706155,
|
||||
"narHash": "sha256-h7Rw0vlb0n0Jsk21WJPm7H+1T1bG+PEuxE5cJ2TZl8A=",
|
||||
"lastModified": 1790316550,
|
||||
"narHash": "sha256-Qr/8JQ8zpmmWmZ7UAjhxVdRbSgwQ0uc2r/wUBMe4NBk=",
|
||||
"owner": "abenz1267",
|
||||
"repo": "elephant",
|
||||
"rev": "376ee71c66db38683daabd57350bf3f6f086eaf8",
|
||||
"rev": "a0670fb2cf590a3ceed6f799a45947b86a78d5f4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -79,16 +156,35 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1767039857,
|
||||
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "edolstra",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
"nixpkgs-lib": [
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1788450739,
|
||||
"narHash": "sha256-glZLQlzIn1fXH6PazR2iUmTo7kzzyYSshrWhLS9TqCU=",
|
||||
"lastModified": 1788306937,
|
||||
"narHash": "sha256-lbrI1UYVpFgKSsyrU9oeF7FHfnu8nLD9ja0ou9f2rxk=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "31729ca8cbdb4fa927b34e5f4353e6a83f39e993",
|
||||
"rev": "f16b25b8c3d2809b87925d0b76652d7821a75c68",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -98,6 +194,24 @@
|
||||
}
|
||||
},
|
||||
"flake-parts_2": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1790898470,
|
||||
"narHash": "sha256-zTwuwezD0w3hpga6a6P8emidzAZFyWqlNeRBlpWOOl8=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "024633cd702b10285db5cb19b40ad48d2399ba60",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts_3": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": [
|
||||
"llm-agents",
|
||||
@@ -105,11 +219,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1788450739,
|
||||
"narHash": "sha256-glZLQlzIn1fXH6PazR2iUmTo7kzzyYSshrWhLS9TqCU=",
|
||||
"lastModified": 1790898470,
|
||||
"narHash": "sha256-zTwuwezD0w3hpga6a6P8emidzAZFyWqlNeRBlpWOOl8=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "31729ca8cbdb4fa927b34e5f4353e6a83f39e993",
|
||||
"rev": "024633cd702b10285db5cb19b40ad48d2399ba60",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -118,6 +232,48 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"ghostty": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1786026742,
|
||||
"narHash": "sha256-tRtgTg/i3w1nFdRHU2IGekfJN8EyP+HARW4MtoUZyqk=",
|
||||
"owner": "ghostty-org",
|
||||
"repo": "ghostty",
|
||||
"rev": "22d13172cde98a0a4dda05d3d6a3fcb0dd8ed018",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ghostty-org",
|
||||
"repo": "ghostty",
|
||||
"rev": "22d13172cde98a0a4dda05d3d6a3fcb0dd8ed018",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"git-hooks": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1788267358,
|
||||
"narHash": "sha256-nt+lUqYVpc9Y6JeMd2WmXzCDojasdadKo0mWcluvY2Y=",
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "27555e2624241fb116b49095df4caaee85a25691",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"home-manager": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
@@ -125,11 +281,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1788651960,
|
||||
"narHash": "sha256-v9wJd32eZ2bvhBzVOd7TIjLQd011P7nwOhjKtWlci5I=",
|
||||
"lastModified": 1791306979,
|
||||
"narHash": "sha256-KuLePuhwJM9NLt/gTLHzXaOoSZKGoB3m7NfXjiQ98HY=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "2c0350c759688177331b8f5242311fae8877bdb3",
|
||||
"rev": "fae6e9e42c3b762ab47635cddcfaf6f52374a61b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -156,19 +312,17 @@
|
||||
"llm-agents": {
|
||||
"inputs": {
|
||||
"bun2nix": "bun2nix",
|
||||
"flake-parts": "flake-parts_2",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"flake-parts": "flake-parts_3",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"systems": "systems",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
"treefmt-nix": "treefmt-nix_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1789202377,
|
||||
"narHash": "sha256-gbMsC2v/ubtuW2xYfhfkJL9FQ/o7ri/8Tsw1j8O1cxY=",
|
||||
"lastModified": 1791440787,
|
||||
"narHash": "sha256-nRO1wEJmb4wjz+0Mhpg5VWURTqZRBZq3MvMjtPkKuAI=",
|
||||
"owner": "numtide",
|
||||
"repo": "llm-agents.nix",
|
||||
"rev": "6cb1a5b7a42d03cf09fc4936db9db570d83f3466",
|
||||
"rev": "2109db8971dc18137ff1ac9393627c761609600d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -177,6 +331,46 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix": {
|
||||
"inputs": {
|
||||
"flake-compat": [
|
||||
"devenv",
|
||||
"flake-compat"
|
||||
],
|
||||
"flake-parts": [
|
||||
"devenv",
|
||||
"flake-parts"
|
||||
],
|
||||
"git-hooks-nix": [
|
||||
"devenv",
|
||||
"git-hooks"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-23-11": [
|
||||
"devenv"
|
||||
],
|
||||
"nixpkgs-regression": [
|
||||
"devenv"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1790176416,
|
||||
"narHash": "sha256-acP7QvsB+dHghUeL9AwSSZ3qS//skpqjv484aveg1EQ=",
|
||||
"owner": "cachix",
|
||||
"repo": "nix",
|
||||
"rev": "bd10d7f563420c6034b9d1dc75c4168fc01e5afc",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"ref": "devenv-2.35",
|
||||
"repo": "nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-index-database": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
@@ -184,11 +378,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1788680125,
|
||||
"narHash": "sha256-amGSoDobwmp4CFvCn841ws2iuitus+HUJdD/gKgsrJA=",
|
||||
"lastModified": 1791105314,
|
||||
"narHash": "sha256-oTM7QFgRQ+vxBpMBMc7hEYpqBtpXAzXu2xuB/tru84U=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nix-index-database",
|
||||
"rev": "116ad1c2adb642405ef8916f6a94c8626f971344",
|
||||
"rev": "e740bc4e9d8918d7819a51486d5099930c65c72e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -204,11 +398,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1788744583,
|
||||
"narHash": "sha256-oNMrijfyaoRjjWhxHNFSBYyEB4u/zb/AxoKewC56LvE=",
|
||||
"lastModified": 1791440872,
|
||||
"narHash": "sha256-PwiSn1VO3/6BD/Gxw/3Ei2sPB3SccyKyJ7nD72Y+WU4=",
|
||||
"owner": "BirdeeHub",
|
||||
"repo": "nix-wrapper-modules",
|
||||
"rev": "e88c449105c6aafa460196d3cbd16718655aee0f",
|
||||
"rev": "d82c328c504648fa952f25fac8332319eb425d4c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -217,6 +411,32 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixd": {
|
||||
"inputs": {
|
||||
"flake-parts": [
|
||||
"devenv",
|
||||
"flake-parts"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
],
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1788165251,
|
||||
"narHash": "sha256-ITHt6eU6DuwQqlNV1/wehvKYPE6J8sZIlv75CpDjcHI=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixd",
|
||||
"rev": "e2ca72c353cfbc3d63e942fe9aeae4ec497863bf",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixd",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixos-hardware": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
@@ -224,11 +444,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1788860136,
|
||||
"narHash": "sha256-MhPMOFV4pVkygWEbQ8t1De/uQ9cWF1u++tRe2L5tG48=",
|
||||
"lastModified": 1791373788,
|
||||
"narHash": "sha256-c3oMt0QoE+4yiRk6u1m2L9ogjaUw0uTGn9yk808KkgE=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixos-hardware",
|
||||
"rev": "62173785b9a18c78b4a15aca2623d02bceb9d077",
|
||||
"rev": "4bc63156b109ee7b4103e3ec30565c8c92bde435",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -239,69 +459,155 @@
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"inputs": {
|
||||
"nixpkgs-src": "nixpkgs-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1788834054,
|
||||
"narHash": "sha256-kkWAS4mE+fmi+u3mxwD9/bfX6DGJ49Tv8O9kzN5r0HQ=",
|
||||
"rev": "e9b9cbecbf6c25ca26c150d78570e6332694a129",
|
||||
"lastModified": 1787753358,
|
||||
"narHash": "sha256-Tl77VbWyAKrOfRNQhL6JbQtb/MLzbYa/1RG1gWWfICk=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv-nixpkgs",
|
||||
"rev": "256551e45f6303e142ab4a98be1bf243feb77dc0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"ref": "rolling",
|
||||
"repo": "devenv-nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1790477367,
|
||||
"narHash": "sha256-oujbxmVKDhci2E4EM3KyAbRUgp5/IsCoiA/LfEF7OOk=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "f7cd230690d9fc982d06129f9189b101fddcfa3b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1787394516,
|
||||
"narHash": "sha256-pRGOQSClnXNI2iLUG6DYpsGvYcuw0drOutVZFTJNw90=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c8f90650c15282fa8656a041bfbbd2403997a9a7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1790600678,
|
||||
"narHash": "sha256-6k3n7X2UsZ6tAlWTz99RimiZab+17tMUrw9sRUcELiA=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "f45c6f04c2f013f004bf94e284e95d72898d9393",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1791336361,
|
||||
"narHash": "sha256-7WQwlykef1x8n7aoyw4XP6882i2NRs5F6dVCXZ1NI1g=",
|
||||
"rev": "7dd199b0e2993e37b4775ed66b1c291699608c9f",
|
||||
"type": "tarball",
|
||||
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.11pre1069601.e9b9cbecbf6c/nixexprs.tar.xz"
|
||||
"url": "https://releases.nixos.org/nixpkgs/nixpkgs-26.11pre1086972.7dd199b0e299/nixexprs.tar.xz"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
"url": "https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"nixpkgs_4": {
|
||||
"locked": {
|
||||
"lastModified": 1788057806,
|
||||
"narHash": "sha256-DTQSMxzDWmT0zhguthvegnVkn7CFqGCv4IHCzk5ZUpM=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "596e2e3940e09b2abbeb03f75fa1828c57fcd72c",
|
||||
"type": "github"
|
||||
"lastModified": 1790822859,
|
||||
"narHash": "sha256-VBA0QerzzmL+4wxgtwiCdiLwLpJg2/9xOxhgO3BRJQA=",
|
||||
"rev": "c59305bab2065cfecc4944690d9eedbb56f3a9fa",
|
||||
"type": "tarball",
|
||||
"url": "https://releases.nixos.org/nixos/unstable/nixos-26.11pre1083223.c59305bab206/nixexprs.tar.zst"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"type": "github"
|
||||
"type": "tarball",
|
||||
"url": "https://channels.nixos.org/nixos-unstable/nixexprs.tar.zst"
|
||||
}
|
||||
},
|
||||
"noctalia": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
"nixpkgs": "nixpkgs_4"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1788886125,
|
||||
"narHash": "sha256-5rgQFUGr8GXYyyuy8ynrN39WfZzSiO/wqGWjELBOkEY=",
|
||||
"lastModified": 1791427429,
|
||||
"narHash": "sha256-FzM3cunGIn8oDC1afUsXzGdfI/tK5HjPOHzHXFIAVx4=",
|
||||
"owner": "noctalia-dev",
|
||||
"repo": "noctalia",
|
||||
"rev": "d6607b4eafbb92f6d494ebed3e296d4b741c0b58",
|
||||
"rev": "3e059de4aec58ba5b81f6c366b2b234a58f5ab0a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "noctalia-dev",
|
||||
"ref": "cachix",
|
||||
"repo": "noctalia",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"devenv": "devenv",
|
||||
"disko": "disko",
|
||||
"flake-parts": "flake-parts",
|
||||
"flake-parts": "flake-parts_2",
|
||||
"home-manager": "home-manager",
|
||||
"import-tree": "import-tree",
|
||||
"llm-agents": "llm-agents",
|
||||
"nix-index-database": "nix-index-database",
|
||||
"nix-wrapper-modules": "nix-wrapper-modules",
|
||||
"nixos-hardware": "nixos-hardware",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nixpkgs": "nixpkgs_3",
|
||||
"noctalia": "noctalia",
|
||||
"sops-nix": "sops-nix",
|
||||
"walker": "walker"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"devenv",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1788332415,
|
||||
"narHash": "sha256-BTFrmyh0oaVDsvA9NNw0YpbbeppTwU0t70iVx672Ew8=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "860d7c835ab91bfc8972b67092f5f2db8e9390a0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"sops-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
@@ -309,11 +615,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1788337237,
|
||||
"narHash": "sha256-gkSH8VUtCo6hnysNmb9DbTuDepH2t5pv+QWjP75xKAk=",
|
||||
"lastModified": 1791103873,
|
||||
"narHash": "sha256-nFxM+pKoZ8LJAEnUXARyCaOAloWgaW9kZQOSjWzKcTE=",
|
||||
"owner": "Mic92",
|
||||
"repo": "sops-nix",
|
||||
"rev": "fbf759290e0cb0a98dfc813a4eb7d53ad1dacb57",
|
||||
"rev": "dcd241ba97088c22569d1573286e1b9daad340c0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -324,11 +630,11 @@
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"lastModified": 1791244623,
|
||||
"narHash": "sha256-uuanbkFk8a313Zfrxdcz8Us/yQtaxNnKJtX8v0ZPpd8=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"rev": "d36eeed142f88b5a636f2e3856a53c61654cf29b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -355,7 +661,8 @@
|
||||
"treefmt-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"llm-agents",
|
||||
"devenv",
|
||||
"nixd",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
@@ -373,6 +680,27 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"treefmt-nix_2": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"llm-agents",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1791203292,
|
||||
"narHash": "sha256-vuzVwvjeucSnwKwMBpyP8r5IzdrMKCHEdwTrRFwUN7I=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "03d8ee1bcbc8f9638907bb79edb9673151d0e22b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"walker": {
|
||||
"inputs": {
|
||||
"elephant": "elephant",
|
||||
@@ -382,11 +710,11 @@
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1784217994,
|
||||
"narHash": "sha256-gxHJsrJZo2qbKK47Kf/9ho3i/HhRwkL8XvPnczGPd9E=",
|
||||
"lastModified": 1791441924,
|
||||
"narHash": "sha256-2SLAXtgtsZgjK+dyzDAV47oQNoip3dsGX+XXrT/udEk=",
|
||||
"owner": "abenz1267",
|
||||
"repo": "walker",
|
||||
"rev": "42b3ed88abf50bc52638fb2835b7f17e3ea3ac4c",
|
||||
"rev": "a515085fd39fae80acc086c8fbfd9b7771a9a11d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
description = "NixOS Configuration";
|
||||
|
||||
inputs = {
|
||||
devenv.url = "github:cachix/devenv";
|
||||
disko = {
|
||||
url = "github:nix-community/disko";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
@@ -12,10 +13,7 @@
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
import-tree.url = "github:vic/import-tree";
|
||||
llm-agents = {
|
||||
url = "github:numtide/llm-agents.nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
llm-agents.url = "github:numtide/llm-agents.nix";
|
||||
nix-wrapper-modules = {
|
||||
url = "github:BirdeeHub/nix-wrapper-modules";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
@@ -26,8 +24,8 @@
|
||||
};
|
||||
nixpkgs.url = "https://channels.nixos.org/nixpkgs-unstable/nixexprs.tar.xz";
|
||||
noctalia = {
|
||||
url = "github:noctalia-dev/noctalia";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
url = "github:noctalia-dev/noctalia/cachix";
|
||||
# Keep upstream's nixpkgs pin to match its cached builds.
|
||||
};
|
||||
sops-nix = {
|
||||
url = "github:Mic92/sops-nix";
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# List repository maintenance commands.
|
||||
default:
|
||||
@just --list
|
||||
|
||||
# Format, evaluate, and build all hosts and the workbench; optionally name hosts for iteration.
|
||||
validate *hosts:
|
||||
@python3 scripts/validate.py {{hosts}}
|
||||
File renamed without changes.
@@ -1,6 +1,6 @@
|
||||
{ pkgs }:
|
||||
let
|
||||
sources = import ./_sources.nix { inherit pkgs; };
|
||||
sources = import ./sources.nix { inherit pkgs; };
|
||||
context7Headers = pkgs.writeShellScript "context7-http-headers" ''
|
||||
set -euo pipefail
|
||||
CONTEXT7_KEY_FILE="''${XDG_CONFIG_HOME:-$HOME/.config}/context7/api-key"
|
||||
@@ -26,7 +26,6 @@ in
|
||||
inherit context7Headers codexHooks;
|
||||
skills = {
|
||||
context7-mcp = ../skills/context7-mcp;
|
||||
nixos-maintainer = ../skills/nixos-maintainer;
|
||||
herdr = "${sources.herdr}/skills/herdr";
|
||||
};
|
||||
servers = {
|
||||
File renamed without changes.
@@ -7,7 +7,7 @@ in
|
||||
flake.modules.nixos.ai =
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
ai = import ./ai/_assets.nix { inherit pkgs; };
|
||||
ai = import ./_support/assets.nix { inherit pkgs; };
|
||||
codexConfig = (pkgs.formats.toml { }).generate "codex-config.toml" {
|
||||
model = "gpt-5.6-sol";
|
||||
model_reasoning_effort = "high";
|
||||
@@ -15,11 +15,6 @@ in
|
||||
service_tier = "default";
|
||||
approvals_reviewer = "auto_review";
|
||||
features.hooks = true;
|
||||
notify = [
|
||||
"${pkgs.llm-agents.agent-deck}/bin/agent-deck"
|
||||
"codex-notify"
|
||||
];
|
||||
|
||||
tui = {
|
||||
status_line = [
|
||||
"model-with-reasoning"
|
||||
@@ -33,6 +28,8 @@ in
|
||||
};
|
||||
|
||||
features.plugins = true;
|
||||
# The Nix package lacks the codex-package.json layout the daemon requires.
|
||||
features.daemon_auto_start = false;
|
||||
hooks = ai.codexHooks;
|
||||
mcp_servers = ai.servers // {
|
||||
context7 = ai.servers.context7 // {
|
||||
@@ -71,7 +68,7 @@ in
|
||||
flake.modules.homeManager.ai =
|
||||
{ pkgs, lib, ... }:
|
||||
let
|
||||
ai = import ./ai/_assets.nix { inherit pkgs; };
|
||||
ai = import ./_support/assets.nix { inherit pkgs; };
|
||||
in
|
||||
{
|
||||
services.podman = {
|
||||
@@ -102,12 +99,12 @@ in
|
||||
# Node-based Codex plugin hooks and skill scripts need node/npm outside project dev shells.
|
||||
pkgs.nodejs
|
||||
pkgs.python3
|
||||
pkgs.llm-agents.codex
|
||||
pkgs.llm-agents.pi
|
||||
pkgs.llm-agents.claude-code
|
||||
pkgs.llm-agents.agent-deck
|
||||
pkgs.llm-agents.herdr
|
||||
pkgs.tmux
|
||||
inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system}.codex
|
||||
pkgs.bubblewrap
|
||||
inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system}.chatgpt
|
||||
inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system}.pi
|
||||
inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system}.claude-code
|
||||
inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system}.herdr
|
||||
pkgs.ctx7
|
||||
];
|
||||
|
||||
@@ -190,7 +187,7 @@ in
|
||||
name: source: lib.nameValuePair ".agents/skills/${name}" { inherit source; }
|
||||
) ai.skills
|
||||
// {
|
||||
".codex/AGENTS.md".text = builtins.readFile ./ai/instructions/codex.md;
|
||||
".codex/AGENTS.md".text = builtins.readFile ./instructions/codex.md;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -14,9 +14,8 @@
|
||||
|
||||
# Validation
|
||||
|
||||
- Write or run automated tests only when explicitly requested or covered by the repository's established testing policy. Do not invent a testing policy or introduce tests into a repository without tests unless explicitly requested.
|
||||
- Validate changes with the smallest relevant build, manual behaviour check, or review of code and output.
|
||||
- Broaden or repeat validation only for changes, failures or unresolved concerns. Report only checks performed.
|
||||
- Do not write or run tests or perform extensive validation unless explicitly requested.
|
||||
- For routine changes, use a focused review or small manual check. Report only checks performed.
|
||||
|
||||
# Writing
|
||||
|
||||
|
||||
File renamed without changes.
@@ -8,4 +8,13 @@
|
||||
jack.enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
flake.modules.homeManager.audio =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
home.packages = [
|
||||
pkgs.reaper
|
||||
pkgs.bitwig-studio
|
||||
];
|
||||
};
|
||||
}
|
||||
File renamed without changes.
@@ -22,68 +22,9 @@
|
||||
};
|
||||
|
||||
flake.modules.homeManager.vivaldi =
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{ lib, pkgs, ... }:
|
||||
let
|
||||
command = lib.getExe pkgs.vivaldi;
|
||||
appCommand = id: "${command} --profile-directory=Default --app-id=${id}";
|
||||
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 ];
|
||||
@@ -91,33 +32,7 @@
|
||||
packages = [ pkgs.vivaldi ];
|
||||
sessionVariables.BROWSER = command;
|
||||
};
|
||||
xdg = {
|
||||
dataFile = {
|
||||
"applications/vivaldi-${actualId}-Default.desktop" = {
|
||||
force = true;
|
||||
source = "${actualDesktop}/share/applications/vivaldi-${actualId}-Default.desktop";
|
||||
};
|
||||
"applications/vivaldi-${whatsappId}-Default.desktop" = {
|
||||
force = true;
|
||||
source = "${whatsappDesktop}/share/applications/vivaldi-${whatsappId}-Default.desktop";
|
||||
};
|
||||
"applications/vivaldi-${outlookId}-Default.desktop" = {
|
||||
force = true;
|
||||
source = "${outlookDesktop}/share/applications/vivaldi-${outlookId}-Default.desktop";
|
||||
};
|
||||
};
|
||||
mimeApps.defaultApplicationPackages = [ pkgs.vivaldi ];
|
||||
};
|
||||
};
|
||||
|
||||
flake.modules.homeManager.nautilus =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
home.packages = [ pkgs.nautilus ];
|
||||
gtk.gtk3.bookmarks = [
|
||||
"sftp://${config.facts.topology.server.sshAlias} Orion VPS"
|
||||
];
|
||||
xdg.mimeApps.defaultApplicationPackages = [ pkgs.nautilus ];
|
||||
xdg.mimeApps.defaultApplicationPackages = [ pkgs.vivaldi ];
|
||||
};
|
||||
|
||||
flake.modules.homeManager.imv =
|
||||
@@ -142,7 +57,7 @@
|
||||
};
|
||||
|
||||
flake.modules.homeManager.workstation-apps =
|
||||
{ pkgs, ... }:
|
||||
{ lib, pkgs, ... }:
|
||||
{
|
||||
home.packages = with pkgs; [
|
||||
spotify
|
||||
@@ -150,8 +65,9 @@
|
||||
dbeaver-bin
|
||||
onlyoffice-desktopeditors
|
||||
pinta
|
||||
zellij
|
||||
audacity
|
||||
];
|
||||
xdg.mimeApps.defaultApplicationPackages = lib.mkAfter [ pkgs.onlyoffice-desktopeditors ];
|
||||
};
|
||||
|
||||
flake.modules.homeManager.xdg =
|
||||
|
||||
@@ -1,38 +1,14 @@
|
||||
{ inputs, ... }:
|
||||
{
|
||||
perSystem = { pkgs, ... }: {
|
||||
packages.devenv = inputs.devenv.packages.${pkgs.stdenv.hostPlatform.system}.devenv;
|
||||
};
|
||||
flake.modules.homeManager.dev-tools =
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
gardenctl = pkgs.stdenvNoCC.mkDerivation {
|
||||
pname = "gardenctl";
|
||||
version = "2.14.0";
|
||||
src = pkgs.fetchurl {
|
||||
url = "https://github.com/gardener/gardenctl-v2/releases/download/v2.14.0/gardenctl_v2_linux_amd64";
|
||||
hash = "sha256-JCZ0eJLgYbd2ZMCCWFlhX4vOhHKZL40oR2a3EZ3i4Q8=";
|
||||
};
|
||||
dontUnpack = true;
|
||||
installPhase = ''
|
||||
install -Dm755 "$src" "$out/bin/gardenctl"
|
||||
install -d "$out/share/zsh/site-functions"
|
||||
"$out/bin/gardenctl" completion zsh > "$out/share/zsh/site-functions/_gardenctl"
|
||||
'';
|
||||
};
|
||||
gardenlogin = pkgs.stdenvNoCC.mkDerivation {
|
||||
pname = "gardenlogin";
|
||||
version = "0.10.0";
|
||||
src = pkgs.fetchurl {
|
||||
url = "https://github.com/gardener/gardenlogin/releases/download/v0.10.0/gardenlogin_linux_amd64";
|
||||
hash = "sha256-X4nHiZ29Mbnhxv/wlyD6PxX1KxzaX2R27N7Muu/gnH8=";
|
||||
};
|
||||
dontUnpack = true;
|
||||
installPhase = ''
|
||||
install -Dm755 "$src" "$out/bin/gardenlogin"
|
||||
ln -s gardenlogin "$out/bin/kubectl-gardenlogin"
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
home.sessionVariables.CARGO_HOME = "${config.xdg.dataHome}/cargo";
|
||||
home.packages = with pkgs; [
|
||||
inputs.devenv.packages.${pkgs.stdenv.hostPlatform.system}.devenv
|
||||
bruno
|
||||
kubectl
|
||||
kubelogin-oidc
|
||||
@@ -40,8 +16,6 @@
|
||||
scc
|
||||
visidata
|
||||
just
|
||||
gardenctl
|
||||
gardenlogin
|
||||
];
|
||||
|
||||
programs.lazygit.enable = true;
|
||||
@@ -51,14 +25,8 @@
|
||||
programs.ripgrep.enable = true;
|
||||
programs.k9s.enable = true;
|
||||
|
||||
programs.direnv = {
|
||||
enable = true;
|
||||
nix-direnv.enable = true;
|
||||
|
||||
config.global = {
|
||||
hide_env_diff = true;
|
||||
log_format = "↳ %s";
|
||||
};
|
||||
};
|
||||
programs.zsh.initContent = ''
|
||||
eval "$(devenv hook zsh)"
|
||||
'';
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
flake.modules.nixos.file-management = {
|
||||
services.gvfs.enable = true;
|
||||
services.udisks2.enable = true;
|
||||
};
|
||||
|
||||
flake.modules.homeManager.file-management =
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
home.packages = [ pkgs.nautilus ];
|
||||
gtk.gtk3.bookmarks = [
|
||||
"sftp://${config.facts.topology.server.sshAlias} Orion VPS"
|
||||
];
|
||||
xdg.mimeApps.defaultApplicationPackages = [ pkgs.nautilus ];
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,63 +1,40 @@
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
account = config.facts.account;
|
||||
mkScope =
|
||||
config: scope:
|
||||
let
|
||||
email = account.emails.${scope}.address;
|
||||
key = config.facts.machine.sshKeys.${scope} or null;
|
||||
hasSigningKey = key != null;
|
||||
privateKeyPath =
|
||||
if hasSigningKey && key.privateKeyPath != null then key.privateKeyPath else "~/.ssh/id_${scope}";
|
||||
in
|
||||
{
|
||||
allowedSigners = lib.optional hasSigningKey "${email} ${key.publicKey}";
|
||||
git = {
|
||||
user = {
|
||||
name = account.realName;
|
||||
inherit email;
|
||||
}
|
||||
// lib.optionalAttrs hasSigningKey {
|
||||
signingKey = "${privateKeyPath}.pub";
|
||||
};
|
||||
}
|
||||
// lib.optionalAttrs hasSigningKey {
|
||||
core.sshCommand = "ssh -i ${privateKeyPath} -o IdentitiesOnly=yes";
|
||||
gpg.ssh.allowedSignersFile = "${config.xdg.configHome}/git/allowed_signers";
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
flake.modules.homeManager.git =
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{ config, ... }:
|
||||
let
|
||||
machine = config.facts.machine;
|
||||
allowedSignersFile = "${config.xdg.configHome}/git/allowed_signers";
|
||||
githubTokenFile = "${config.xdg.configHome}/yookr/github-read-only.token";
|
||||
|
||||
gh = pkgs.symlinkJoin {
|
||||
name = "gh-with-read-only-token-${pkgs.gh.version}";
|
||||
paths = [ pkgs.gh ];
|
||||
nativeBuildInputs = [ pkgs.makeWrapper ];
|
||||
meta.mainProgram = "gh";
|
||||
postBuild = ''
|
||||
wrapProgram "$out/bin/gh" \
|
||||
--run 'if [[ -z "''${GH_TOKEN:-}" && -r "${githubTokenFile}" ]]; then export GH_TOKEN="$(< "${githubTokenFile}")"; fi'
|
||||
'';
|
||||
};
|
||||
|
||||
mkScope =
|
||||
scope:
|
||||
let
|
||||
email = account.emails.${scope}.address;
|
||||
key = machine.sshKeys.${scope} or null;
|
||||
hasSigningKey = key != null;
|
||||
privateKeyPath =
|
||||
if hasSigningKey && key.privateKeyPath != null then key.privateKeyPath else "~/.ssh/id_${scope}";
|
||||
in
|
||||
{
|
||||
allowedSigners = lib.optional hasSigningKey "${email} ${key.publicKey}";
|
||||
git = {
|
||||
user = {
|
||||
name = account.realName;
|
||||
inherit email;
|
||||
}
|
||||
// lib.optionalAttrs hasSigningKey {
|
||||
signingKey = "${privateKeyPath}.pub";
|
||||
};
|
||||
}
|
||||
// lib.optionalAttrs hasSigningKey {
|
||||
core.sshCommand = "ssh -i ${privateKeyPath} -o IdentitiesOnly=yes";
|
||||
gpg.ssh.allowedSignersFile = allowedSignersFile;
|
||||
};
|
||||
};
|
||||
|
||||
personal = mkScope "personal";
|
||||
work = mkScope "work";
|
||||
personal = mkScope config "personal";
|
||||
in
|
||||
{
|
||||
xdg.configFile."git/allowed_signers".text = lib.concatStringsSep "\n" (
|
||||
personal.allowedSigners ++ work.allowedSigners ++ [ "" ]
|
||||
);
|
||||
xdg.configFile."git/allowed_signers".text = lib.concatStringsSep "\n" personal.allowedSigners;
|
||||
|
||||
programs.git = {
|
||||
enable = true;
|
||||
@@ -73,34 +50,52 @@ in
|
||||
email = account.emails.personal.address;
|
||||
};
|
||||
};
|
||||
includes = [
|
||||
{
|
||||
condition = "gitdir:${account.nixosConfigurationPath}/";
|
||||
contents = personal.git;
|
||||
}
|
||||
{
|
||||
condition = "gitdir:${config.home.homeDirectory}/yookr/";
|
||||
contents = work.git;
|
||||
}
|
||||
{
|
||||
condition = "gitdir:${config.xdg.userDirs.projects}/own/";
|
||||
contents = personal.git;
|
||||
}
|
||||
{
|
||||
condition = "gitdir:${config.xdg.userDirs.projects}/ext/";
|
||||
contents = personal.git;
|
||||
}
|
||||
{
|
||||
condition = "gitdir:${config.xdg.userDirs.projects}/forks/";
|
||||
contents = personal.git;
|
||||
}
|
||||
];
|
||||
includes =
|
||||
map
|
||||
(path: {
|
||||
condition = "gitdir:${path}/";
|
||||
contents = personal.git;
|
||||
})
|
||||
[
|
||||
account.nixosConfigurationPath
|
||||
"${config.xdg.userDirs.projects}/own"
|
||||
"${config.xdg.userDirs.projects}/ext"
|
||||
"${config.xdg.userDirs.projects}/forks"
|
||||
];
|
||||
};
|
||||
|
||||
programs.gh = {
|
||||
enable = true;
|
||||
package = gh;
|
||||
settings.git_protocol = "ssh";
|
||||
};
|
||||
};
|
||||
|
||||
flake.modules.homeManager.work-git =
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
work = mkScope config "work";
|
||||
githubTokenFile = "${config.xdg.configHome}/yookr/github-read-only.token";
|
||||
gh = pkgs.symlinkJoin {
|
||||
name = "gh-with-read-only-token-${pkgs.gh.version}";
|
||||
paths = [ pkgs.gh ];
|
||||
nativeBuildInputs = [ pkgs.makeWrapper ];
|
||||
meta.mainProgram = "gh";
|
||||
postBuild = ''
|
||||
wrapProgram "$out/bin/gh" \
|
||||
--run 'if [[ -z "''${GH_TOKEN:-}" && -r "${githubTokenFile}" ]]; then export GH_TOKEN="$(< "${githubTokenFile}")"; fi'
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
xdg.configFile."git/allowed_signers".text = lib.mkAfter (
|
||||
lib.concatStringsSep "\n" work.allowedSigners
|
||||
);
|
||||
programs.git.includes = [
|
||||
{
|
||||
condition = "gitdir:${config.home.homeDirectory}/yookr/";
|
||||
contents = work.git;
|
||||
}
|
||||
];
|
||||
programs.gh.package = gh;
|
||||
};
|
||||
}
|
||||
@@ -15,7 +15,6 @@ in
|
||||
|
||||
settings = {
|
||||
server = {
|
||||
DOMAIN = service.domain;
|
||||
ROOT_URL = "${service.origin}/";
|
||||
HTTP_PORT = 3001;
|
||||
HTTP_ADDR = "127.0.0.1";
|
||||
@@ -1,37 +0,0 @@
|
||||
"""Synthetic key-state checks; never reads a keyboard or records audio."""
|
||||
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import _push_to_talk as ptt
|
||||
|
||||
down = [ptt.key.KEY_LEFTMETA, ptt.key.KEY_D]
|
||||
|
||||
|
||||
def check(states, expected, error=None):
|
||||
device = Mock()
|
||||
device.active_keys.side_effect = states
|
||||
device.read_loop.return_value = iter([object(), object()])
|
||||
if error:
|
||||
device.read_loop.side_effect = error
|
||||
with (
|
||||
patch.object(ptt, "list_devices", return_value=["keyboard"]),
|
||||
patch.object(ptt, "InputDevice", return_value=device),
|
||||
patch.object(ptt.signal, "signal"),
|
||||
patch.object(ptt.subprocess, "run") as run,
|
||||
):
|
||||
try:
|
||||
ptt.main("handy")
|
||||
except (OSError, KeyboardInterrupt):
|
||||
assert error is not None
|
||||
assert [call.args[0][1] for call in run.call_args_list] == expected
|
||||
device.close.assert_called_once()
|
||||
|
||||
|
||||
check([[]], []) # Released before the listener starts: do not record.
|
||||
check([down, []], ["--toggle-transcription", "--toggle-transcription"])
|
||||
check([down, down, down, [ptt.key.KEY_D]], ["--toggle-transcription"] * 2)
|
||||
check([down, down], ["--toggle-transcription", "--cancel"], OSError("unplugged"))
|
||||
check([down, down], ["--toggle-transcription", "--cancel"], KeyboardInterrupt())
|
||||
assert ptt.held([ptt.key.KEY_RIGHTMETA, ptt.key.KEY_D])
|
||||
assert not ptt.held([ptt.key.KEY_LEFTMETA])
|
||||
print("Push-to-talk release, repeat, disconnect and cancellation checks passed")
|
||||
@@ -34,7 +34,7 @@
|
||||
Requisite = [ "handy.service" ];
|
||||
After = [ "handy.service" ];
|
||||
};
|
||||
Service.ExecStart = "${python}/bin/python ${./handy/_push_to_talk.py} ${pkgs.handy}/bin/handy";
|
||||
Service.ExecStart = "${python}/bin/python ${./push_to_talk.py} ${pkgs.handy}/bin/handy";
|
||||
};
|
||||
};
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
@@ -13,7 +13,7 @@ in
|
||||
}:
|
||||
let
|
||||
nvimTreesitter = pkgs.vimPlugins.nvim-treesitter;
|
||||
svelteLanguageServer = import ../_svelte-language-server.nix { inherit pkgs; };
|
||||
svelteLanguageServer = import ./_support/svelte-language-server.nix { inherit pkgs; };
|
||||
svelteGrammar = nvimTreesitter.builtGrammars.svelte.overrideAttrs (_: {
|
||||
version = "0.0.0+rev=c2457b9";
|
||||
src = pkgs.fetchFromGitHub {
|
||||
@@ -224,5 +224,6 @@ in
|
||||
VISUAL = "nvim";
|
||||
};
|
||||
xdg.mimeApps.defaultApplicationPackages = [ pkgs.neovim ];
|
||||
xdg.mimeApps.defaultApplications."text/markdown" = "nvim.desktop";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
;; extends
|
||||
|
||||
(pair
|
||||
key: [
|
||||
(property_identifier) @_key
|
||||
(string
|
||||
(string_fragment) @_key)
|
||||
]
|
||||
value: (template_string) @injection.content
|
||||
(#eq? @_key "sql")
|
||||
(#offset! @injection.content 0 1 0 -1)
|
||||
(#set! injection.include-children)
|
||||
(#set! injection.language "sql"))
|
||||
@@ -1,7 +1,8 @@
|
||||
require("lz.n").load({
|
||||
{
|
||||
"mini.nvim",
|
||||
event = "VimEnter",
|
||||
-- Icons must exist before Oil opens an initial directory buffer.
|
||||
lazy = false,
|
||||
after = function()
|
||||
require("mini.ai").setup({ n_lines = 500 })
|
||||
require("mini.surround").setup()
|
||||
|
||||
+6
-2
@@ -25,9 +25,13 @@ in
|
||||
spawn = browser;
|
||||
_props.hotkey-overlay-title = "Browser";
|
||||
};
|
||||
"Mod+Shift+N" = {
|
||||
spawn-sh = "rename-workspace";
|
||||
_props.hotkey-overlay-title = "Name workspace (empty clears)";
|
||||
};
|
||||
"Mod+N" = {
|
||||
spawn-sh = "open-daily-note";
|
||||
_props.hotkey-overlay-title = "Daily Note";
|
||||
spawn-sh = "open-notes";
|
||||
_props.hotkey-overlay-title = "Find or create notes";
|
||||
};
|
||||
"Mod+Space" = {
|
||||
_props.repeat = false;
|
||||
File renamed without changes.
@@ -13,10 +13,6 @@ in
|
||||
|
||||
programs.niri.enable = true;
|
||||
|
||||
services = {
|
||||
gvfs.enable = true;
|
||||
udisks2.enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
flake.modules.homeManager.niri =
|
||||
@@ -27,7 +23,22 @@ in
|
||||
...
|
||||
}:
|
||||
let
|
||||
swapMonitorWorkspaces = import ./_shortcuts.nix { inherit pkgs; };
|
||||
swapMonitorWorkspaces = import ./_support/shortcuts.nix { inherit pkgs; };
|
||||
renameWorkspace = pkgs.writeShellApplication {
|
||||
name = "rename-workspace";
|
||||
runtimeInputs = [
|
||||
pkgs.niri
|
||||
pkgs.zenity
|
||||
];
|
||||
text = ''
|
||||
name=$(zenity --entry --title="Workspace name" --text="Name this workspace (empty clears its name):") || exit 0
|
||||
if [[ -n "$name" ]]; then
|
||||
niri msg action set-workspace-name "$name"
|
||||
else
|
||||
niri msg action unset-workspace-name
|
||||
fi
|
||||
'';
|
||||
};
|
||||
machine = config.facts.machine;
|
||||
in
|
||||
{
|
||||
@@ -41,7 +52,10 @@ in
|
||||
wireplumber
|
||||
wtype
|
||||
]
|
||||
++ [ swapMonitorWorkspaces ];
|
||||
++ [
|
||||
swapMonitorWorkspaces
|
||||
renameWorkspace
|
||||
];
|
||||
};
|
||||
|
||||
wayland.windowManager.niri = {
|
||||
@@ -121,7 +135,7 @@ in
|
||||
};
|
||||
};
|
||||
|
||||
binds = import ./_bindings.nix {
|
||||
binds = import ./_support/bindings.nix {
|
||||
inherit (config.desktop.commands)
|
||||
browser
|
||||
launcher
|
||||
|
||||
@@ -14,6 +14,14 @@ in
|
||||
settings = {
|
||||
trusted-users = [ "@wheel" ];
|
||||
use-xdg-base-directories = true;
|
||||
extra-substituters = [
|
||||
"https://devenv.cachix.org"
|
||||
"https://noctalia.cachix.org"
|
||||
];
|
||||
extra-trusted-public-keys = [
|
||||
"devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw="
|
||||
"noctalia.cachix.org-1:pCOR47nnMEo5thcxNDtzWpOxNFQsBRglJzxWPp3dkU4="
|
||||
];
|
||||
|
||||
experimental-features = [
|
||||
"nix-command"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
flake.modules.nixos.noctalia-greeter = {
|
||||
services.displayManager.noctalia-greeter.enable = true;
|
||||
};
|
||||
}
|
||||
@@ -124,7 +124,11 @@
|
||||
volume.show_label = false;
|
||||
network.show_label = false;
|
||||
battery.show_label = false;
|
||||
workspaces.show_labels = false;
|
||||
workspaces = {
|
||||
show_labels = true;
|
||||
label_source = "name";
|
||||
max_label_chars = 16;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -38,25 +38,25 @@ in
|
||||
|
||||
# {{title}}
|
||||
'';
|
||||
openDailyNote = pkgs.writeShellApplication {
|
||||
name = "open-daily-note";
|
||||
openNotes = pkgs.writeShellApplication {
|
||||
name = "open-notes";
|
||||
text = ''
|
||||
exec ${lib.getExe config.programs.kitty.package} --detach \
|
||||
--directory "${home}/sync/notes" \
|
||||
${lib.getExe pkgs.zsh} -lic 'zk daily'
|
||||
nvim '+autocmd VimEnter * ++once ZkNotes'
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
home.packages = [
|
||||
openDailyNote
|
||||
openNotes
|
||||
(pkgs.makeDesktopItem {
|
||||
name = "notes";
|
||||
desktopName = "Notes";
|
||||
icon = "text-editor";
|
||||
exec = "nvim .";
|
||||
exec = lib.getExe openNotes;
|
||||
path = "${home}/sync/notes";
|
||||
terminal = true;
|
||||
terminal = false;
|
||||
})
|
||||
pkgs.ripgrep-all
|
||||
pkgs.zk
|
||||
|
||||
File renamed without changes.
@@ -8,22 +8,17 @@ let
|
||||
homeModules = config.flake.modules.homeManager;
|
||||
in
|
||||
{
|
||||
flake.modules.nixos.evolution =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
programs.evolution = {
|
||||
enable = true;
|
||||
plugins = [ pkgs.evolution-ews ];
|
||||
};
|
||||
};
|
||||
|
||||
flake.modules.homeManager.email =
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
mailspring = pkgs.mailspring.override {
|
||||
commandLineArgs = "--password-store=gnome-libsecret";
|
||||
};
|
||||
mkOffice365Account =
|
||||
{
|
||||
address,
|
||||
@@ -71,6 +66,9 @@ in
|
||||
};
|
||||
in
|
||||
{
|
||||
home.packages = [ mailspring ];
|
||||
xdg.mimeApps.defaultApplicationPackages = [ mailspring ];
|
||||
|
||||
programs.thunderbird = {
|
||||
enable = true;
|
||||
profiles.${config.home.username} = {
|
||||
|
||||
File renamed without changes.
@@ -1,10 +0,0 @@
|
||||
{
|
||||
flake.modules.nixos.sddm = {
|
||||
services.displayManager.sddm = {
|
||||
enable = true;
|
||||
enableHidpi = true;
|
||||
wayland.enable = true;
|
||||
wayland.compositor = "kwin";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
{ config, ... }:
|
||||
{
|
||||
flake.modules.homeManager.sharepoint-reference =
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
rcloneConfig = "${config.xdg.configHome}/rclone/rclone.conf";
|
||||
destination = "${config.xdg.dataHome}/yookr-reference/sharepoint";
|
||||
in
|
||||
{
|
||||
systemd.user.services.sharepoint-reference-sync = {
|
||||
Unit = {
|
||||
Description = "Refresh the local SharePoint reference mirror";
|
||||
ConditionPathExists = rcloneConfig;
|
||||
# Let an in-flight sync finish; activation must not restart and wait for it.
|
||||
X-SwitchMethod = "keep-old";
|
||||
};
|
||||
|
||||
Service = {
|
||||
Type = "oneshot";
|
||||
UMask = "0077";
|
||||
ExecStart = [
|
||||
"${lib.getExe pkgs.rclone} --config ${rcloneConfig} sync company-sharepoint: ${destination}/Algemeen --max-delete 100"
|
||||
"${lib.getExe pkgs.rclone} --config ${rcloneConfig} sync company-sharepoint-projecten: ${destination}/Projecten --max-delete 100"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
systemd.user.timers.sharepoint-reference-sync = {
|
||||
Unit.Description = "Refresh the local SharePoint reference mirror hourly";
|
||||
|
||||
Timer = {
|
||||
OnCalendar = "hourly";
|
||||
Persistent = true;
|
||||
RandomizedDelaySec = "10m";
|
||||
};
|
||||
|
||||
Install.WantedBy = [ "timers.target" ];
|
||||
};
|
||||
};
|
||||
|
||||
flake.modules.nixos.sharepoint-reference = {
|
||||
home-manager.sharedModules = [ config.flake.modules.homeManager.sharepoint-reference ];
|
||||
};
|
||||
}
|
||||
@@ -52,7 +52,18 @@
|
||||
history.path = "${config.xdg.dataHome}/zsh_history";
|
||||
|
||||
initContent = ''
|
||||
source <(gardenctl rc zsh --no-completion)
|
||||
# Terminal/window switchers show the working directory and active command.
|
||||
autoload -Uz add-zsh-hook
|
||||
function terminal-title-precmd() {
|
||||
print -Pn '\e]2;%m: %~\a'
|
||||
}
|
||||
function terminal-title-preexec() {
|
||||
local command="''${1//[[:cntrl:]]/}"
|
||||
print -Pn '\e]2;%m: %~ — '
|
||||
print -rn -- "$command"$'\a'
|
||||
}
|
||||
add-zsh-hook precmd terminal-title-precmd
|
||||
add-zsh-hook preexec terminal-title-preexec
|
||||
|
||||
bindkey -v
|
||||
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
---
|
||||
name: nixos-maintainer
|
||||
description: Maintain this NixOS and Home Manager flake by inspecting, changing, simplifying, or validating its configuration. Use for system configuration work in the NixOS repository, not generic Nix questions or unrelated projects.
|
||||
---
|
||||
|
||||
# NixOS Maintainer
|
||||
|
||||
Read the applicable `AGENTS.md` and the complete current owner of the behavior
|
||||
before editing. Trace its imports through the host, profile, and capability
|
||||
modules so the change lands at the narrowest shared level that matches its real
|
||||
scope.
|
||||
|
||||
Prefer an existing module or native NixOS/Home Manager option. Keep mutable,
|
||||
secret, credential, and application-owned state outside the Nix store. Verify
|
||||
unfamiliar pinned interfaces against the exact package source or current primary
|
||||
documentation.
|
||||
|
||||
Preserve unrelated work. Format touched Nix files and use the narrowest useful
|
||||
evaluation for configuration changes. Build affected hosts only when needed to
|
||||
resolve a concrete concern or explicitly requested; instruction-only edits do
|
||||
not need system builds. Do not add tests or migration/compatibility code by
|
||||
default. Explain manual steps for breaking changes.
|
||||
|
||||
Do not switch or boot a generation unless the user explicitly requests activation.
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
flake.modules.nixos.tailscale = {
|
||||
services.tailscale.enable = true;
|
||||
# Keep existing SSH exposure until tailnet enrollment and access are verified.
|
||||
networking.firewall.interfaces.tailscale0.allowedTCPPorts = [ 22 ];
|
||||
};
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
+68
-31
@@ -1,7 +1,8 @@
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
palette = (import ./_kanagawa-palette.nix).palette;
|
||||
palette = (import ./palette.nix).palette;
|
||||
hex = lib.removePrefix "#";
|
||||
wallpaper = ./assets/green-leaves.png;
|
||||
cursor = {
|
||||
name = "phinger-cursors-light";
|
||||
size = 24;
|
||||
@@ -63,8 +64,12 @@ in
|
||||
gtk4.theme = null;
|
||||
iconTheme = {
|
||||
name = "Kanagawa";
|
||||
package = pkgs.kanagawa-icon-theme.overrideAttrs (_: {
|
||||
package = pkgs.kanagawa-icon-theme.overrideAttrs (old: {
|
||||
inherit src version;
|
||||
postInstall = (old.postInstall or "") + ''
|
||||
find "$out/share/icons" -name index.theme -exec \
|
||||
sed -i 's/^Inherits=.*/Inherits=Adwaita,hicolor/' {} +
|
||||
'';
|
||||
});
|
||||
};
|
||||
};
|
||||
@@ -74,6 +79,8 @@ in
|
||||
platformTheme.name = "gtk3";
|
||||
};
|
||||
|
||||
home.packages = [ pkgs.adwaita-icon-theme ];
|
||||
|
||||
xdg.systemDirs.data = [
|
||||
"${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name}"
|
||||
"${pkgs.gtk3}/share/gsettings-schemas/${pkgs.gtk3.name}"
|
||||
@@ -89,7 +96,7 @@ in
|
||||
{
|
||||
boot.loader.limine.style = {
|
||||
wallpapers = [ pkgs.nixos-artwork.wallpapers.simple-dark-gray-bootloader.gnomeFilePath ];
|
||||
wallpaperStyle = "stretched";
|
||||
wallpaperStyle = "centered";
|
||||
backdrop = hex palette.secondaryBackground;
|
||||
|
||||
graphicalTerminal = {
|
||||
@@ -127,21 +134,56 @@ in
|
||||
};
|
||||
|
||||
interface = {
|
||||
branding = config.networking.hostName;
|
||||
branding = "NixOS - ${config.networking.hostName}";
|
||||
brandingColor = hex palette.accents.blue;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
flake.modules.nixos.sddm = { pkgs, ... }: {
|
||||
services.displayManager.sddm = {
|
||||
theme = "${
|
||||
pkgs.sddm-astronaut.override { embeddedTheme = "purple_leaves"; }
|
||||
}/share/sddm/themes/sddm-astronaut-theme";
|
||||
extraPackages = [ pkgs.kdePackages.qtmultimedia ];
|
||||
settings.Theme = {
|
||||
CursorTheme = cursor.name;
|
||||
CursorSize = cursor.size;
|
||||
flake.modules.nixos.noctalia-greeter = { pkgs, ... }: {
|
||||
services.displayManager.noctalia-greeter = {
|
||||
cursorTheme = {
|
||||
package = pkgs.phinger-cursors;
|
||||
name = cursor.name;
|
||||
};
|
||||
settings = {
|
||||
appearance = {
|
||||
scheme = "Synced";
|
||||
password_style = "default";
|
||||
hide_logo = true;
|
||||
power_buttons_position = "bottom-right";
|
||||
scheme_selector_position = "hidden";
|
||||
theme_mode = "dark";
|
||||
corner_radius_scale = 1.0;
|
||||
font_family = "Lexend";
|
||||
|
||||
palette = {
|
||||
primary = palette.terminal.color2;
|
||||
on_primary = palette.background;
|
||||
secondary = palette.terminal.color3;
|
||||
on_secondary = palette.background;
|
||||
tertiary = palette.terminal.color4;
|
||||
on_tertiary = palette.background;
|
||||
error = palette.terminal.color1;
|
||||
on_error = palette.background;
|
||||
surface = palette.background;
|
||||
on_surface = palette.selectionForeground;
|
||||
surface_variant = "#2A2A37";
|
||||
on_surface_variant = "#717C7C";
|
||||
outline = "#363646";
|
||||
shadow = palette.background;
|
||||
hover = palette.terminal.color4;
|
||||
on_hover = palette.background;
|
||||
};
|
||||
|
||||
wallpaper = {
|
||||
path = toString wallpaper;
|
||||
fill_mode = "crop";
|
||||
fill_color = palette.background;
|
||||
};
|
||||
};
|
||||
|
||||
cursor.size = cursor.size;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -225,25 +267,20 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
flake.modules.homeManager.noctalia =
|
||||
{ config, ... }:
|
||||
let
|
||||
wallpaper = "${config.home.homeDirectory}/media/images/wallpapers/green-leaves.png";
|
||||
in
|
||||
{
|
||||
programs.noctalia.settings = {
|
||||
shell.font_family = "Lexend";
|
||||
theme = {
|
||||
builtin = "Kanagawa";
|
||||
community_palette = "Kanagawa Paper";
|
||||
wallpaper_scheme = "m3-content";
|
||||
};
|
||||
wallpaper = {
|
||||
default.path = wallpaper;
|
||||
last.path = wallpaper;
|
||||
};
|
||||
flake.modules.homeManager.noctalia = {
|
||||
programs.noctalia.settings = {
|
||||
shell.font_family = "Lexend";
|
||||
theme = {
|
||||
builtin = "Kanagawa";
|
||||
community_palette = "Kanagawa Paper";
|
||||
wallpaper_scheme = "m3-content";
|
||||
};
|
||||
wallpaper = {
|
||||
default.path = toString wallpaper;
|
||||
last.path = toString wallpaper;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
flake.modules.homeManager.neovim = { pkgs, ... }: {
|
||||
wrappers.neovim.specs.theme = {
|
||||
@@ -266,7 +303,7 @@ in
|
||||
};
|
||||
|
||||
flake.modules.homeManager.walker = {
|
||||
programs.walker.themes.constellation.style = lib.mkAfter ''
|
||||
programs.walker.themes.desktop.style = lib.mkAfter ''
|
||||
@define-color window_bg_color ${palette.background};
|
||||
@define-color accent_bg_color ${palette.selectionBackground};
|
||||
@define-color theme_fg_color ${palette.foreground};
|
||||
File renamed without changes.
@@ -1,2 +1,2 @@
|
||||
# Select a complete theme module: plugins, assets and behavior as well as colors.
|
||||
{ imports = [ ./_themes/kanagawa.nix ]; }
|
||||
{ imports = [ ./_support/kanagawa.nix ]; }
|
||||
+2
-2
@@ -31,7 +31,7 @@
|
||||
executable = true;
|
||||
text = ''
|
||||
#!${diarizationPython}/bin/python3
|
||||
${builtins.readFile ./_diarize-transcript.py}
|
||||
${builtins.readFile ./diarize-transcript.py}
|
||||
'';
|
||||
};
|
||||
whisper = pkgs.whisper-cpp;
|
||||
@@ -72,7 +72,7 @@
|
||||
findutils
|
||||
whisper
|
||||
];
|
||||
text = builtins.readFile ./_transcribe-recordings.sh;
|
||||
text = builtins.readFile ./transcribe-recordings.sh;
|
||||
};
|
||||
home = config.home.homeDirectory;
|
||||
in
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,171 +0,0 @@
|
||||
{ config, inputs, ... }:
|
||||
{
|
||||
flake.modules.nixos.walker = {
|
||||
home-manager.sharedModules = [ config.flake.modules.homeManager.walker ];
|
||||
|
||||
nix.settings = {
|
||||
extra-substituters = [
|
||||
"https://walker.cachix.org"
|
||||
"https://walker-git.cachix.org"
|
||||
];
|
||||
extra-trusted-public-keys = [
|
||||
"walker.cachix.org-1:fG8q+uAaMqhsMxWjwvk0IMb4mFPFLqHjuvfwQxE4oJM="
|
||||
"walker-git.cachix.org-1:vmC0ocfPWh0S/vRAQGtChuiZBTAe4wiKDeyyXM0/7pM="
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
flake.modules.homeManager.walker =
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
homeDir = config.home.homeDirectory;
|
||||
projectRoots = [
|
||||
{
|
||||
scope = "work";
|
||||
path = "${homeDir}/yookr/projects";
|
||||
}
|
||||
]
|
||||
++
|
||||
map
|
||||
(scope: {
|
||||
inherit scope;
|
||||
path = "${config.xdg.userDirs.projects}/${scope}";
|
||||
})
|
||||
[
|
||||
"own"
|
||||
"ext"
|
||||
"forks"
|
||||
];
|
||||
walkerDefaults = builtins.fromTOML (builtins.readFile "${inputs.walker}/resources/config.toml");
|
||||
|
||||
launcher = pkgs.writeShellApplication {
|
||||
name = "walker-launcher";
|
||||
runtimeInputs = [ pkgs.libressl.nc ];
|
||||
text = ''
|
||||
exec nc -U "$XDG_RUNTIME_DIR/walker/walker.sock"
|
||||
'';
|
||||
};
|
||||
openProject = pkgs.writeShellApplication {
|
||||
name = "open-project";
|
||||
text = ''
|
||||
project_path="''${1:-}"
|
||||
if [[ -z "$project_path" ]]; then
|
||||
IFS= read -r project_path || [[ -n "$project_path" ]]
|
||||
fi
|
||||
|
||||
exec ${lib.getExe config.xdg.terminal-exec.package} --dir="$project_path" \
|
||||
${lib.getExe pkgs.zsh} -lic 'nvim .; exec zsh -l'
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
imports = [ inputs.walker.homeManagerModules.default ];
|
||||
|
||||
desktop.commands.launcher = [ (lib.getExe launcher) ];
|
||||
home.packages = [ openProject ];
|
||||
|
||||
xdg.configFile."elephant/menus/projects.lua".text = ''
|
||||
Name = "projects"
|
||||
NamePretty = "Projects"
|
||||
Description = "Projects and workspaces"
|
||||
Icon = "folder"
|
||||
Action = "${lib.getExe openProject}"
|
||||
SearchName = true
|
||||
RefreshOnChange = {
|
||||
${lib.concatMapStringsSep "\n " (root: ''"${root.path}",'') projectRoots}
|
||||
}
|
||||
|
||||
local roots = {
|
||||
${lib.concatMapStringsSep "\n " (
|
||||
root: ''{ scope = "${root.scope}", path = "${root.path}" },''
|
||||
) projectRoots}
|
||||
}
|
||||
|
||||
local function repositories(root)
|
||||
return io.popen(
|
||||
"${lib.getExe pkgs.findutils} " .. root ..
|
||||
" -mindepth 1 \\( -type d \\( -name .archive -o -name .worktrees \\) -prune \\)" ..
|
||||
" -o \\( -name .git -print \\) 2>/dev/null"
|
||||
)
|
||||
end
|
||||
|
||||
function GetEntries()
|
||||
local entries = {
|
||||
{
|
||||
Text = "Yookr",
|
||||
Subtext = "Company workspace",
|
||||
Value = "${homeDir}/yookr",
|
||||
Keywords = { "work", "company", "workbench" },
|
||||
},
|
||||
}
|
||||
|
||||
for _, root in ipairs(roots) do
|
||||
local handle = repositories(root.path)
|
||||
if handle then
|
||||
for git_path in handle:lines() do
|
||||
local project = git_path:gsub("/%.git$", "")
|
||||
local relative = project:sub(#root.path + 2)
|
||||
table.insert(entries, {
|
||||
Text = project:match("([^/]+)$"),
|
||||
Subtext = root.scope .. " · " .. relative,
|
||||
Value = project,
|
||||
Keywords = { root.scope, relative, project },
|
||||
})
|
||||
end
|
||||
handle:close()
|
||||
end
|
||||
end
|
||||
|
||||
return entries
|
||||
end
|
||||
'';
|
||||
|
||||
programs.walker = {
|
||||
enable = true;
|
||||
runAsService = true;
|
||||
|
||||
config.theme = "constellation";
|
||||
themes.constellation = {
|
||||
layouts.layout =
|
||||
lib.replaceStrings
|
||||
[ ''<property name="height-request">570</property>'' ]
|
||||
[ ''<property name="height-request">460</property>'' ]
|
||||
(builtins.readFile "${inputs.walker}/resources/themes/default/layout.xml");
|
||||
style = lib.mkBefore (
|
||||
builtins.readFile "${inputs.walker}/resources/themes/default/style.css"
|
||||
+ ''
|
||||
.box-wrapper {
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.search-container, .item-box, .preview { border-radius: 6px; }
|
||||
.input { padding: 8px 10px; }
|
||||
.item-box { padding: 5px 8px; }
|
||||
.large-icons { -gtk-icon-size: 24px; }
|
||||
.item-image-text { font-size: 24px; }
|
||||
.item-subtext { opacity: 0.75; }
|
||||
.item-quick-activation { padding: 3px 5px; }
|
||||
.keybinds { padding-top: 6px; }
|
||||
.keybind-bind { opacity: 0.65; }
|
||||
''
|
||||
);
|
||||
};
|
||||
|
||||
config.providers = {
|
||||
default = walkerDefaults.providers.default ++ [ "menus:projects" ];
|
||||
prefixes = walkerDefaults.providers.prefixes ++ [
|
||||
{
|
||||
provider = "bitwarden";
|
||||
prefix = "?";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
{ config, inputs, ... }:
|
||||
{
|
||||
flake.modules.nixos.walker = {
|
||||
home-manager.sharedModules = [ config.flake.modules.homeManager.walker ];
|
||||
|
||||
nix.settings = {
|
||||
extra-substituters = [
|
||||
"https://walker.cachix.org"
|
||||
"https://walker-git.cachix.org"
|
||||
];
|
||||
extra-trusted-public-keys = [
|
||||
"walker.cachix.org-1:fG8q+uAaMqhsMxWjwvk0IMb4mFPFLqHjuvfwQxE4oJM="
|
||||
"walker-git.cachix.org-1:vmC0ocfPWh0S/vRAQGtChuiZBTAe4wiKDeyyXM0/7pM="
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
flake.modules.homeManager.walker =
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
walkerDefaults = builtins.fromTOML (builtins.readFile "${inputs.walker}/resources/config.toml");
|
||||
|
||||
launcher = pkgs.writeShellApplication {
|
||||
name = "walker-launcher";
|
||||
runtimeInputs = [ pkgs.libressl.nc ];
|
||||
text = ''
|
||||
exec nc -U "$XDG_RUNTIME_DIR/walker/walker.sock"
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
imports = [ inputs.walker.homeManagerModules.default ];
|
||||
|
||||
desktop.commands.launcher = [ (lib.getExe launcher) ];
|
||||
programs.walker = {
|
||||
enable = true;
|
||||
runAsService = true;
|
||||
|
||||
config.theme = "desktop";
|
||||
themes.desktop = {
|
||||
layouts.layout =
|
||||
lib.replaceStrings
|
||||
[ ''<property name="height-request">570</property>'' ]
|
||||
[ ''<property name="height-request">460</property>'' ]
|
||||
(builtins.readFile "${inputs.walker}/resources/themes/default/layout.xml");
|
||||
style = lib.mkBefore (
|
||||
builtins.readFile "${inputs.walker}/resources/themes/default/style.css"
|
||||
+ ''
|
||||
.box-wrapper {
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.search-container, .item-box, .preview { border-radius: 6px; }
|
||||
.input { padding: 8px 10px; }
|
||||
.item-box { padding: 5px 8px; }
|
||||
.large-icons { -gtk-icon-size: 24px; }
|
||||
.item-image-text { font-size: 24px; }
|
||||
.item-subtext { opacity: 0.75; }
|
||||
.item-quick-activation { padding: 3px 5px; }
|
||||
.keybinds { padding-top: 6px; }
|
||||
.keybind-bind { opacity: 0.65; }
|
||||
''
|
||||
);
|
||||
};
|
||||
|
||||
config.providers = {
|
||||
default = walkerDefaults.providers.default;
|
||||
prefixes = walkerDefaults.providers.prefixes ++ [
|
||||
{
|
||||
provider = "bitwarden";
|
||||
prefix = "?";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
# Wardrowbe on Orion
|
||||
|
||||
[Wardrowbe](https://github.com/Anyesh/wardrowbe) is served through the existing
|
||||
Caddy instance. The capability owns container images, routing, runtime secret
|
||||
templates and startup ordering. Image references and settings live in
|
||||
`default.nix`; endpoints live in the shared service facts.
|
||||
|
||||
Internal AI is disabled. Upload clothes and enter tags manually; automatic tagging
|
||||
and AI outfit generation are unavailable. Local rotation and background removal
|
||||
remain available. No AI provider key or notification provider is configured.
|
||||
|
||||
Tinyauth provides a cookie-based password gateway. After authenticating there,
|
||||
Wardrowbe's development login accepts an email/name to select a profile. Use a
|
||||
consistent email for each person. This is for a trusted household: gateway users
|
||||
can select one another's profiles. It is not verified individual identity.
|
||||
|
||||
## Provision before building or activating Orion
|
||||
|
||||
Add these top-level keys with `sops edit modules/secrets/secrets.yaml`, using the
|
||||
existing system-secret recipients. The keys are deliberately not provisioned by
|
||||
the configuration change; Orion's SOPS build checks require them to exist.
|
||||
|
||||
| Key | Value |
|
||||
| --- | --- |
|
||||
| `wardrowbe-postgres-password` | A new 64-character hexadecimal password |
|
||||
| `wardrowbe-secret-key` | A separate new 64-character hexadecimal signing secret |
|
||||
| `wardrowbe-nextauth-secret` | A separate new 64-character hexadecimal session secret |
|
||||
| `wardrowbe-auth-users` | A single-line `kiri:bcrypt-hash` string |
|
||||
|
||||
Generate each of the first three separately with `openssl rand -hex 32`. If
|
||||
OpenSSL is unavailable, use `nix run --quiet nixpkgs#openssl -- rand -hex 32`.
|
||||
Keep values inside the encrypted document; do not paste them into Nix or chat.
|
||||
Hexadecimal database passwords work directly in the application's connection URL.
|
||||
|
||||
On a workstation with Podman, create the gateway user interactively:
|
||||
|
||||
```console
|
||||
podman run --rm -it ghcr.io/tinyauthapp/tinyauth:v5.2.0@sha256:220aa235c76393a73fece831d7ba967b8698d3907fd8e3c4c952f699c7b142a5 user create --interactive
|
||||
```
|
||||
|
||||
Choose username `kiri` and your password, and answer **No** to Docker formatting.
|
||||
Put the emitted `username:hash` string
|
||||
in `wardrowbe-auth-users` as a quoted YAML scalar. Additional gateway accounts can
|
||||
be added as a comma-separated list on the same line. If Podman is unavailable,
|
||||
prefix its arguments with `nix run --quiet nixpkgs#podman --` instead of `podman`.
|
||||
This command only creates a hash; gateway sessions persist separately on Orion.
|
||||
|
||||
Create DNS records for `wardrobe.jelles.net` and `wardrobe-auth.jelles.net` pointing
|
||||
at Orion. Publish an AAAA record only if Orion has working public IPv6. Existing
|
||||
Caddy firewall access supplies HTTP/HTTPS and automatic certificates.
|
||||
|
||||
The private container network reserves `10.89.30.0/24`, with gateway
|
||||
`10.89.30.1`. It must not overlap Orion's routes. If changing it, update both the
|
||||
network declaration and Tinyauth's trusted-proxy address before first activation.
|
||||
|
||||
Add new source files to Git before using a Git-backed flake. From this repository,
|
||||
activate explicitly:
|
||||
|
||||
```console
|
||||
nixos-rebuild switch --quiet --target-host orion --sudo --flake .#orion
|
||||
```
|
||||
|
||||
The initial activation downloads container images, including the image-processing
|
||||
model. Allow time and disk space for those downloads. Containers start on boot;
|
||||
PostgreSQL and Redis become healthy before the backend runs `alembic upgrade head`.
|
||||
The frontend and workers wait for the backend's health check. A failed migration
|
||||
prevents the API from starting. Each container has a 15-minute startup deadline.
|
||||
|
||||
## Operation
|
||||
|
||||
Run these commands on Orion. Container names and unit names correspond directly
|
||||
to the declarations in `default.nix`:
|
||||
|
||||
```console
|
||||
sudo systemctl status podman-wardrowbe-backend.service
|
||||
sudo journalctl -u podman-wardrowbe-backend.service -n 80 --no-pager
|
||||
sudo podman ps --filter name=wardrowbe
|
||||
sudo podman volume ls --filter name=wardrowbe
|
||||
```
|
||||
|
||||
Persistent named volumes contain PostgreSQL, Redis's append-only queue, uploaded
|
||||
images and Tinyauth's SQLite sessions. They are root-owned Podman state under
|
||||
`/var/lib/containers/storage/volumes`, outside Nix. Container recreation and system
|
||||
rollback preserve these volumes; do not remove them or prune volumes. The network
|
||||
is also retained when services stop.
|
||||
|
||||
Only Caddy serves public traffic. Frontend, backend and gateway host ports bind to
|
||||
localhost; database and queue ports remain inside the container network. Every
|
||||
Wardrowbe route, including API and signed image URLs, passes through the gateway.
|
||||
The gateway's own login endpoint is public. Gateway cookies are secure, HTTP-only
|
||||
and scoped to `jelles.net` to support redirects between the two subdomains.
|
||||
|
||||
SOPS renders root-only mode-0400 environment files at activation. Changes to their
|
||||
contents restart the consuming containers. Rotating session secrets signs users out.
|
||||
Changing `POSTGRES_PASSWORD` alone does **not** change an existing database role's
|
||||
password: coordinate the database password change with the encrypted value.
|
||||
|
||||
## Upgrade and recovery
|
||||
|
||||
Review upstream release notes and migrations, then update the image tags and
|
||||
manifest digests together. Upgrade backend and frontend to the same release;
|
||||
workers use the backend image. Image updates happen through explicit configuration
|
||||
activation, with no automatic image updater. Review resource requirements before
|
||||
enabling AI later.
|
||||
|
||||
If startup fails, inspect the failing container unit's journal. After correcting
|
||||
the cause, start the affected units and any dependents whose startup was blocked.
|
||||
To restart the complete application in dependency order:
|
||||
|
||||
```console
|
||||
sudo systemctl restart podman-wardrowbe-postgres.service podman-wardrowbe-redis.service podman-wardrowbe-backend.service podman-wardrowbe-worker.service podman-wardrowbe-image-worker.service podman-wardrowbe-frontend.service podman-wardrowbe-auth.service
|
||||
```
|
||||
|
||||
Reverting a NixOS generation restores configuration and image references, not
|
||||
database schema changes. Do not assume an older application can use a migrated
|
||||
database; follow upstream recovery guidance. No backup system is added here.
|
||||
|
||||
## Manual acceptance after activation
|
||||
|
||||
- Open the wardrobe in a private browser window. It should require the gateway
|
||||
password; unauthenticated API and image requests should also be blocked.
|
||||
- Authenticate, select a Wardrowbe profile, complete onboarding and upload a photo.
|
||||
Add tags manually and confirm the photo renders; try a rotation.
|
||||
- With the gateway session active, open `/api/v1/capabilities` on the wardrobe
|
||||
origin. Both internal AI capabilities should be `false`.
|
||||
- Restart the application and confirm the profile, photo and tags remain present.
|
||||
- Check that both public hostnames have valid HTTPS certificates and that the
|
||||
existing Orion web services still work.
|
||||
|
||||
No tests, builds, activation or browser acceptance checks were performed while
|
||||
preparing this capability.
|
||||
@@ -0,0 +1,234 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
nixosModules = config.flake.modules.nixos;
|
||||
in
|
||||
{
|
||||
flake.modules.nixos.wardrowbe =
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
service = config.facts.services.wardrowbe;
|
||||
auth = config.facts.services.wardrowbe-auth;
|
||||
podman = lib.getExe config.virtualisation.podman.package;
|
||||
# Multi-architecture manifest digests; updating a tag alone cannot change an image.
|
||||
images = {
|
||||
backend = "ghcr.io/anyesh/wardrowbe:backend-1.10.3@sha256:75fd6f85fb7b91ccbb227b52d616bab11b5130c4e22858579f45736419fb592d";
|
||||
frontend = "ghcr.io/anyesh/wardrowbe:frontend-1.10.3@sha256:730cf52bd8a03b94473b129e9a988b3dae9818e9377fd88917029156ee7a2e17";
|
||||
auth = "ghcr.io/tinyauthapp/tinyauth:v5.2.0@sha256:220aa235c76393a73fece831d7ba967b8698d3907fd8e3c4c952f699c7b142a5";
|
||||
postgres = "docker.io/library/postgres:15-alpine@sha256:f7d23353e1b15400d22ebe31189f4d314b87a4c129cc400c8c2d8d4ca127bf81";
|
||||
redis = "docker.io/library/redis:7-alpine@sha256:858f009f9709ce576febc734aa78b8f6d624b82571f9ddb6bda4377c833b3499";
|
||||
};
|
||||
healthOptions = [
|
||||
"--health-interval=10s"
|
||||
"--health-timeout=5s"
|
||||
"--health-start-period=30s"
|
||||
"--health-retries=6"
|
||||
"--health-on-failure=kill"
|
||||
];
|
||||
backendContainer = {
|
||||
image = images.backend;
|
||||
environment = {
|
||||
# Profile selection is available only behind the password gateway.
|
||||
DEBUG = "true";
|
||||
AI_INTERNAL_ENABLED = "false";
|
||||
AI_VISION_ENABLED = "false";
|
||||
AI_TEXT_ENABLED = "false";
|
||||
APP_URL = service.origin;
|
||||
CORS_ORIGINS = builtins.toJSON [ service.origin ];
|
||||
REDIS_URL = "redis://wardrowbe-redis:6379/0";
|
||||
STORAGE_PATH = "/data/uploads";
|
||||
LOG_LEVEL = "INFO";
|
||||
};
|
||||
environmentFiles = [ config.sops.templates."wardrowbe-backend.env".path ];
|
||||
volumes = [ "wardrowbe-uploads:/data/uploads" ];
|
||||
};
|
||||
backendUnits = [
|
||||
"podman-wardrowbe-backend.service"
|
||||
"podman-wardrowbe-worker.service"
|
||||
"podman-wardrowbe-image-worker.service"
|
||||
];
|
||||
containers = {
|
||||
wardrowbe-postgres = {
|
||||
image = images.postgres;
|
||||
environment = {
|
||||
POSTGRES_USER = "wardrobe";
|
||||
POSTGRES_DB = "wardrobe";
|
||||
};
|
||||
environmentFiles = [ config.sops.templates."wardrowbe-postgres.env".path ];
|
||||
volumes = [ "wardrowbe-postgres:/var/lib/postgresql/data" ];
|
||||
podman.sdnotify = "healthy";
|
||||
extraOptions = healthOptions ++ [
|
||||
"--health-cmd=pg_isready -h 127.0.0.1 -U wardrobe -d wardrobe"
|
||||
];
|
||||
};
|
||||
wardrowbe-redis = {
|
||||
image = images.redis;
|
||||
cmd = [
|
||||
"redis-server"
|
||||
"--appendonly"
|
||||
"yes"
|
||||
];
|
||||
volumes = [ "wardrowbe-redis:/data" ];
|
||||
podman.sdnotify = "healthy";
|
||||
extraOptions = healthOptions ++ [ "--health-cmd=redis-cli ping" ];
|
||||
};
|
||||
wardrowbe-backend = backendContainer // {
|
||||
dependsOn = [
|
||||
"wardrowbe-postgres"
|
||||
"wardrowbe-redis"
|
||||
];
|
||||
ports = [ "127.0.0.1:8002:8000" ];
|
||||
cmd = [
|
||||
"sh"
|
||||
"-c"
|
||||
"alembic upgrade head && exec uvicorn app.main:app --host 0.0.0.0 --port 8000"
|
||||
];
|
||||
podman.sdnotify = "healthy";
|
||||
extraOptions = healthOptions ++ [
|
||||
"--health-cmd=curl --fail --silent http://127.0.0.1:8000/api/v1/health"
|
||||
];
|
||||
};
|
||||
wardrowbe-worker = backendContainer // {
|
||||
dependsOn = [ "wardrowbe-backend" ];
|
||||
cmd = [
|
||||
"arq"
|
||||
"app.workers.worker.WorkerSettings"
|
||||
];
|
||||
};
|
||||
wardrowbe-image-worker = backendContainer // {
|
||||
dependsOn = [ "wardrowbe-backend" ];
|
||||
cmd = [
|
||||
"arq"
|
||||
"app.workers.image_worker.ImageWorkerSettings"
|
||||
];
|
||||
};
|
||||
wardrowbe-frontend = {
|
||||
image = images.frontend;
|
||||
dependsOn = [ "wardrowbe-backend" ];
|
||||
environment = {
|
||||
BACKEND_URL = "http://wardrowbe-backend:8000";
|
||||
NEXTAUTH_URL = service.origin;
|
||||
DEV_MODE = "true";
|
||||
};
|
||||
environmentFiles = [ config.sops.templates."wardrowbe-frontend.env".path ];
|
||||
ports = [ "127.0.0.1:3002:3000" ];
|
||||
podman.sdnotify = "healthy";
|
||||
extraOptions = healthOptions ++ [
|
||||
"--health-cmd=node -e \"fetch('http://127.0.0.1:3000').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))\""
|
||||
];
|
||||
};
|
||||
wardrowbe-auth = {
|
||||
image = images.auth;
|
||||
environment = {
|
||||
TINYAUTH_APPURL = auth.origin;
|
||||
TINYAUTH_AUTH_SECURECOOKIE = "true";
|
||||
TINYAUTH_AUTH_TRUSTEDPROXIES = "10.89.30.1";
|
||||
TINYAUTH_LABELPROVIDER = "none";
|
||||
TINYAUTH_ANALYTICS_ENABLED = "false";
|
||||
TINYAUTH_DATABASE_PATH = "/data/tinyauth.db";
|
||||
TINYAUTH_UI_TITLE = "Wardrobe login";
|
||||
};
|
||||
environmentFiles = [ config.sops.templates."wardrowbe-auth.env".path ];
|
||||
volumes = [ "wardrowbe-auth:/data" ];
|
||||
ports = [ "127.0.0.1:3003:3000" ];
|
||||
podman.sdnotify = "healthy";
|
||||
extraOptions = healthOptions ++ [ "--health-cmd=tinyauth healthcheck" ];
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
nixosModules.caddy
|
||||
nixosModules.sops
|
||||
];
|
||||
|
||||
sops.secrets = {
|
||||
wardrowbe-postgres-password = { };
|
||||
wardrowbe-secret-key = { };
|
||||
wardrowbe-nextauth-secret = { };
|
||||
wardrowbe-auth-users = { };
|
||||
};
|
||||
sops.templates = {
|
||||
"wardrowbe-postgres.env" = {
|
||||
content = ''
|
||||
POSTGRES_PASSWORD=${config.sops.placeholder.wardrowbe-postgres-password}
|
||||
'';
|
||||
restartUnits = [ "podman-wardrowbe-postgres.service" ];
|
||||
};
|
||||
"wardrowbe-backend.env" = {
|
||||
content = ''
|
||||
DATABASE_URL=postgresql+asyncpg://wardrobe:${config.sops.placeholder.wardrowbe-postgres-password}@wardrowbe-postgres:5432/wardrobe
|
||||
SECRET_KEY=${config.sops.placeholder.wardrowbe-secret-key}
|
||||
'';
|
||||
restartUnits = backendUnits;
|
||||
};
|
||||
"wardrowbe-frontend.env" = {
|
||||
content = ''
|
||||
NEXTAUTH_SECRET=${config.sops.placeholder.wardrowbe-nextauth-secret}
|
||||
'';
|
||||
restartUnits = [ "podman-wardrowbe-frontend.service" ];
|
||||
};
|
||||
"wardrowbe-auth.env" = {
|
||||
content = ''
|
||||
TINYAUTH_AUTH_USERS=${config.sops.placeholder.wardrowbe-auth-users}
|
||||
'';
|
||||
restartUnits = [ "podman-wardrowbe-auth.service" ];
|
||||
};
|
||||
};
|
||||
|
||||
virtualisation.oci-containers = {
|
||||
backend = "podman";
|
||||
containers = lib.mapAttrs (
|
||||
_: container:
|
||||
{
|
||||
networks = [ "wardrowbe" ];
|
||||
}
|
||||
// container
|
||||
) containers;
|
||||
};
|
||||
|
||||
systemd.services = {
|
||||
wardrowbe-network = {
|
||||
description = "Wardrowbe container network";
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
script = ''
|
||||
${podman} network exists wardrowbe || \
|
||||
${podman} network create --subnet 10.89.30.0/24 --gateway 10.89.30.1 wardrowbe
|
||||
'';
|
||||
};
|
||||
}
|
||||
// lib.mapAttrs' (
|
||||
name: _:
|
||||
lib.nameValuePair "podman-${name}" {
|
||||
requires = [ "wardrowbe-network.service" ];
|
||||
after = [ "wardrowbe-network.service" ];
|
||||
serviceConfig.TimeoutStartSec = lib.mkForce 900;
|
||||
}
|
||||
) containers;
|
||||
|
||||
services.caddy.virtualHosts = {
|
||||
${auth.domain}.extraConfig = "reverse_proxy 127.0.0.1:3003";
|
||||
${service.domain}.extraConfig = ''
|
||||
request_body {
|
||||
max_size 50MB
|
||||
}
|
||||
route {
|
||||
forward_auth 127.0.0.1:3003 {
|
||||
uri /api/auth/caddy
|
||||
}
|
||||
handle /api/auth/* {
|
||||
reverse_proxy 127.0.0.1:3002
|
||||
}
|
||||
handle /api/* {
|
||||
reverse_proxy 127.0.0.1:8002
|
||||
}
|
||||
handle {
|
||||
reverse_proxy 127.0.0.1:3002
|
||||
}
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
# Company environment
|
||||
|
||||
This capability owns the company-root tools, human workbench environment and
|
||||
persistent read-only company mounts. Garden packaging and company certificates
|
||||
remain in `../yookr-dev/`. Requests, queries, analysis, `uv.lock`, content and
|
||||
runtime state stay in `~/yookr/workbench`.
|
||||
|
||||
Home Manager installs `devenv.nix` and `devenv.yaml` in `~/yookr/workbench`
|
||||
(human credentials), selecting the system's exact nixpkgs store path. The work
|
||||
profile also installs the tools for use at the company root without activation. The devenv CLI
|
||||
uses its own upstream dependency closure; this avoids rebuilding its dependencies
|
||||
against an unsupported pin. Its version is locked by the repository flake.
|
||||
|
||||
## Activation and trust
|
||||
|
||||
After reviewing and activating the configuration on each workstation:
|
||||
|
||||
1. Remove the obsolete `~/yookr/.envrc` and `~/yookr/workbench/.envrc`. Direnv is
|
||||
no longer installed or hooked. Product/personal `.envrc` files remain yours to
|
||||
migrate; this pass does not convert them.
|
||||
2. Run `devenv allow` in `~/yookr/workbench` only. Open a new terminal
|
||||
for the native Zsh hook. Reload/file watching uses upstream defaults.
|
||||
3. `devenv.lock` and `.devenv/` are local generated state, not new configuration
|
||||
owners. Do not commit them into the Nix repository. Retain product locks.
|
||||
4. The human workbench attempts company, live and test resolution independently.
|
||||
`workbench-reload` retries; `workbench-reload --tools-only` clears the variables
|
||||
owned by this loader. Existing processes/editors keep their original environment.
|
||||
|
||||
For a clean, credential-free check use the flake's tools-only `#workbench` shell
|
||||
from a clean parent environment. For native devenv without credential loading,
|
||||
set `WORKBENCH_TOOLS_ONLY=1` before `devenv shell`. Neither method removes arbitrary
|
||||
credentials already inherited from its parent; they are not isolation mechanisms.
|
||||
|
||||
The company root intentionally has no generated devenv environment pending review:
|
||||
upstream's hook does nothing while another devenv shell is active, so an ancestor
|
||||
shell would prevent automatic workbench entry and interfere with project environments.
|
||||
The recommendation is installed tools at the company root, native activation at the
|
||||
workbench, and product-owned environments below `projects/`.
|
||||
|
||||
## Credential provisioning (user-run only)
|
||||
|
||||
The encrypted company document belongs at `modules/secrets/company.yaml` in this
|
||||
repository. It is deliberately absent until you migrate it. Reuse the existing
|
||||
machine age identities; never copy private keys into Git. On each workstation,
|
||||
obtain its public recipient with `age-keygen -y /var/lib/sops/keys.txt`.
|
||||
|
||||
On the machine holding the old credentials, run from this repository:
|
||||
|
||||
```console
|
||||
SOPS_AGE_KEY_FILE="$HOME/.config/sops/age/keys.txt" python3 modules/capabilities/workbench/migrate-credentials.py \
|
||||
"$HOME/.config/yookr/credentials.yaml" modules/secrets/company.yaml \
|
||||
--age 'POLARIS_PUBLIC_AGE_RECIPIENT,ZENITH_PUBLIC_AGE_RECIPIENT'
|
||||
```
|
||||
|
||||
The helper decrypts only in memory, preserves the old file, and refuses to overwrite
|
||||
an existing destination. The new document contains root-level company login and
|
||||
live database URLs for SecretSpec, plus the existing `leafcloud` map for mounts.
|
||||
MySQL certificate verification behavior is preserved. Test credentials are not
|
||||
copied into the new document. Review the encrypted result before a local commit;
|
||||
pushing remains a separate decision. Other machines receive ciphertext through the
|
||||
existing repository. Edit it with `sops edit modules/secrets/company.yaml` using the
|
||||
machine's normal `SOPS_AGE_KEY_FILE=/var/lib/sops/keys.txt`.
|
||||
|
||||
### Test cluster: mapping and login still pending
|
||||
|
||||
No cluster query is needed to build this repository. Before enabling test loading,
|
||||
settle the explicit test context and existing application Secret/namespace/key
|
||||
mappings. The loader expects a `~/.config/workbench/test-secrets.toml` manifest with
|
||||
profile `test`, using only explicit `k8s+secret://SECRET@NAMESPACE` providers and
|
||||
`ref.item` for existing keys. Required exports are:
|
||||
|
||||
- `YOOKR_MYSQL_TEST_DATABASE_URL`, `YOOKR_MONGODB_TEST_DATABASE_URL`
|
||||
- `RABBITMQADMIN_USERNAME`, `RABBITMQADMIN_PASSWORD`
|
||||
|
||||
If applications store URL components separately, the manifest/composition must be
|
||||
adapted after confirming their keys; do not invent field names or change URL escaping.
|
||||
This manifest is a provisioning boundary pending review, not a second global secret store.
|
||||
|
||||
Test loading also requires an explicitly prepared, noninteractive kubeconfig at
|
||||
`$XDG_RUNTIME_DIR/workbench/test-kubeconfig`. No generic login helper is provided:
|
||||
the actual context and exec plugin must be inspected first, then its native or
|
||||
provider-specific cached login flow selected. Do not reimplement Kubernetes' exec
|
||||
credential protocol for an unknown plugin. Until both runtime files exist, test
|
||||
reports unavailable. Shell entry never invokes authentication or opens a login popup.
|
||||
|
||||
SecretSpec exports are captured in memory with stdin closed and a timeout. Errors
|
||||
report only the unavailable source, not provider output or secret values. Sources
|
||||
load independently; unavailable sources clear their old variables. Test never falls
|
||||
back to SOPS, and live never depends on Kubernetes. Native devenv's all-at-once secret
|
||||
resolution is intentionally not used: it would couple their failure behavior.
|
||||
|
||||
## Mounts
|
||||
|
||||
`company-obs.service` and `company-sharepoint.service` are user services on both
|
||||
workstations. Mounts live at `~/yookr/workbench/{obs,sharepoint}`. They are read-only,
|
||||
use `--vfs-cache-mode off`, and retry failures every minute. No background content
|
||||
mirror remains. The existing local SharePoint mirror is preserved, not deleted.
|
||||
Small memory buffers and filesystem metadata are still used.
|
||||
|
||||
OBS uses the company's encrypted `leafcloud` map and machine age key. SharePoint
|
||||
uses the existing native rclone remotes `company-sharepoint` and
|
||||
`company-sharepoint-projecten` in `~/.config/rclone/rclone.conf`; configure/login to
|
||||
these on each workstation with rclone. Tokens remain application-owned outside Git.
|
||||
Before first activation, unmount any old workbench-entry OBS mount. Do not empty or
|
||||
replace nonempty mount directories: move any authored content deliberately first.
|
||||
|
||||
```console
|
||||
systemctl --user restart company-obs company-sharepoint
|
||||
systemctl --user status company-obs company-sharepoint
|
||||
```
|
||||
|
||||
Do not invoke the old workspace `just mount-obs` helper once systemd owns the mount.
|
||||
The old helper/recipes can be removed after the new mounts are accepted in use.
|
||||
Shell exit does not unmount company files. All processes running as your user can
|
||||
read the mounts; these services do not isolate agents.
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
pkgs,
|
||||
garden ? import ../../yookr-dev/_support/packages.nix { inherit pkgs; },
|
||||
}:
|
||||
{
|
||||
packages = [
|
||||
garden.gardenctl
|
||||
garden.gardenlogin
|
||||
]
|
||||
++ (with pkgs; [
|
||||
k9s
|
||||
kubectl
|
||||
kubelogin-oidc
|
||||
jq
|
||||
just
|
||||
python3Packages.python-openstackclient
|
||||
python314
|
||||
rclone
|
||||
sops
|
||||
secretspec
|
||||
util-linux
|
||||
uv
|
||||
]);
|
||||
env = {
|
||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [
|
||||
pkgs.stdenv.cc.cc.lib
|
||||
pkgs.zlib
|
||||
];
|
||||
UV_PYTHON = "${pkgs.python314}/bin/python";
|
||||
UV_PYTHON_DOWNLOADS = "never";
|
||||
RABBITMQADMIN_TARGET_HOST = "test.rabbitmq.yookr.org";
|
||||
RABBITMQADMIN_TARGET_PORT = "443";
|
||||
RABBITMQADMIN_USE_TLS = "true";
|
||||
RABBITMQADMIN_NON_INTERACTIVE_MODE = "true";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
{ inputs, ... }:
|
||||
{
|
||||
flake.modules.homeManager.workbench =
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
home = config.home.homeDirectory;
|
||||
root = "${home}/yookr/workbench";
|
||||
companySecrets = "${home}/.config/nixos/modules/secrets/company.yaml";
|
||||
environmentInputs = builtins.toJSON {
|
||||
inputs = {
|
||||
nixpkgs.url = "path:${inputs.nixpkgs}";
|
||||
git-hooks.url = "path:${inputs.devenv.inputs.git-hooks}";
|
||||
};
|
||||
};
|
||||
secretsManifest = (pkgs.formats.toml { }).generate "workbench-secrets.toml" {
|
||||
project = {
|
||||
name = "workbench";
|
||||
revision = "1";
|
||||
};
|
||||
providers.company = "sops://${companySecrets}?age_key_file=/var/lib/sops/keys.txt";
|
||||
profiles =
|
||||
lib.mapAttrs
|
||||
(
|
||||
_: names:
|
||||
lib.genAttrs names (name: {
|
||||
description = name;
|
||||
ref.item = name;
|
||||
providers = [ "company" ];
|
||||
})
|
||||
)
|
||||
{
|
||||
company = [
|
||||
"email"
|
||||
"password"
|
||||
];
|
||||
live = [
|
||||
"YOOKR_MYSQL_LIVE_DATABASE_URL"
|
||||
"YOOKR_MONGODB_LIVE_DATABASE_URL"
|
||||
];
|
||||
};
|
||||
};
|
||||
mountObs = pkgs.writeShellApplication {
|
||||
name = "mount-company-obs";
|
||||
runtimeInputs = [
|
||||
pkgs.python3
|
||||
pkgs.rclone
|
||||
pkgs.sops
|
||||
];
|
||||
text = ''
|
||||
export PATH=/run/wrappers/bin:"$PATH"
|
||||
exec python3 ${./mount-obs.py} ${lib.escapeShellArg companySecrets} ${lib.escapeShellArg "${root}/obs"}
|
||||
'';
|
||||
};
|
||||
mountSharepoint = pkgs.writeShellApplication {
|
||||
name = "mount-company-sharepoint";
|
||||
runtimeInputs = [
|
||||
pkgs.rclone
|
||||
pkgs.coreutils
|
||||
];
|
||||
text = ''
|
||||
export PATH=/run/wrappers/bin:"$PATH"
|
||||
mkdir -p ${lib.escapeShellArg "${root}/sharepoint"}
|
||||
export RCLONE_CONFIG_SHAREPOINT_TYPE=combine
|
||||
export RCLONE_CONFIG_SHAREPOINT_UPSTREAMS='Algemeen=company-sharepoint: Projecten=company-sharepoint-projecten:'
|
||||
exec rclone mount sharepoint: ${lib.escapeShellArg "${root}/sharepoint"} \
|
||||
--config ${lib.escapeShellArg "${config.xdg.configHome}/rclone/rclone.conf"} \
|
||||
--read-only --vfs-cache-mode off --buffer-size 8M
|
||||
'';
|
||||
};
|
||||
mountService = name: command: {
|
||||
Unit = {
|
||||
Description = "Read-only company ${name} mount";
|
||||
After = [ "network-online.target" ];
|
||||
StartLimitIntervalSec = 0;
|
||||
};
|
||||
Service = {
|
||||
Type = "simple";
|
||||
ExecStart = lib.getExe command;
|
||||
ExecStop = "/run/wrappers/bin/fusermount3 -u ${root}/${name}";
|
||||
Environment = "SOPS_AGE_KEY_FILE=/var/lib/sops/keys.txt";
|
||||
UMask = "0077";
|
||||
Restart = "on-failure";
|
||||
RestartSec = 60;
|
||||
};
|
||||
Install.WantedBy = [ "default.target" ];
|
||||
};
|
||||
in
|
||||
{
|
||||
# devenv already installs its bundled SecretSpec CLI in the workstation profile.
|
||||
home.packages = lib.remove pkgs.secretspec (import ./_support/tools.nix { inherit pkgs; }).packages;
|
||||
programs.zsh.initContent = lib.mkAfter ''
|
||||
workbench-reload() {
|
||||
if [[ "''${DEVENV_ROOT:-}" != ${lib.escapeShellArg root} ]]; then
|
||||
print -u2 'Enter the workbench devenv shell before loading credentials.'
|
||||
return 1
|
||||
fi
|
||||
local exports
|
||||
exports="$(${pkgs.python3}/bin/python3 ${./load-secrets.py} ${secretsManifest} "$@")" || return
|
||||
eval "$exports"
|
||||
}
|
||||
'';
|
||||
|
||||
home.file."yookr/workbench/devenv.yaml".text = environmentInputs;
|
||||
home.file."yookr/workbench/devenv.nix".text = ''
|
||||
{ pkgs, ... }:
|
||||
let tools = import ${./_support/tools.nix} {
|
||||
inherit pkgs;
|
||||
garden = import ${../yookr-dev/_support/packages.nix} { inherit pkgs; };
|
||||
};
|
||||
in {
|
||||
inherit (tools) packages env;
|
||||
enterShell = ${lib.strings.escapeNixString ''
|
||||
if [ "''${WORKBENCH_TOOLS_ONLY:-0}" = 1 ]; then
|
||||
eval "$(python3 ${./load-secrets.py} ${secretsManifest} --tools-only)"
|
||||
else
|
||||
eval "$(python3 ${./load-secrets.py} ${secretsManifest})"
|
||||
fi
|
||||
''};
|
||||
}
|
||||
'';
|
||||
systemd.user.services = {
|
||||
company-obs = mountService "obs" mountObs;
|
||||
company-sharepoint = mountService "sharepoint" mountSharepoint;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
perSystem =
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
tools = import ./_support/tools.nix { inherit pkgs; };
|
||||
in
|
||||
{
|
||||
# A credential-free build target for the same tools used by native devenv.
|
||||
devShells.workbench = pkgs.mkShell (tools.env // { inherit (tools) packages; });
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
"""Emit shell exports for independent sources; never print provider errors or values to stderr."""
|
||||
import argparse
|
||||
import concurrent.futures
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('manifest', type=Path)
|
||||
parser.add_argument('--tools-only', action='store_true')
|
||||
args = parser.parse_args()
|
||||
groups = {
|
||||
'company': ['email', 'password'],
|
||||
'live': ['YOOKR_MYSQL_LIVE_DATABASE_URL', 'YOOKR_MONGODB_LIVE_DATABASE_URL'],
|
||||
'test': ['YOOKR_MYSQL_TEST_DATABASE_URL', 'YOOKR_MONGODB_TEST_DATABASE_URL',
|
||||
'RABBITMQADMIN_USERNAME', 'RABBITMQADMIN_PASSWORD'],
|
||||
}
|
||||
|
||||
def load(group):
|
||||
env = os.environ.copy()
|
||||
# Explicit manifests/profiles/providers must not inherit a user's override.
|
||||
for name in ('SECRETSPEC_PROVIDER', 'SECRETSPEC_PROFILE', 'SECRETSPEC_SCOPE'):
|
||||
env.pop(name, None)
|
||||
if group == 'test':
|
||||
# A dedicated, explicitly provisioned test kubeconfig is mandatory.
|
||||
kubeconfig = Path(os.environ.get('XDG_RUNTIME_DIR', '/nonexistent')) / 'workbench/test-kubeconfig'
|
||||
manifest = Path.home() / '.config/workbench/test-secrets.toml'
|
||||
if not kubeconfig.is_file() or not manifest.is_file():
|
||||
return group, None
|
||||
env['KUBECONFIG'] = str(kubeconfig)
|
||||
else:
|
||||
manifest = args.manifest
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['secretspec', '--file', str(manifest), 'export', '--profile', group, '--format', 'json'],
|
||||
env=env, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
|
||||
timeout=20, check=True, text=True)
|
||||
values = json.loads(result.stdout)
|
||||
if not all(isinstance(values.get(key), str) and values[key] for key in groups[group]):
|
||||
return group, None
|
||||
return group, {key: values[key] for key in groups[group]}
|
||||
except (OSError, ValueError, subprocess.SubprocessError):
|
||||
return group, None
|
||||
|
||||
# Clear inherited target values before attempting a reload, including on failure.
|
||||
for keys in groups.values():
|
||||
for key in keys:
|
||||
print('unset ' + key)
|
||||
if args.tools_only:
|
||||
for group in groups:
|
||||
print('export WORKBENCH_' + group.upper() + '_STATUS=disabled')
|
||||
else:
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
|
||||
for group, values in executor.map(load, groups):
|
||||
status = 'ready' if values is not None else 'unavailable'
|
||||
print('export WORKBENCH_' + group.upper() + '_STATUS=' + status)
|
||||
if values:
|
||||
for key, value in values.items():
|
||||
print('export ' + key + '=' + shlex.quote(value))
|
||||
print(f'workbench: {group} {status}', file=sys.stderr)
|
||||
print('Retry after provisioning/login: workbench-reload. Clean tools: workbench-reload --tools-only.', file=sys.stderr)
|
||||
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
"""User-run migration: decrypt in memory, reshape, encrypt for both machine keys."""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
from urllib.parse import quote
|
||||
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('source', type=Path)
|
||||
parser.add_argument('destination', type=Path)
|
||||
parser.add_argument('--age', required=True, help='Comma-separated public age recipients for both machines')
|
||||
args = parser.parse_args()
|
||||
if args.destination.exists():
|
||||
parser.error('Destination exists; refusing to overwrite it')
|
||||
|
||||
def url(scheme, database):
|
||||
authority = quote(database['username'], safe='') + ':' + quote(database['password'], safe='')
|
||||
authority += '@' + database['host']
|
||||
if database.get('port'):
|
||||
authority += ':' + str(database['port'])
|
||||
return scheme + '://' + authority
|
||||
|
||||
try:
|
||||
old = json.loads(subprocess.check_output(
|
||||
['sops', 'decrypt', '--output-type', 'json', str(args.source)], stderr=subprocess.DEVNULL))
|
||||
new = {
|
||||
'email': old['company_login']['email'],
|
||||
'password': old['company_login']['password'],
|
||||
'YOOKR_MYSQL_LIVE_DATABASE_URL': url('mariadb', old['databases']['mysql']['live']) + '?ssl-verify-server-cert=0',
|
||||
'YOOKR_MONGODB_LIVE_DATABASE_URL': url('mongodb+srv', old['databases']['mongodb']['live']),
|
||||
'leafcloud': old['leafcloud'],
|
||||
}
|
||||
encrypted = subprocess.check_output(
|
||||
['sops', 'encrypt', '--input-type', 'json', '--output-type', 'yaml',
|
||||
'--age', args.age, '/dev/stdin'], input=json.dumps(new).encode(), stderr=subprocess.DEVNULL)
|
||||
os.umask(0o077)
|
||||
with args.destination.open('xb') as output:
|
||||
output.write(encrypted)
|
||||
except (subprocess.CalledProcessError, KeyError, ValueError, OSError):
|
||||
raise SystemExit('Migration failed; no plaintext was written. Check source schema, keys and recipients.')
|
||||
print('Encrypted company document created. Original credentials were preserved.')
|
||||
@@ -0,0 +1,35 @@
|
||||
"""Foreground read-only OBS mount; systemd owns lifecycle and retries."""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
try:
|
||||
projects = json.loads(subprocess.check_output(
|
||||
['sops', 'decrypt', '--extract', '["leafcloud"]', '--output-type', 'json', sys.argv[1]],
|
||||
stderr=subprocess.DEVNULL))
|
||||
if not projects:
|
||||
raise ValueError('No projects')
|
||||
env = os.environ.copy()
|
||||
upstreams = []
|
||||
for index, (project, credentials) in enumerate(projects.items()):
|
||||
if not re.fullmatch(r'[A-Za-z0-9_-]+', project):
|
||||
raise ValueError('Invalid project name')
|
||||
remote = f'obs_project_{index}'
|
||||
options = dict(type='s3', provider='Other', region='europe-nl-ams1',
|
||||
endpoint='https://leafcloud.store', force_path_style='true',
|
||||
access_key_id=credentials['access_key_id'],
|
||||
secret_access_key=credentials['secret_access_key'])
|
||||
env.update({f'RCLONE_CONFIG_{remote.upper()}_{key.upper()}': str(value)
|
||||
for key, value in options.items()})
|
||||
upstreams.append(f'{project}={remote}:')
|
||||
env['RCLONE_CONFIG_OBS_TYPE'] = 'combine'
|
||||
env['RCLONE_CONFIG_OBS_UPSTREAMS'] = ' '.join(upstreams)
|
||||
mount = Path(sys.argv[2])
|
||||
mount.mkdir(parents=True, exist_ok=True)
|
||||
os.execvpe('rclone', ['rclone', 'mount', 'obs:', str(mount), '--config', '/dev/null',
|
||||
'--read-only', '--vfs-cache-mode', 'off', '--buffer-size', '8M'], env)
|
||||
except (subprocess.CalledProcessError, KeyError, ValueError, OSError):
|
||||
raise SystemExit('OBS mount unavailable: check company SOPS provisioning and the machine age key.')
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
flake.modules.nixos.yookr-dev = {
|
||||
security.pki.certificateFiles = [
|
||||
./yookr-dev/zenith-rootCA.pem
|
||||
./yookr-dev/polaris-rootCA.pem
|
||||
];
|
||||
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
5173
|
||||
5174
|
||||
];
|
||||
|
||||
networking.extraHosts = ''
|
||||
127.0.0.1 local.management.yookr.org
|
||||
127.0.0.1 local.dashboard.yookr.org
|
||||
'';
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{ pkgs }:
|
||||
{
|
||||
gardenctl = pkgs.stdenvNoCC.mkDerivation {
|
||||
pname = "gardenctl";
|
||||
version = "2.14.0";
|
||||
src = pkgs.fetchurl {
|
||||
url = "https://github.com/gardener/gardenctl-v2/releases/download/v2.14.0/gardenctl_v2_linux_amd64";
|
||||
hash = "sha256-JCZ0eJLgYbd2ZMCCWFlhX4vOhHKZL40oR2a3EZ3i4Q8=";
|
||||
};
|
||||
dontUnpack = true;
|
||||
installPhase = ''
|
||||
install -Dm755 "$src" "$out/bin/gardenctl"
|
||||
install -d "$out/share/zsh/site-functions"
|
||||
"$out/bin/gardenctl" completion zsh > "$out/share/zsh/site-functions/_gardenctl"
|
||||
'';
|
||||
};
|
||||
gardenlogin = pkgs.stdenvNoCC.mkDerivation {
|
||||
pname = "gardenlogin";
|
||||
version = "0.10.0";
|
||||
src = pkgs.fetchurl {
|
||||
url = "https://github.com/gardener/gardenlogin/releases/download/v0.10.0/gardenlogin_linux_amd64";
|
||||
hash = "sha256-X4nHiZ29Mbnhxv/wlyD6PxX1KxzaX2R27N7Muu/gnH8=";
|
||||
};
|
||||
dontUnpack = true;
|
||||
installPhase = ''
|
||||
install -Dm755 "$src" "$out/bin/gardenlogin"
|
||||
ln -s gardenlogin "$out/bin/kubectl-gardenlogin"
|
||||
'';
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
flake.modules.homeManager.yookr-tools =
|
||||
{ pkgs, ... }:
|
||||
let
|
||||
tools = import ./_support/packages.nix { inherit pkgs; };
|
||||
in
|
||||
{
|
||||
programs.zsh.initContent = "source <(gardenctl rc zsh --no-completion)";
|
||||
home.packages = [
|
||||
tools.gardenctl
|
||||
tools.gardenlogin
|
||||
];
|
||||
};
|
||||
|
||||
flake.modules.nixos.yookr-dev = {
|
||||
security.pki.certificateFiles = [
|
||||
./zenith-rootCA.pem
|
||||
./polaris-rootCA.pem
|
||||
];
|
||||
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
5173
|
||||
5174
|
||||
];
|
||||
|
||||
networking.extraHosts = ''
|
||||
127.0.0.1 local.management.yookr.org
|
||||
127.0.0.1 local.dashboard.yookr.org
|
||||
'';
|
||||
};
|
||||
}
|
||||
@@ -235,6 +235,14 @@ in
|
||||
scheme = "https";
|
||||
domain = "vault.jelles.net";
|
||||
};
|
||||
wardrowbe = {
|
||||
scheme = "https";
|
||||
domain = "wardrobe.jelles.net";
|
||||
};
|
||||
wardrowbe-auth = {
|
||||
scheme = "https";
|
||||
domain = "wardrobe-auth.jelles.net";
|
||||
};
|
||||
};
|
||||
|
||||
externalDevices.pixel-10.syncthingId = "MTJHEHA-UMZDQZ7-BTMRRLQ-Y7BFPUJ-ZTY6LZX-PDXV3IS-XVJCU7B-EPETBQZ";
|
||||
|
||||
@@ -10,7 +10,7 @@ in
|
||||
stateVersion = "24.05";
|
||||
};
|
||||
|
||||
flake.modules.nixos.orion = {
|
||||
flake.modules.nixos.orion = { pkgs, ... }: {
|
||||
imports = [
|
||||
nixosModules.host-base
|
||||
|
||||
@@ -24,11 +24,13 @@ in
|
||||
nixosModules.actual
|
||||
nixosModules.gitea
|
||||
nixosModules.transcription
|
||||
nixosModules.wardrowbe
|
||||
./_hardware.nix
|
||||
./_disk.nix
|
||||
];
|
||||
|
||||
environment.enableAllTerminfo = true;
|
||||
# SSH clients on both workstations use Kitty.
|
||||
environment.systemPackages = [ pkgs.kitty.terminfo ];
|
||||
|
||||
users.users.${config.facts.account.name}.linger = true;
|
||||
};
|
||||
|
||||
@@ -37,16 +37,13 @@ in
|
||||
flake.modules.nixos.zenith = {
|
||||
imports = [
|
||||
nixosModules.workstation-base
|
||||
nixosModules.openssh
|
||||
nixosModules.laptop-power
|
||||
nixosModules.sharepoint-reference
|
||||
./_hardware.nix
|
||||
inputs.nixos-hardware.nixosModules.lenovo-yoga-7-14ARH7-amdgpu
|
||||
];
|
||||
|
||||
services.fwupd.enable = true;
|
||||
services.teamviewer.enable = true;
|
||||
# Temporary Xorg session for remote support.
|
||||
# services.xserver.enable = true;
|
||||
# services.xserver.desktopManager.xfce.enable = true;
|
||||
services.openssh.openFirewall = false;
|
||||
};
|
||||
}
|
||||
@@ -13,6 +13,7 @@ in
|
||||
nixosModules.facts
|
||||
inputs.home-manager.nixosModules.home-manager
|
||||
nixosModules.nix
|
||||
nixosModules.tailscale
|
||||
nixosModules.locale
|
||||
nixosModules.sops
|
||||
];
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
nixosModules = config.flake.modules.nixos;
|
||||
homeModules = config.flake.modules.homeManager;
|
||||
in
|
||||
{
|
||||
# Provisional boundary: both workstations select the same company integration.
|
||||
flake.modules.nixos.work = {
|
||||
imports = [
|
||||
nixosModules.openvpn
|
||||
nixosModules.yookr-dev
|
||||
];
|
||||
home-manager.sharedModules = [ homeModules.work ];
|
||||
};
|
||||
|
||||
flake.modules.homeManager.work = {
|
||||
imports = [
|
||||
homeModules.yookr-tools
|
||||
homeModules.workbench
|
||||
homeModules.work-git
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -11,9 +11,9 @@ in
|
||||
nixosModules.adb
|
||||
nixosModules.audio
|
||||
nixosModules.bluetooth
|
||||
nixosModules.file-management
|
||||
nixosModules.flatpak
|
||||
nixosModules.fonts
|
||||
nixosModules.evolution
|
||||
nixosModules.network-manager
|
||||
nixosModules.nftables-firewall
|
||||
nixosModules.niri
|
||||
@@ -21,16 +21,15 @@ in
|
||||
nixosModules.notes
|
||||
nixosModules.plymouth
|
||||
nixosModules.printing
|
||||
nixosModules.sddm
|
||||
nixosModules.noctalia-greeter
|
||||
nixosModules.sops-admin-key-file
|
||||
nixosModules.limine
|
||||
nixosModules.localsend
|
||||
nixosModules.theme
|
||||
nixosModules.ai
|
||||
nixosModules.yunzi-b75-pro
|
||||
nixosModules.openvpn
|
||||
nixosModules.walker
|
||||
nixosModules.yookr-dev
|
||||
nixosModules.work
|
||||
];
|
||||
|
||||
programs.nix-ld.enable = true;
|
||||
@@ -44,6 +43,7 @@ in
|
||||
flake.modules.homeManager.workstation-base = {
|
||||
imports = [
|
||||
homeModules.passwords
|
||||
homeModules.audio
|
||||
homeModules.clipboard
|
||||
homeModules.handy
|
||||
homeModules.desktop-commands
|
||||
@@ -51,7 +51,7 @@ in
|
||||
homeModules.xdg
|
||||
homeModules.workstation-apps
|
||||
homeModules.vivaldi
|
||||
homeModules.nautilus
|
||||
homeModules.file-management
|
||||
homeModules.imv
|
||||
homeModules.sioyek
|
||||
homeModules.discord
|
||||
|
||||
@@ -5,6 +5,10 @@ orion-ip: ENC[AES256_GCM,data:S6fpCWnD8dvchvrHlEo=,iv:72+oRxHUEJ7imJ+sWjGbG+TUrS
|
||||
hashed-password: ENC[AES256_GCM,data:mhYvALF/VrPMFFYEIGiML1MoRxdOI/J9akZPD+OF5drDTR4J5LBn3EejlbzP8CTCRsyoIF6tigMBxF8ZLz9W7hOVwT70Hr/yXfLWJ2lfBsf7l/SPZ+6NglKppCeQQktm4QKIHTVj7CkbRw==,iv:0RxXkb+vz/QK9dvecCZpWCWCsoPhi2F3OCt6CwUDqX4=,tag:j7QO8xUfWUnSpk4mMQUYmA==,type:str]
|
||||
gemini-api-key-neovim: ENC[AES256_GCM,data:B8FeFt45FsU3aagyLDKXiwmx0mRrsw4C8RQ3EWXwZ+YfWLMvwJad,iv:1HqBD6vc07Ke/PMYXfHqFrWDGw/UMjiiBjLRN33/xHI=,tag:czcrYGbJFi41rYtIPM4qTQ==,type:str]
|
||||
booklore-db-pass: ENC[AES256_GCM,data:dlPGXQ24itEaBRJSJ9WOogWCdF3atFQ2ZtlLGyGq8Tin5OmSZI6lZUzSE+femBW5SBTIlKQvzHEPCs9MT5tyMIqetzGLm+mMN3FDW7si684Cuv9z9Uq5gjAZWh14KQMWYPI=,iv:oLnqu2EDFBVcBpswVRXXeF617YolPxOUx9CscHRRn/8=,tag:Si6gF1EXhcHalk11D3Exlw==,type:str]
|
||||
wardrowbe-auth-users: ENC[AES256_GCM,data:0padDqNkWA9p6GXaX3bpyH78tEeOndlX6/Gm0OzsfecnPkJmBJUQZr/328+51Q4eWPRflg4NRm04nejrqohKGcs=,iv:VJQD/ko4B18PbJh0gWdfQJ3xlGeHW4oBlho4e3Bkhk8=,tag:iE7gfUi1khmwIJA/3L8zvA==,type:str]
|
||||
wardrowbe-postgres-password: ENC[AES256_GCM,data:3t+py1nmtzbdFFMbBtQXQKttRj1MQrz+wcfjQGPYahOaQTIDu1Pzk3KJf+LANCTftpvrwO6N92K0b5q0+1MEzw==,iv:o9Hiew2vx6OinLBOKgPyx0K+WVTAvPopcEJoLtYCZTY=,tag:xi0wueITInYXwvVaLusqyw==,type:str]
|
||||
wardrowbe-secret-key: ENC[AES256_GCM,data:c9++BrtGsAFHML3gUG/E319jg9kxixH8RfZqs9pymxjYEFJsclaiWmknqeel4luIAuD5ufvDRzBLqxaBSE6rhQ==,iv:YntoeASLdE+lbU+15eIaR4SqWU3PzaWH8MJFlW1FwaM=,tag:aZhDTPc3qbNTZ8N9lvCJgA==,type:str]
|
||||
wardrowbe-nextauth-secret: ENC[AES256_GCM,data:3UZm0L+0q7C6OVYfCyIghzK80dPKOOz9BLfFCHctD2oMckPl0yfL5S1NqsXULycx+ctwWPV7SQj7/AiD6cyw1w==,iv:SocQDS2GbQ6ZWifmwb7AJC7SNkAMjJWetlTM9TfUC3Q=,tag:WuXajPY4e4zF5T6zC7okTQ==,type:str]
|
||||
sops:
|
||||
age:
|
||||
- enc: |
|
||||
@@ -25,7 +29,7 @@ sops:
|
||||
sWu/+CcQDpUu4tAeBSCUZ/BV2nTvfb94DZGK2dWUFniJaZit5tRZlg==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
recipient: age1upsxyevxdv3xax8jhnffclryuve70k63cjrca69ffds4h2m3nvuql59ufw
|
||||
lastmodified: "2026-08-08T14:56:00Z"
|
||||
mac: ENC[AES256_GCM,data:pXcvH73xNMSkOGytdhJ4Qu1pH8d3qS/FkPfGvbkzQoNLKRlLiArJExPYIkEtFdr7mDhYer91wPtkJu2HyyxCVPkPp8EFaeVdGuFufLQruQjP2KRfR841hohVW4KN53nh1AJT22CW2LTqgS0BnTBrd6wUQWvBHdek99eh8vwgurI=,iv:d8I+vKIgfirDvkHXVSNWJrKUO5vACkbk3VSN8TXSE+Q=,tag:itzAvtD+aBFmDilxZBepLw==,type:str]
|
||||
lastmodified: "2026-10-10T13:58:26Z"
|
||||
mac: ENC[AES256_GCM,data:S21qc5jLIoRrR28yiNmQHAU9soV74haGAbYXxsut0PqeRAMzUj4RSdBiR7Mja84r+w148Gx4GqTrPLhVxfj+78lyWcV3MWYbf9rr4tbxUYbhlJPRwVPdeP/JEc4rhPzX7QnUcrlym2zOhumJ/RoUHWW2yUEC/7DmzCqMXH1Kx5U=,iv:p7lwWl76cvELLhMlOdnxFdYdoIt/tVwxL/ihR/T3d7Y=,tag:DWsJYfaIZ2yZqXPktl9V1g==,type:str]
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.12.2
|
||||
version: 3.13.3
|
||||
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate current repository files without changing the Git index."""
|
||||
|
||||
from collections import deque
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def main():
|
||||
os.chdir(ROOT)
|
||||
run_dir = Path(tempfile.mkdtemp(prefix="nixos-validate-", dir="/tmp"))
|
||||
print(f"Logs: {run_dir}", flush=True)
|
||||
|
||||
def run(label, command):
|
||||
log = run_dir / f"{label}.log"
|
||||
print(f"{label} ...", flush=True)
|
||||
with log.open("wb") as output:
|
||||
result = subprocess.run(command, stdout=output, stderr=subprocess.STDOUT)
|
||||
if result.returncode:
|
||||
print(f"{label}: FAILED ({result.returncode}); {log}", flush=True)
|
||||
with log.open(errors="replace") as output:
|
||||
excerpt = "".join(deque(output, maxlen=40))
|
||||
print(excerpt[-12000:])
|
||||
raise SystemExit(result.returncode)
|
||||
print(f"{label}: OK", flush=True)
|
||||
|
||||
# Git supplies selection only: contents always come from the working tree.
|
||||
paths = subprocess.check_output(
|
||||
["git", "ls-files", "--cached", "--others", "--exclude-standard", "-z"]
|
||||
).split(b"\0")
|
||||
files = sorted({os.fsdecode(path) for path in paths if path})
|
||||
files = [name for name in files if os.path.lexists(ROOT / name)]
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="nixos-source-", dir="/tmp") as temp:
|
||||
source = Path(temp) / "source"
|
||||
|
||||
def snapshot():
|
||||
if source.exists():
|
||||
shutil.rmtree(source)
|
||||
source.mkdir()
|
||||
for name in files:
|
||||
original = ROOT / name
|
||||
destination = source / name
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
if original.is_symlink():
|
||||
destination.symlink_to(os.readlink(original))
|
||||
elif original.is_file():
|
||||
shutil.copy2(original, destination)
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported source entry: {name}")
|
||||
|
||||
snapshot()
|
||||
flake = f"path:{source}"
|
||||
nix = ["nix", "--quiet", "--accept-flake-config"]
|
||||
locked = ["--no-update-lock-file"]
|
||||
# The repository currently exposes only x86_64-linux outputs.
|
||||
nix_files = [str(ROOT / name) for name in files if name.endswith(".nix")]
|
||||
run("format", nix + ["run", *locked, f"{flake}#formatter.x86_64-linux", "--", *nix_files])
|
||||
snapshot()
|
||||
# Upstream devenv evaluates generated Nix (including cabal2nix). Realize
|
||||
# those evaluation dependencies before flake check enters read-only mode.
|
||||
run("prepare-devenv", nix + ["eval", *locked, "--raw", f"{flake}#packages.x86_64-linux.devenv.drvPath"])
|
||||
run("check", nix + ["flake", "check", "--no-build", *locked, flake])
|
||||
hosts_file = run_dir / "hosts.json"
|
||||
# Keep machine-readable output separate from Nix diagnostics.
|
||||
with hosts_file.open("wb") as output, (run_dir / "hosts.log").open("wb") as errors:
|
||||
result = subprocess.run(
|
||||
nix + ["eval", *locked, "--json", f"{flake}#nixosConfigurations", "--apply", "builtins.attrNames"],
|
||||
stdout=output, stderr=errors,
|
||||
)
|
||||
if result.returncode:
|
||||
print(f"Host discovery failed; see {run_dir / 'hosts.log'}")
|
||||
raise SystemExit(result.returncode)
|
||||
available = json.loads(hosts_file.read_text())
|
||||
hosts = list(dict.fromkeys(sys.argv[1:])) or available
|
||||
unknown = set(hosts) - set(available)
|
||||
if unknown:
|
||||
raise RuntimeError(f"Unknown hosts: {', '.join(sorted(unknown))}")
|
||||
for host in hosts:
|
||||
run(f"build-{host}", nix + ["build", *locked, "--no-link", f"{flake}#nixosConfigurations.{host}.config.system.build.toplevel"])
|
||||
if not sys.argv[1:]:
|
||||
run("build-workbench", nix + ["build", *locked, "--no-link", f"{flake}#devShells.x86_64-linux.workbench"])
|
||||
print(f"Validation passed: {', '.join(hosts)}. Logs: {run_dir}", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
sys.exit("Validation interrupted; completed stages do not establish a full pass.")
|
||||
except (OSError, ValueError, RuntimeError, subprocess.CalledProcessError) as error:
|
||||
sys.exit(str(error))
|
||||
Reference in new issue
Block a user