diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..c0aa80a --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,221 @@ +# Omnixient Architecture + +## Overview +Omnixient is built on a layered architecture that combines NixOS's declarative system configuration with modern desktop tools and development environments. + +## System Layers + +``` +┌─────────────────────────────────────────┐ +│ User Interface │ +│ Hyprland + Waybar + Applications │ +├─────────────────────────────────────────┤ +│ Desktop Environment │ +│ Theme System + Window Management │ +├─────────────────────────────────────────┤ +│ Omnixient Configuration │ +│ Modules + Scripts + Packages │ +├─────────────────────────────────────────┤ +│ Home Manager Layer │ +│ User Environment & Dotfiles │ +├─────────────────────────────────────────┤ +│ NixOS System │ +│ Package Management + Services │ +├─────────────────────────────────────────┤ +│ Linux Kernel │ +│ Hardware Abstraction │ +└─────────────────────────────────────────┘ +``` + +## Core Components + +### 1. Nix Flake System (`flake.nix`) +The foundation that defines all system inputs and outputs: +- **Inputs**: External dependencies (nixpkgs, home-manager, hyprland) +- **Outputs**: System configurations, packages, development shells, apps +- **Lock File**: Ensures reproducible builds across machines + +### 2. System Configuration (`configuration.nix`) +Main NixOS system configuration that: +- Imports all modules +- Defines system-wide settings +- Sets the current theme +- Configures hardware and services + +### 3. Module System (`modules/`) +Modular architecture with focused components: +- **Core**: Base system settings and Omnixient options (always on) +- **Themes**: Complete color schemes and application theming +- **Hardware**: Device-specific configurations +- **Desktop**: Window manager and GUI settings +- **Packs** (`modules/packs/`): opt-in bundles of packages/services/config + under `omni.packs..enable` — media, development, gaming, office, + containers, lnbits, aiolabs, bitcoin. Exposed as reusable + `flake.nixosModules.`; `lnbits`/`aiolabs`/`bitcoin` work on a + non-Omnixient NixOS. The legacy `preset`/`features` knobs drive packs via a + compat bridge. See [`packs.md`](packs.md). + +### 4. Home Manager (`home.nix`) +User environment management: +- User-specific packages +- Dotfile configuration +- Application settings +- Theme integration + +### 5. Package System (`packages/`) +Custom Nix packages: +- Omnixient utility scripts +- Specialized tools +- Theme packages + +### 6. Unix Tools (`scripts/`) +Focused utilities following Unix philosophy: +- System management +- Configuration backup +- User setup +- Build automation + +## Data Flow + +### System Build Process +``` +flake.nix → configuration.nix → modules/*.nix → system build + ↓ ↓ ↓ + inputs system opts module config + ↓ ↓ ↓ +nixpkgs theme selection packages/services +``` + +### Theme Application Flow +``` +Theme Selection → Module Configuration → Application Settings + ↓ ↓ ↓ + omni-theme modules/themes/ GTK/Qt/Terminal + .nix theming +``` + +### User Environment Flow +``` +home.nix → Home Manager → User Packages & Dotfiles + ↓ ↓ ↓ +user config evaluation ~/.config/* files +``` + +## Configuration Management + +### Declarative Configuration +- All system state defined in Nix expressions +- No imperative commands modify system configuration +- Changes require rebuild to take effect + +### Immutable System +- Built configurations are immutable +- Previous generations available for rollback +- Atomic upgrades prevent partial failures + +### Module Composition +- Features implemented as independent modules +- Modules can depend on other modules +- Options system provides configuration interface + +### Reproducible Builds +- Flake inputs pinned with lock file +- Same inputs produce identical outputs +- Cross-machine consistency guaranteed + +## Development Architecture + +### Language Support +Each language environment includes: +- Runtime and tools +- Language server protocols (LSPs) +- Package managers +- Development utilities + +### Shell Environments +``` +Development Shell: + nix develop .# + ↓ + Language-specific packages + ↓ + Configured environment +``` + +### Tool Integration +- Git with lazygit TUI +- Terminal with shell integration +- Editor with language support +- Build systems and debuggers + +## Theme Architecture + +### Unified Theming +All applications themed consistently: +- Terminal emulators (Alacritty, Kitty) +- Text editors (Neovim, VSCode) +- Desktop components (Waybar, Hyprland) +- GUI applications (GTK, Qt) + +### Color Management +``` +Theme Module → Color Variables → Application Configs + ↓ ↓ ↓ +tokyo-night.nix → #7aa2f7 (blue) → terminal.colors.blue +``` + +### Theme Switching +1. Update configuration.nix with new theme +2. Rebuild system to apply changes +3. All applications automatically use new theme + +## Hardware Support + +### Adaptive Configuration +- Automatic hardware detection +- GPU-specific optimizations (Intel, AMD, NVIDIA) +- Audio system configuration +- Network and Bluetooth setup + +### Conditional Modules +```nix +config = lib.mkIf cfg.hardware.nvidia.enable { + # NVIDIA-specific configuration +}; +``` + +## Security Architecture + +### System Security +- Secure boot support +- Firewall configuration +- AppArmor profiles +- User isolation + +### Authentication +- Multi-factor authentication support +- Fingerprint integration +- FIDO2 security keys +- Password management + +## Extensibility + +### Custom Modules +- Follow NixOS module structure +- Use options for configuration +- Implement proper dependencies +- Document all options + +### Package Development +- Custom packages in `packages/` +- Integration with flake outputs +- Proper meta information +- Cross-platform support + +### Theme Development +- Color palette definition +- Application configuration +- Testing across components +- Documentation and examples + +This architecture provides a solid foundation for a reproducible, customizable, and maintainable desktop Linux system. \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..ffe0290 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,25 @@ +# /etc/nixos/docs + +Index for the omni documentation. + +- **[`system-map.md`](system-map.md)** — *Start here if you're + returning to this stack or arriving fresh.* Cross-project map of + the aiolabs ecosystem: which repo does what, how the pieces fit + together, where keys live, deployment dependencies, and a + start-here runbook for common tasks. +- **[`ARCHITECTURE.md`](ARCHITECTURE.md)** — omni-the-desktop: + Hyprland, themes, the mksystem layer, flake structure. Scope is + this dev box, not the broader stack. +- **[`packs.md`](packs.md)** — the **core + opt-in packs** model: + `omni.packs..enable`, the pack catalog, enabling packs (on a + host, via presets/features, or standalone on another NixOS via + `nixosModules.`), the compat bridge, the bitcoin/nix-bitcoin + pack, and how to author + verify a new pack. +- **[`deploy-strategy.md`](deploy-strategy.md)** — fleet caching, + GC policy, dry-run workflows, Nix-vs-OCI comparison. Relevant when + pushing changes to fleet hosts. +- **[`mcp.md`](mcp.md)** — Claude Code MCP server configuration on + this machine. + +For the workspace-level conventions that cross-cut every repo in +`~/dev/`, see `~/dev/CLAUDE.md`. diff --git a/docs/deploy-strategy.md b/docs/deploy-strategy.md new file mode 100644 index 0000000..80442bd --- /dev/null +++ b/docs/deploy-strategy.md @@ -0,0 +1,137 @@ +# Deploy Strategy: Nix at Scale + +Operational patterns for deploying NixOS to the aiolabs fleet (host1, host2, host3, host5, etc.). +Many of these patterns come from Anthropic's production Nix infrastructure — see +[Anish Athalye's NixCon 2025 talk](https://www.youtube.com/watch?v=iPoL03tFBtU) for the full story. + +## Why Nix beats OCI images for server deploys + +OCI (Docker) images have structural limits that don't apply to Nix: + +| Constraint | OCI images | Nix closures | +|---|---|---| +| Granularity | Max 128 layers | Thousands of store paths | +| Fetching | Layers downloaded in parallel, extracted serially | Store paths fetched and extracted in parallel | +| Cache invalidation | Change one layer → rebuild everything below it | Only changed derivations + their dependents rebuild | +| Dependency tracking | Manual (`apt-get install` pulls the universe) | Automatic (build-time vs. runtime deps separated) | +| Reproducibility | GPG keys expire, mirrors vanish, timestamps drift | Content-addressed, hash-locked | + +Our fleet runs `nixos-rebuild switch` against flake-locked closures. +No container images are built or shipped for NixOS hosts — the Nix store *is* the deployment artifact. + +## Caching architecture + +### Current setup + +``` +CI / dev machine + │ nix build + cachix push + ▼ +┌──────────────┐ +│ cachix.org │ (aiolabs-nix) +│ (shared) │ +└──────┬───────┘ + │ nix-store --realise (substituters) + ▼ +┌──────────────┐ +│ Target host │ /nix/store (local) +│ host1, etc. │ +└──────────────┘ +``` + +### How it works + +1. **Build** — `make build HOST=` builds the system closure locally or in CI. +2. **Push** — `make cache HOST=` pushes the closure to cachix. + Second and third hosts deploying the same closure pull from cache instead of rebuilding. +3. **Deploy** — `nixos-rebuild switch --flake .#` on target. + Nix fetches only the store paths not already present locally. + +### Scaling up (future) + +If hosts span regions or have slow links to cachix, add a caching proxy closer to the fleet: + +``` +cachix.org ──► nginx reverse-proxy + disk cache (per-region) ──► target hosts +``` + +Anthropic saw a **4x improvement** (70s → 15s for 10 GB) going from bare S3 to a regional caching layer. + +## Preview before you deploy + +Before deploying to multiple hosts, check what will actually be built vs. fetched: + +```bash +make diff HOST=host1 +``` + +This runs `nix build ... --dry-run` and shows: +- **"will be fetched"** — already in cache, just needs downloading +- **"will be built"** — not cached, will compile locally + +Use this to decide whether to push to cache first, or whether a deploy will be fast (all cached) or slow (root dependency changed). + +## Garbage collection policy + +### The principle: don't throw away what you downloaded + +Every store path that survives between deploys is a store path you don't re-download next time. +Keeping old generations also enables instant rollback. + +### Recommended approach + +| Environment | Command | Retention | +|---|---|---| +| **Production servers** | `make gc` | Keep paths newer than 14 days | +| **Dev machines** | `make clean` | Delete all old generations | + +```makefile +# Production: conservative GC +gc: + sudo nix-collect-garbage --delete-older-than $(GC_KEEP) + sudo nix-store --optimise + +# Dev: aggressive GC +clean: + nix-collect-garbage -d + sudo nix-collect-garbage -d +``` + +### Why this matters + +- A host with 2 cached generations shares most store paths with the next deploy. + The diff is typically just a handful of changed derivations. +- A host that was aggressively GC'd may need to re-download gigabytes of unchanged dependencies. +- Rollback (`nixos-rebuild switch --rollback`) only works if the previous generation's store paths still exist. + +## Specificity: lock what ships + +Anthropic stressed that production workloads should only see paths they explicitly declared. +Our equivalent: + +- **`flake.lock`** pins every input. No floating refs in production. +- **`server-deploy`** is the source of truth for which refs ship to which host. + omni consumes it as a flake input. +- **No ad-hoc `nix-env -i` on servers.** Everything goes through the flake. + If a tool is needed on a host, add it to the host's NixOS config. + +## Build evaluation in CI (future) + +Anthropic evaluates PRs to determine what needs building before spinning up build jobs: + +```bash +# Show which derivations changed between current system and new config +nix build .#nixosConfigurations.$HOST.config.system.build.toplevel --dry-run 2>&1 \ + | grep 'will be built' +``` + +This can gate CI: if only leaf derivations changed, build is fast. +If a root dependency (nixpkgs, python, CUDA) changed, allocate more time/resources. + +## Key takeaways + +1. **Nix closures are the deployment artifact** — no OCI images needed for NixOS hosts. +2. **Cache aggressively, GC conservatively** on production servers. +3. **Preview before deploying** with `--dry-run` to avoid surprises. +4. **Lock everything** through flake inputs — no imperative state on servers. +5. **Regional cache proxies** are the next scaling lever when the fleet grows. diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..7eabcbe --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,201 @@ +# Getting Started — adopting Omnixient on your machine + +Omnixient is a personal NixOS config published as a starting point, not a +turnkey distro installer. Adopting it means **forking it and making it +yours**. Almost everything you need to change is funnelled through one +file (`settings.nix`) plus your machine's hardware scan. + +This guide assumes you already have a working NixOS install (or are +installing from the official NixOS ISO). If you don't know NixOS at all, +read [the NixOS manual](https://nixos.org/manual/nixos/stable/) and +[NixOS & Flakes Book](https://nixos-and-flakes.thiscute.world/) first. + +> **Heads-up:** the `omni` host (`hosts/omni/`) is the maintainer's +> actual machine — a Framework Desktop with WireGuard, the aiolabs dev +> environment, and a Bitcoin/Lightning workflow. You'll want your own +> host directory rather than inheriting all of that. See +> [Step 4](#4-make-it-your-host). + +--- + +## The two things that are truly yours + +1. **`settings.nix`** — identity, hostname, timezone, theme, SSH keys, and + the all-important `stateVersion`. One file, fully commented. +2. **`hosts//hardware-configuration.nix`** — the scan of your + actual hardware. Generated by `nixos-generate-config`; never copy + someone else's. + +Everything else (the desktop, theming, packs) is shared and opt-in. + +--- + +## 1. Get the repo + +```bash +git clone https://git.atitlan.io/aiolabs/omnixient.git ~/omni +cd ~/omni +``` + +You can put it anywhere; `/etc/nixos` and `~/omni` both work. Rebuild +commands below use `--flake .#` from inside the clone. + +## 2. Generate your hardware scan + +```bash +sudo nixos-generate-config --show-hardware-config > /tmp/hardware-configuration.nix +``` + +Review it (it captures your disks, filesystems, kernel modules, CPU +microcode). You'll place it into your host directory in step 4. + +> A stray `hardware-configuration.nix` at the repo root is gitignored on +> purpose — the tracked copy belongs in your host directory. + +## 3. Fill in `settings.nix` + +Open `settings.nix` and set every field for your machine: + +```nix +{ + user = "alice"; # your Linux username + gitName = "Alice Example"; + gitEmail = "alice@example.com"; + hostName = "nimbus"; # your machine's hostname + timeZone = "America/New_York"; + theme = "tokyo-night"; # any of modules/themes/*.nix + sshKeys = [ + "ssh-ed25519 AAAAC3Nza... alice@nimbus" + ]; + stateVersion = "24.05"; # ⚠ the release you FIRST installed — never change later +} +``` + +The comments in the file explain each field. The **`stateVersion`** +warning is the one that bites people: it is *not* your current NixOS +version — it pins stateful defaults from your original install, and +changing it on a live system can break data. + +## 4. Make it your host + +The cleanest path is your own host directory, and there's a ready-made +template to copy — **`hosts/example/`** (a minimal, generic host, kept +build-checked in `flake.nix` so it never rots): + +```bash +cp -r hosts/example hosts/nimbus # name it your host +cp /tmp/hardware-configuration.nix \ + hosts/nimbus/hardware-configuration.nix # your real scan (step 2) +``` + +`hosts/example/default.nix` imports your hardware scan + the shared Omnixient +base and enables a sensible starter set of packs — edit it to taste (it's +fully commented). Then register the host in `flake.nix` (copy the +`example` block, rename it): + +```nix +# flake.nix → nixosConfigurations +nimbus = mkSystem "nimbus" { + inherit system; + user = settings.user; # add `devEnv = true;` if you want the dev-env + extraSpecialArgs = { inherit settings; }; + extraHmArgs = { inherit settings; }; + modules = [ + { home-manager.sharedModules = [ + inputs.nix-colors.homeManagerModules.default + inputs.lazyvim.homeManagerModules.default + inputs.walker.homeManagerModules.default + ]; + } + ]; +}; +``` + +If your username differs from the template's, also create +`users//home-manager.nix` — copy `users/user/home-manager.nix`, +a one-line shim to the shared `home.nix`. + +**What's maintainer-specific in `hosts/omni/` — leave it out of yours:** +the `framework-desktop-*` hardware module, `wireguard.nix` (a private +tunnel), `omni.packs.aiolabs.enable` and the whole `dev-env { … }` block +(the aiolabs Lightning workflow), and the Framework `fwupd`/polkit rule. + +> Prefer the absolute-minimum change? You *can* skip renaming: replace +> `hosts/omni/hardware-configuration.nix` with your scan, strip the +> maintainer-specific lines from `hosts/omni/default.nix`, set +> `settings.hostName`, and build `.#omni`. The flake attribute name and +> your machine's hostname are independent. A dedicated host dir is just +> tidier once you have more than one machine. + +## 5. Choose your packs + +Core (base system + Hyprland desktop + theming) is always on. Everything +else is opt-in via `omni.packs..enable`. The full catalog and the +standalone-import story are in **[docs/packs.md](packs.md)**. + +```nix +omni.packs.media.enable = true; # gimp, inkscape, mpv, obs, … +omni.packs.development.enable = true; # editors, LSPs, compilers, container CLIs +omni.packs.gaming.enable = true; # Steam, Lutris, Wine +# omni.packs.bitcoin.enable = true; # a real bitcoind + lightning node (opt-in import) +``` + +## 6. Build and switch + +```bash +sudo nixos-rebuild switch --flake .# +``` + +> **Flakes not enabled yet?** If your existing NixOS install hasn't +> turned on flakes, this first command fails with something like +> `experimental Nix feature 'nix-command' is disabled` — flakes are +> opt-in on stock NixOS. Bootstrap the first build with a one-shot flag: +> +> ```bash +> sudo nixos-rebuild switch --flake .# \ +> --extra-experimental-features "nix-command flakes" +> ``` +> +> You only need this **once** — Omnixient's own config enables +> `nix-command` and `flakes` (`configuration.nix`), so every later +> `nixos-rebuild` / `omni-rebuild` works without the flag. +> (Alternatively, add +> `nix.settings.experimental-features = [ "nix-command" "flakes" ];` +> to your *current* `/etc/nixos/configuration.nix`, run +> `sudo nixos-rebuild switch`, then come back to this step.) + +On the next boot you'll have the Omnixient desktop. After that, the bundled +helpers take over (`omni-rebuild`, `omni-update`, `omni-theme`, +`omni-help` lists them all — note they're individual `omni-*` +commands, not a single dispatcher). + +## 7. Set your password + +The template ships `initialPassword = "omni"` (`modules/users.nix`). +Change it immediately after first login: + +```bash +passwd +``` + +For a permanent declarative password, switch to +`hashedPasswordFile` with a sops-managed secret (see +`modules/secrets.nix` and `.sops.yaml`). + +--- + +## Building a custom ISO (optional) + +```bash +nix build .#omni-iso # → result/iso/nixos-*.iso +``` + +The ISO is a live Omnixient environment for installing onto new hardware. + +## Where to go next + +- **[docs/packs.md](packs.md)** — the pack catalog, enabling/disabling, and + reusing packs on a non-Omnixient NixOS. +- **[docs/ARCHITECTURE.md](ARCHITECTURE.md)** — how the flake, `mksystem`, + modules, and theming fit together. +- **[docs/system-map.md](system-map.md)** — a file-by-file map of the repo. diff --git a/docs/mcp.md b/docs/mcp.md new file mode 100644 index 0000000..3902f09 --- /dev/null +++ b/docs/mcp.md @@ -0,0 +1,105 @@ +# Claude Code MCP Configuration + +Omnixient manages Claude Code MCP (Model Context Protocol) servers declaratively +via `modules/mcp.nix`. This doc covers setup, secrets, and the safety defaults. + +## How it works + +1. `modules/mcp.nix` renders a `mcpServers` attrset to + `~/.config/omni/mcp-servers.json` on each `nixos-rebuild switch`. +2. A home-manager activation step then merges `.mcpServers` from that file + into `~/.claude.json` via `jq`, preserving Claude Code's other state + (project history, OAuth tokens). +3. Secrets live in `~/.config/omni/secrets/` (mode 0700) and are loaded by + thin wrapper scripts at MCP launch time — never baked into the nix store + or exported to the shell. + +## Enabling / disabling servers + +Edit `configuration.nix`: + +```nix +omni.mcp = { + enable = true; + + servers = { + # Defaults on: + mcp-nixos.enable = true; # nixpkgs / NixOS search + github.enable = true; # HTTP + OAuth + forgejo.enable = true; # needs ~/.config/omni/secrets/forgejo-token + fetch.enable = true; # generic HTTP fetch + + # Defaults off — opt-in per host: + postgres.enable = false; + docker.enable = false; + nostr.enable = false; + shadcn-vue.enable = false; + }; +}; +``` + +## Required secret files + +Create any of these **outside the nix store** with mode 0600. The secrets +directory is auto-created at 0700 on the first rebuild. + +### Forgejo + +```bash +install -m600 /dev/stdin ~/.config/omni/secrets/forgejo-token <<< 'YOUR_FORGEJO_PAT' +``` + +Issue a PAT at `https://git.atitlan.io/user/settings/applications`. + +### GitHub + +No secret file needed — uses OAuth via `/mcp` inside Claude Code. On first use +run `/mcp` in a Claude session and authenticate in the browser. + +## Safety defaults + +- **No filesystem MCP**. Claude Code's built-in Read/Edit/Write tools already + cover filesystem work with per-action approval; adding a filesystem MCP + widens blast radius without adding capability. +- **Postgres is restricted by default**. `--access-mode=restricted` blocks + destructive SQL. To run migrations, flip `postgres.writable = true` for + that session only: + + ```nix + omni.mcp.servers.postgres.writable = true; + ``` + + Rebuild, do the migration, revert. Or better: pipe `psql` yourself. + +## Adding a new server + +1. Add a new `servers.` option block in `modules/mcp.nix`. +2. Append a corresponding `optionalAttrs cfg.servers..enable { ... }` + clause to `mcpServers`. +3. If it needs a secret, write a wrapper in the pattern of `forgejoMcpWrapper` + that sources the token file and execs the real binary. + +## Future work + +- Package `forgejo-mcp` as a `buildGoModule` derivation instead of reading + from `~/go/bin/` (imperative state leftover from `go install`). +- Replace plain secret files with agenix or sops-nix once `forgejo-mcp` is + validated end-to-end. +- Add a `permissions.deny` list in `~/.claude/settings.json` for tool names + that should be hard-blocked regardless of approval prompts. + +## Debugging + +```bash +# Inspect the rendered JSON +cat ~/.config/omni/mcp-servers.json + +# See the merged result +jq '.mcpServers' ~/.claude.json + +# List from Claude Code's side +claude mcp list +``` + +Inside a Claude Code session, `/mcp` shows live server status and handles +OAuth for HTTP servers. diff --git a/docs/moonlander.md b/docs/moonlander.md new file mode 100644 index 0000000..8ec12b3 --- /dev/null +++ b/docs/moonlander.md @@ -0,0 +1,54 @@ +# Flashing the ZSA Moonlander + +How to flash firmware to the ZSA Moonlander Mark I on bohm. + +## What's wired into the config + +Two pieces, both committed: + +- `configuration.nix` → `hardware.keyboard.zsa.enable = true;` — installs + ZSA's udev rules. Required so the keyboard is reachable for flashing and + for Oryx in-browser live-training **without root**. +- `home.nix` → `keymapp` in `home.packages` — ZSA's official flashing GUI. + +After editing those you must `omni-rebuild switch` (a system switch, not +just home-manager — the udev rules live at the NixOS layer). + +> **Gotcha:** after the rebuild that first enables the udev rules, **unplug +> and replug** the keyboard so the new rules bind to the device. Otherwise +> Keymapp won't see it. + +## Flashing flow (normal path) + +The Moonlander runs **QMK**. You don't compile anything locally for the +normal path — ZSA's Oryx configurator produces the firmware. + +1. Build/edit your layout in **Oryx** (https://configure.zsa.io) and + download the firmware `.bin`. +2. Launch `keymapp`. +3. Press the small **reset button** on the keyboard (top-left, reachable + with the included tool or a paperclip). +4. Flash the `.bin` from Keymapp. + +## CLI alternative + +`wally-cli` (nixpkgs attribute `wally-cli`, **not** `wally` — that's a +Roblox package manager) flashes from the terminal: + +```bash +nix-shell -p wally-cli --run 'wally-cli firmware.bin' +``` + +Same reset-button step applies. Add `wally-cli` to `home.packages` if you +want it permanently. + +## Troubleshooting + +- **Keyboard not detected** — confirm the rebuild applied + (`hardware.keyboard.zsa.enable`), then unplug/replug. Check the device + shows up: `lsusb | grep -i zsa` (or look for `3297:` vendor id). +- **Permission denied on flash** — udev rules aren't active for this + plug-in; replug, or `sudo udevadm control --reload && sudo udevadm + trigger`. +- **Reset button does nothing** — it's recessed; use the keycap-puller + tool ZSA ships, or a straightened paperclip. diff --git a/docs/packs.md b/docs/packs.md new file mode 100644 index 0000000..6d3fdbd --- /dev/null +++ b/docs/packs.md @@ -0,0 +1,249 @@ +# Packs — the core + opt-in goodies model + +Omnixient is structured as a **core** that every host gets, plus **opt-in +packs** you switch on for the goodies you want: + +```nix +omni.packs.media.enable = true; # gimp, inkscape, mpv, obs, … +omni.packs.development.enable = true; # editors, LSPs, compilers, container CLIs +omni.packs.bitcoin.enable = true; # a real bitcoind + lightning node +``` + +The same pack definitions are exposed as reusable **`flake.nixosModules`**, +so a *second* Omnixient host (or someone else's NixOS entirely) can consume +them with a different subset — install the core, add the goodies you want. + +> **TL;DR** +> - Core = base system + Hyprland desktop + theming (always on). +> - Packs = opt-in bundles of packages/services/config under +> `omni.packs..enable`. +> - `lnbits` / `aiolabs` / `bitcoin` are **standalone-importable** — usable +> on a non-Omnixient NixOS via `inputs.omni.nixosModules.`. +> - The legacy `omni.preset` / `omni.features.*` knobs still work — +> they're now a friendly front-end that drives packs (see +> [Compatibility bridge](#compatibility-bridge)). + +## What's core vs. what's a pack + +| Layer | Contents | Toggle | +|---|---|---| +| **Core** (always) | base packages + fonts, Hyprland desktop, theming/colors, walker/menus, boot, security, services, hardware, users, secrets, `nh`, MCP | — | +| **Packs** (opt-in) | see catalog below | `omni.packs..enable` | + +Theming and the Hyprland desktop are deliberately **core** (Omnixient is a +desktop distro; there's no headless target yet). If a headless/server +target is ever needed, the desktop becomes its own pack — see +[Authoring a pack](#authoring-a-new-pack). + +## Pack catalog + +| Pack | Provides | Standalone-importable? | +|---|---|---| +| `media` | players (mpv/vlc), image/video editing (gimp, inkscape, krita, kdenlive), screen capture, PDF, `morph-py`, openscad | no (desktop bundle) | +| `development` | editors/IDEs, LSPs, debuggers, build tools, compilers, container CLIs, DB clients, cloud tooling, `git`/`npm`, dev manpages, and the richer `modules/development.nix` dev module | no | +| `gaming` | Steam (+ firewall), Lutris, Wine, performance tools | no | +| `office` | browsers, comms (Signal/Element/Ferdium), office suites, notes, password managers, sync/backup | no | +| `containers` | Docker + Podman (unified daemon config) | no | +| `lnbits` | the LNbits multi-project dev environment (`modules/dev-env`: worktrees, regtest, tmux, pre-commit hook) | **yes** | +| `aiolabs` | the aiolabs project list + deploy targets + tmux sessions, on top of `lnbits` | **yes** | +| `bitcoin` | a real Bitcoin/Lightning node via nix-bitcoin (bitcoind + clightning) | **yes** (opt-in import) | + +Each pack's `enable` carries a one-line description — discover them with: + +```sh +nix eval .#nixosConfigurations.omni.options.omni.packs --apply builtins.attrNames +man configuration.nix # then search /omni.packs +``` + +## Enabling packs + +### On an Omnixient host + +Set them in your host config (`hosts//default.nix`) or +`configuration.nix`: + +```nix +omni.packs = { + development.enable = true; + containers.enable = true; + media.enable = true; +}; +``` + +`gizmo` (a laptop) and `omni` (the desktop) can enable different +subsets from the same definitions — that's the point. + +### Via presets / features (the friendly front-end) + +The legacy knobs still work and now drive packs through the +[compatibility bridge](#compatibility-bridge): + +```nix +omni.preset = "developer"; # → development, containers, media, office packs + theming +omni.features.media = true; # → omni.packs.media.enable +``` + +Use whichever you prefer; explicit `omni.packs.*.enable` always wins +over a preset/feature default. + +### Standalone — on a non-Omnixient NixOS (the reusable path) + +`lnbits`, `aiolabs`, and `bitcoin` don't depend on Omnixient core. Add this +flake as an input and import just the pack you want: + +```nix +# your own flake.nix +inputs.omni.url = "git+ssh://forgejo@git.atitlan.io/aiolabs/omnixient"; + +# your host's modules +{ + imports = [ inputs.omni.nixosModules.lnbits ]; + omni.packs.lnbits.enable = true; + dev-env.user = "alice"; # configure via the standard dev-env.* options + dev-env.root = "/home/alice/dev"; +} +``` + +You get the full LNbits dev environment without Hyprland, themes, or any +Omnixient host config. This is what makes the separate `lnbits-sensei` +scaffold obsolete — the dev-env is now a clean importable module. + +> `nixosModules.bitcoin` additionally needs `inputs.nix-bitcoin` available +> in your `specialArgs` (Omnixient's own hosts get it via `lib/mksystem.nix`). +> See [The bitcoin pack](#the-bitcoin-pack). + +## Compatibility bridge + +`modules/packs/compat.nix` maps the legacy `omni.features.*` booleans +onto the new pack toggles with `mkDefault`: + +```nix +omni.packs.media.enable = mkDefault (cfg.features.media or false); +omni.packs.development.enable = mkDefault (cfg.features.coding or false); +omni.packs.containers.enable = mkDefault (cfg.features.containers or false); +omni.packs.gaming.enable = mkDefault (cfg.features.gaming or false); +omni.packs.office.enable = mkDefault ((cfg.features.office or false) || (cfg.features.communication or false)); +``` + +So the chain is **preset → features → packs**. `mkDefault` means an +explicit `omni.packs..enable = true|false` on a host always wins. +The bridge lives in the aggregator (loaded only with full Omnixient), so a +standalone consumer importing a single pack never pulls in the +`omni.features` dependency. + +Non-pack features (`customThemes`, `wallpaperEffects`, `virtualization`, +`backup`) remain plain `omni.features.*` — they're core knobs, not +packs. + +## The bitcoin pack + +`omni.packs.bitcoin` runs a real node via +[nix-bitcoin](https://github.com/fort-nix/nix-bitcoin): + +```nix +{ + imports = [ inputs.omni.nixosModules.bitcoin ]; # or hosts/ imports modules/packs/bitcoin.nix + omni.packs.bitcoin.enable = true; + omni.packs.bitcoin.operatorName = "padreug"; # account granted node CLI access +} +``` + +It imports only `nix-bitcoin.nixosModules.default` (NOT the +`secure-node`/`hardened` presets, which are server-oriented and hostile to +a daily-driver desktop), enables `bitcoind` + `clightning`, sets +`nix-bitcoin.generateSecrets = true` (auto-generates node secrets at +activation), and grants the operator CLI access. + +**Why it's not in the always-loaded aggregator:** nix-bitcoin's default +module fires a secrets assertion merely from being imported — even with no +services enabled. Importing it inert on every host would break eval. So +`bitcoin` is **not** in `modules/packs/default.nix`; it's exposed as +`nixosModules.bitcoin` and a host opts in by importing it explicitly. +(`inputs.nix-bitcoin` is supplied to Omnixient hosts via `lib/mksystem.nix`'s +`specialArgs`.) + +**Caveats** (see also `docs/system-map.md`): +- `generateSecrets` is the simplest path; for unified secret management, + wire sops-nix into nix-bitcoin's `secretsDir` (`secretsSetupMethod = + "manual"`) — note LND has an open issue under manual secrets. +- nix-bitcoin follows our `nixpkgs`; it pins its own upstream for *tested* + builds, so if a service fails to build after enabling, consider dropping + the `follows` in `flake.nix`. + +## Authoring a new pack + +1. Create `modules/packs/.nix` using the canonical template: + + ```nix + { config, lib, pkgs, ... }: + let cfg = config.omni.packs.; in { + options.omni.packs..enable = + lib.mkEnableOption ""; + + config = lib.mkIf cfg.enable { + # ONLY standard NixOS options + this pack's own bundled modules. + environment.systemPackages = with pkgs; [ … ]; + }; + } + ``` + +2. **Register it** in `modules/packs/default.nix` (the aggregator) so the + option namespace always exists and the body is inert until enabled. + *Exception:* if the pack imports a module with import-time side effects + (assertions, global `disabledModules` — like nix-bitcoin), do **not** + add it to the aggregator; expose it only as a `nixosModules` output for + explicit per-host import. + +3. **Expose it** as a flake output in `flake.nix`: + `nixosModules. = import ./modules/packs/.nix;`. + +4. If it should be **standalone-importable**, the module must declare its + own option namespace and touch only standard NixOS options + its own + bundled modules — **never read `config.omni.`**. (This is why + `dev-env` was decoupled from `omni.*`; the `lnbits` pack inherits + that.) + +5. *(Optional)* add a bridge line in `modules/packs/compat.nix` if a + legacy `omni.features.*` flag should imply the new pack. + +6. Sub-options live alongside `enable`, e.g. + `omni.packs.bitcoin.operatorName`. + +## Internals & wiring + +- **Aggregator:** `modules/packs/default.nix` imports every (inert) pack + + `compat.nix`. +- **Wired in once:** `lib/mksystem.nix` imports `../modules/packs`, so all + hosts built through `mkSystem` get the pack option namespace. +- **ISO stays pack-free:** the live ISO (`iso.nix`) is a direct + `nixosSystem` call that bypasses `mksystem`, so it never loads packs. Any + core/ISO-shared module that references `config.omni.packs.*` must guard + with `or false`. +- **Reusable outputs:** `nixosModules.{media,development,gaming,office, + containers,lnbits,aiolabs,bitcoin}` in `flake.nix`. +- **dev-env relationship:** the `lnbits` pack `imports = [ ../dev-env ]` + and enables it; configure via the standard `dev-env.*` options (see + `modules/dev-env/README.md`). + +## Verifying a pack change + +Pack edits should be **closure-neutral** unless you intend a behavior +change. Work in a worktree, build-only (never `switch`), and diff against +a baseline: + +```sh +# baseline (once) +nix build .#nixosConfigurations.omni.config.system.build.toplevel -o ~/omni-baseline + +# after your change +nix build .#nixosConfigurations.omni.config.system.build.toplevel -o ~/omni-after +nix store diff-closures ~/omni-baseline ~/omni-after # empty = behavior-identical + +nix flake check --keep-going # schema + dev-env smoke + VM boot +``` + +For risky work on this live daily-driver, run Claude under +`scripts/sandbox-claude.sh ` — it hard-denies `nixos-rebuild +switch|test|boot`, `omni-rebuild`, sudo, push, and network, leaving +build/eval/in-worktree-git allowed. You apply the real `switch` only once +the gates are green. diff --git a/docs/regtest.md b/docs/regtest.md new file mode 100644 index 0000000..1b12c23 --- /dev/null +++ b/docs/regtest.md @@ -0,0 +1,101 @@ +# Lightning regtest (NixOS-native) + +A reproducible, hermetic Lightning regtest stack — `bitcoind(regtest)` + +Core Lightning + LND in one VM — replacing the core of the docker +`legend-regtest-enviroment` (aiolabs/omnixient#27). No docker, no host state; +the whole thing is a NixOS VM built from the bitcoin pack's nix-bitcoin +modules. + +There are **two ways to use it**, sharing one node definition +(`tests/regtest-node.nix`) so they can't drift: + +| Mode | Command | For | +|---|---|---| +| **CI test** | `nix build .#checks.x86_64-linux.regtest-core -L` | automated pass/fail (fund → channel → pay → settle) | +| **Interactive dev VM** | `nix run .#regtest` | poking the nodes by hand in a real shell | + +## Interactive dev VM — `nix run .#regtest` + +The friendly path. Boots the stack headless and drops you straight into +an SSH shell as the `operator` user — real terminal, copy/paste, +scrollback, no log spam: + +```bash +nix run .#regtest +``` + +You land in the VM with: + +- **user** `operator`, **password** `password` (also `sudo`) — a throwaway + local VM with fake coins, so the trivial password is fine. Your + `settings.sshKeys` are accepted too, if set. +- **aliases**: `btc` → `bitcoin-cli -rpcwallet=test`, `cln` → + `lightning-cli`; `lncli` is the LND CLI as-is. (Plain `bitcoin-cli` + works too — the single `test` wallet auto-selects.) +- **helper**: `regtest-fund` — funds LND on-chain and opens an LND→CLN + channel, so you start with a working channel instead of building it by + hand. +- **nodes**: `bitcoind` (regtest), Core Lightning p2p `:9735`, LND p2p + `:9736`. The chain pre-mines 110 blocks on boot (give it a few seconds). + +`exit` shuts the VM down and discards it (ephemeral disk in a tmp file). + +### A typical spin + +```bash +regtest-fund # fund + open a channel + +bolt11=$(cln invoice 50000000 demo demo | jq -r .bolt11) +lncli payinvoice --force "$bolt11" # LND pays CLN +cln listinvoices demo | jq '.invoices[0].status' # paid + +lncli listchannels | jq '.channels[] | {local_balance, remote_balance}' +btc getblockcount +``` + +### How it boots + +`nix run .#regtest` runs a small wrapper that: +1. boots `packages.regtest-vm` (a `system.build.vm`) headless, with an + ephemeral disk and `host:2222 → guest:22` forwarded; +2. waits for SSH, then `ssh operator@localhost`; +3. tears the VM down and cleans up the temp disk on exit. + +## CI test — `regtest-core` + +The automated assertion that the stack actually settles a payment: + +```bash +nix build .#checks.x86_64-linux.regtest-core -L # ~60s; exit 0 = passed +nix flake check -L # runs it with the rest +``` + +It pre-mines the chain, funds LND, opens an LND→CLN channel, has CLN issue +an invoice, LND pays it, and asserts CLN sees it `paid`. To poke the test +node by hand instead, use the raw nixos-test driver: + +```bash +nix build .#checks.x86_64-linux.regtest-core.driverInteractive +./result/bin/nixos-test-driver # Python REPL; regtest.succeed("…") +``` + +(The `nix run .#regtest` dev VM is the nicer interactive experience — the +driver REPL is awkward for ad-hoc use: `Ctrl-C` tears down the VM, the +QEMU window has no clipboard, and daemon logs flood the console.) + +## Layout + +- `tests/regtest-node.nix` — **shared** bitcoind + CLN + LND config (the + one source of truth). Consumers also import `nix-bitcoin.nixosModules.default`. +- `tests/regtest-core.nix` — the CI test; imports the shared node. +- `tests/regtest-interactive.nix` — the dev-VM layer (SSH, operator login, + aliases, `regtest-fund`, port forward); imports nothing about the test. +- `flake.nix` — wires `checks.regtest-core`, `packages.regtest-vm`, and + `apps.regtest`. + +## Follow-up: `regtest-full` + +This is the **core** tier (#27 tier 1). A `regtest-full` tier — adding +boltz / elements-liquid / litd / lnbits like the docker stack, and more +directions (CLN→LND, an eclair node) — is the documented follow-up on +aiolabs/omnixient#27. diff --git a/docs/system-map.md b/docs/system-map.md new file mode 100644 index 0000000..fd77406 --- /dev/null +++ b/docs/system-map.md @@ -0,0 +1,299 @@ +# aiolabs system map + +The orientation doc for someone returning to this stack after a +break — or arriving fresh. Where everything lives, how the pieces +fit together, who holds which keys, where to start for common +tasks. + +For omni-the-desktop specifically (Hyprland, themes, mksystem, +flake structure), see [`ARCHITECTURE.md`](ARCHITECTURE.md). For +fleet deploy mechanics, see [`deploy-strategy.md`](deploy-strategy.md). +This doc is the **cross-project** map. + +--- + +## The intent + +The aiolabs stack is a self-hosted, Bitcoin/Lightning- and Nostr- +native ecosystem built around the operational needs of **Château du +Faune** — a collective farm + artist residency in Ariège, France — +designed from the start to be adopted by other communities with +similar shape (co-ops, intentional communities, small-scale +agriculture, retreat centers). + +The principles: + +1. **Self-hostable end to end.** No SaaS dependencies in the critical + path. Every running service has a corresponding entry in `aiolabs/ + server-deploy` and can be reproduced on commodity hardware. +2. **Open protocols over proprietary schemas.** Nostr (NIP-52, + NIP-72, NIP-46), iCalendar VTODO, ActivityStreams. Other + communities can adopt the same shape; their renderers and ours + interoperate by virtue of being on the same wire. +3. **Internal-network defaults.** The community-organizer + capabilities work during WAN outages — `docs.ariege.io`, the + Nostr relay, LNbits, and the maubot daemon all colocate on + `host1` so the foyer e-ink panel and the Matrix bots keep + functioning without the internet. +4. **Identity is the user's, not the operator's.** Per-user Nostr + identity, held by a sidecar nsec bunker, with per-device scoped + tokens for every client that signs on the user's behalf. See + *Trust boundaries* below. + +--- + +## Component map + +``` + ───── aiolabs stack ───── + + ┌─────────────────────────────────────────────────────────────────┐ + │ DEV BOX (this machine — bohm) │ + │ │ + │ /etc/nixos (omni) ──── this machine's NixOS + home-manager │ + │ ~/dev/ ────────────── worktree of every project repo │ + │ │ + └────────────────────────────────────┬─────────────────────────────┘ + │ deploys via + ▼ + ┌─────────────────────────────────────────────────────────────────┐ + │ FLEET HOSTS (via server-deploy) │ + │ │ + │ host1 (Château du Faune) host2 host4 │ + │ • Continuwuity • dev • staging │ + │ (Matrix homeserver) • lnbits • webapp demo │ + │ • maubot daemon │ + │ • LNbits + nsecbunkerd │ + │ • Nostr relay │ + │ • castle-docs (Quartz) │ + │ • nginx + sops │ + │ │ + │ host5 host3 host6 (WG hub) │ + │ production-tier hosts │ + └────────────────┬──────────────────────────────┬─────────────────┘ + │ │ + ▼ ▼ + ┌────────────────────────────┐ ┌──────────────────────────────┐ + │ USER-FACING SURFACES │ │ PHYSICAL DEVICES │ + │ │ │ │ + │ webapp (PWA) │ │ inky-impression (Pi + eink) │ + │ activities / wallet │ │ foyer display │ + │ forum / market / chat │ │ │ + │ │ │ bitSpire ATM │ + │ Matrix client (Element) │ │ Nostr-native kiosk │ + │ │ │ │ + │ docs.ariege.io │ │ tufty-badge, InkyImpression │ + │ (Quartz static site) │ │ LivestockGuardDogs │ + │ │ │ standalone hardware │ + └────────────────────────────┘ └──────────────────────────────┘ +``` + +### What lives where in `~/dev/` + +| Path | Role | Where to read more | +|---|---|---| +| `deploy/server-deploy/` | NixOS flake for the fleet — single source of truth for what runs on which host | `flake.nix` + per-host modules | +| `lnbits/main/`, `lnbits/dev/`, `lnbits/nostr-transport/` | Lightning + Nostr identity backend | `lnbits/main/CLAUDE.md`; per-extension READMEs | +| `webapp/main/`, `webapp/dev/` | Vue/Vite/Electron PWA — user-facing apps over Nostr | `webapp/main/CLAUDE.md` | +| `bitspire/bitspire/{main,dev}`, `bitspire/atm-tui/` | bitSpire — KYC-free, Nostr-native Bitcoin ATM frontend (+ atm-tui) | `bitspire/bitspire/CLAUDE.md` | +| `maubot-plugins/` | Matrix bot plugins: `journal`, `tracker`, `wiki` | `maubot-plugins/CLAUDE.md`; `maubot-plugins/docs/community-organizer-spec.md` | +| `inky-impression/` | E-ink renderer for the foyer panel | `inky-impression/CLAUDE.md` | +| `docs/castle-docs/` | Operational wiki (Quartz, Obsidian-flavored markdown) | `docs/castle-docs/CLAUDE.md` | +| `lnbits-extensions/` | The aiolabs LNbits extension catalog (`extensions.json`) | `~/dev/CLAUDE.md` — see "Forgejo conventions" | +| `shared/extensions/` | LNbits extension source tree (mounted into dev compose) | Per-extension READMEs | +| `local/docker/regtest/` | Local Lightning regtest stack for end-to-end testing | `local/README.md` | +| `refs/` | Curated mirrors of upstream reference codebases (Nostr NIPs, Khatru, nostr-tools, etc.) | `refs/README.md` | +| `upstream-prs/` | Branches staged for upstream contribution | per-branch context | + +### Per-project CLAUDE.md (defer to these for repo-specific work) + +The cross-cutting facts live in `~/dev/CLAUDE.md`. Anything more +specific belongs in the per-project `CLAUDE.md`. Start at the most +specific one for your task and walk outward only if the answer isn't +there. + +--- + +## The end-to-end data flow + +This is the picture once everything's wired up. Some pieces (per-user +signing) are in flight — see *Status snapshot* below. + +``` + USER (in a Matrix room) + │ + │ !task fix the south fence #urgent + ▼ + Continuwuity homeserver ─────── Matrix federation + │ + ▼ + maubot daemon (tracker plugin) + │ + │ resolve binding: MXID → LNbits user + │ resolve signer: LNbits → bunker URL + scoped token + │ + │ build NIP-52 event (kind 31922, community a-tag) + │ NIP-46 RPC to nsecbunkerd ──→ sign as user + │ + ▼ + Internal Nostr relay (loopback / wss://lnbits./nostrrelay) + │ + │ fan out to public relays (relay.ariege.io, …) + │ + ▼ + PUBLIC NOSTR + │ + ├─── inky-impression subscribes ──→ renders foyer scene + │ + ├─── webapp subscribes ──→ shows user's items in PWA + │ + └─── any other subscriber ──→ mobile push, etc. +``` + +The community-organizer protocol that defines these event shapes is at +`~/dev/maubot-plugins/docs/community-organizer-spec.md`. It's +runtime-agnostic — other projects can produce the same NIP-52 events +from any source (CLI, web form, voice assistant) and the renderers +don't care. + +--- + +## Trust boundaries (where keys live) + +Knowing what's compromised when X is compromised is the first thing +to need when something breaks. The honest picture: + +| Key | Where it lives | Compromise impact | Reference | +|---|---|---|---| +| **User target keys** (`X_alice`, `X_bob`, …) | `nsecbunkerd` sidecar on the LNbits host, encrypted at rest with a passphrase the operator supplies at boot | Catastrophic for user identity *if* the bunker itself is breached. Bunker has a small attack surface (no web server, no plugins). | `aiolabs/lnbits#9`, `#18` | +| **Per-device scoped tokens** (tracker, ATM, webapp, …) | The client device that holds them. Tracker holds them in its SQLite. Webapp holds them in IndexedDB/localStorage. ATM holds them in its config. | Bounded by token scope. A compromised tracker can publish NIP-52 events as the user; nothing else. Revoke the token at the bunker side; target key unaffected. | spec §7.3 | +| **Operator master `M_lnbits`** | `nsecbunkerd` admin key. Used by LNbits to authorize admin RPCs (create-account, issue-token, revoke). | Full identity-pool admin access. Operator is on the hook. | `aiolabs/lnbits#18` | +| **LNbits agent npub** | LNbits process memory, sops-managed at rest | Allows the holder to call `create_account` / `create_token` / `revoke_user` — but NOT to sign as users directly (no nsec). Bounded compared to holding the master. | `aiolabs/lnbits#18` admin client section | +| **maubot bot keypair** (`@trackerbot:ariege.io`) | maubot's sops-managed config on host1 | Can publish events under the bot's own pubkey (used as fallback when a user is unbound). No access to user signing. | `server-deploy/modules/services/maubot.nix` | +| **Server identity (`NOSTR_TRANSPORT_PRIVATE_KEY`)** | LNbits env var, sops-managed | Allows impersonation of the LNbits instance itself on the Nostr transport channel. Migrating to bunker-signed in #18. | `aiolabs/lnbits#4`, `#9` | +| **Wireguard / SSH host keys** | Per host, NixOS-generated | Lateral movement between fleet hosts. Operator concern. | `server-deploy/hosts//` | + +### What's public vs community-private + +| Channel | Audience | +|---|---| +| `wss://relay.ariege.io` | Public Nostr — anyone can subscribe. Community-organizer events for outward-facing rooms land here. | +| `wss://lnbits.ariege.io/nostrrelay/` | Auth-gated internal relays. `nsecbunker` channel is bunker RPC; other channels carry community-scoped events that shouldn't leave the host. | +| Matrix rooms | Federated by default. Set rooms to invite-only for community-private discussions; the bot reads room membership as trust boundary. | +| LNbits API | Per-user auth (NIP-98 or session). Bot agent has its own scoped credentials. | +| `docs.ariege.io` | Public read; published Quartz site. Castle-docs `private/` directory is git-ignored. | + +--- + +## Cross-repo dependencies + +When you change repo A, repo B may need a follow-up to actually +deploy. The chain: + +``` + ~/dev// changes go here first + │ + ▼ + `aiolabs/` (forgejo) push to the canonical remote + │ + ▼ + `aiolabs/server-deploy` flake.lock bump the input pointing at + │ (`nix flake lock --update-input `) + ▼ + deploy.sh rolls the change to the host +``` + +Catalogs (LNbits extension catalog, etc.) are the exception — they +read live from `aiolabs/lnbits-extensions/extensions.json` at install +time, so no flake.lock bump is needed there. See +`~/dev/CLAUDE.md` "Forgejo conventions" for the per-repo specifics +(direct-to-main vs PR, fork versioning scheme, catalog rules). + +--- + +## Status snapshot (2026-05-26) + +Not everything in the data-flow diagram is shipped. The current state: + +| Capability | Status | +|---|---| +| Matrix + Continuwuity homeserver on host1 | ✅ live | +| `journal` maubot plugin (`@journalbot:ariege.io`) | ✅ live | +| `tracker` maubot plugin (Phase 1 — Matrix + SQLite, rules classifier) | ✅ in repo, ready to deploy | +| `wiki` maubot plugin (HTTP search against `docs.ariege.io`) | ✅ in repo, ready to deploy | +| Community-organizer protocol spec | ✅ at `~/dev/maubot-plugins/docs/community-organizer-spec.md` | +| LNbits Nostr-transport (HTTP-free API) | ✅ merged (#4) | +| LNbits signer abstraction + transitional LocalSigner | ✅ Phase 1 of #9 — PR #17 (open, awaiting cascading merge) | +| **`nsecbunkerd` aiolabs fork functional** | ✅ Fork at `aiolabs/nsecbunkerd@master`; 4 upstream-rot patches applied (`#1`–`#4`, `#8`); native nix flake at commit `711a017`; 4 known upstream issues still open (`#5`/`#6`/`#7`/`#8` echo + multi-arch + getKeys) | +| **LNbits bunker provisioning primitive (phase 2.1)** | ✅ PR #19 (open, stacked on #17) — `NsecBunkerAdminClient` + `RemoteBunkerSigner`; live `ping` + `create_new_key` round-trip ~30ms | +| LNbits NIP-46 `sign_event` over bunker (phase 2.2-2.3) | 🚧 blocked on upstream `aiolabs/nsecbunkerd#4`/`#7` stabilizing | +| `error_code` wire vocabulary (14 codes, ATM ↔ LNbits) | ✅ agreed; lands alongside phase 2.3 — see `~/dev/coordination/log.md` | +| Extension `account.prvkey` migration to `resolve_signer` | 🚧 pre-cascade prerequisite for PR #17 — umbrella at `aiolabs/lnbits#21`; per-extension issues filed (`nostrmarket#5`, `restaurant#11`, `tasks#3`, `events#23`, spirekeeper TBD) | +| Tracker Nostr publish bridge (Phase 2a) | ⏸ waiting on bunker phase 2.3 | +| Per-user signing via bunker (Phase 2c) | ⏸ waiting on bunker phase 2.3 | +| `inky-impression` Nostr-native scene plugin | ⏸ waiting on tracker publishing | +| Webapp Nostr identity via NIP-46 | ⏸ waiting on bunker phase 2.3 | + +### 2026-05-26 day's shipping + +- **Cross-session coordination log** established at `~/dev/coordination/log.md` (protocol in sibling `README.md`). Replaces user-relayed messages between the parallel `lnbits`, `bitspire`, `spirekeeper`, and `alfred` sessions. Append-only, chronological. +- **LNbits PR #19** opened — operator-IdP signup primitive via `aiolabs/nsecbunkerd`. Stacked under PR #17 awaiting cascading merge. +- **`aiolabs/nsecbunkerd`** — 4 upstream-rot patches landed on `master` so the fork builds + runs from a clean clone (`06272c8`, `960b939`, `42dbbd7`, `e39eaa6`); 8 issues filed total documenting the upstream gaps. +- **`aiolabs/lnbits#20`** — outbound `_fanout_payment` defense-in-depth dedup (low priority, symmetric to `aiolabs/bitspire#50`). +- **`aiolabs/lnbits#21`** — umbrella audit catching 5 extensions still reading `account.prvkey` directly. Each affected ext now has its own tracking issue (or pre-existing for `nostrmarket#5`). +- **Canonical sat-amount vocabulary** locked across spirekeeper / bitspire / atm-tui: `wire_sats` / `principal_sats` / `fee_sats` / `fee_fraction` (unit fraction, never percentage). See `~/dev/coordination/log.md` 2026-05-26T17:10Z + 18:50Z entries. +- **`bitspire` #49 + #50** closed (Schnorr-verify on RPC replies + two-tier hash dedup on subscribe_payments callbacks). Symmetric lift on the bunker-client side landed in PR #19 commit `4ebcd959`. + +--- + +## Start-here runbook + +| I want to… | Read first | +|---|---| +| Add a new aiolabs project | `~/dev/CLAUDE.md` (workspace conventions); pick the repo's own CLAUDE.md once it exists | +| Deploy a change to a fleet host | `deploy-strategy.md` in this dir; `aiolabs/server-deploy` README | +| Add a new maubot plugin | `~/dev/maubot-plugins/CLAUDE.md`; the multi-line `@command.passive` footgun in `~/dev/CLAUDE.md` | +| Modify a verb / event shape | `~/dev/maubot-plugins/docs/community-organizer-spec.md` **first**, then the plugin code | +| Add a page to the operational wiki | `~/dev/docs/castle-docs/CLAUDE.md`; commit + push triggers Quartz rebuild | +| Render something on the foyer e-ink panel | `~/dev/inky-impression/` (currently HTTP-screenshot; Nostr scenes are planned) | +| Fork or bump an LNbits extension | `~/dev/CLAUDE.md` "Extension version-bump procedure" | +| Add a NixOS option / tool on this dev box | `/etc/nixos/home.nix` or `/etc/nixos/configuration.nix`, then `omni-rebuild` | +| Where are user nsecs? | `nsecbunkerd` on the LNbits host. See `aiolabs/lnbits#9`, `#18`. **Not in lnbits's DB.** | +| Where do connection tokens live? | The client device (maubot SQLite, webapp localStorage, ATM config). Revoke at the bunker. | + +--- + +## Glossary + +A few terms recur across the stack: + +- **IdP** — identity provider. In our stack, LNbits is the IdP for end + users; it brokers token issuance against the sidecar bunker. +- **Bunker** — `nsecbunkerd` (or any NIP-46-compliant sidecar) that + holds the actual nsec material. Speaks kind-24133 (signing) and + kind-24134 (admin) over a relay. +- **Target key** — a per-user nsec held inside the bunker. Addressable + by its npub; never leaves the bunker. +- **Operator master `M_lnbits`** — the bunker's admin key. Used to + authorize admin RPCs (create-account, issue-token, revoke). +- **Connection token** — a per-device NIP-46 credential, scoped to + specific event kinds, issued by the bunker to a specific client + (tracker, webapp, ATM). Compromise leaks only the scope. +- **Signer abstraction** — the `NostrSigner` ABC in + `lnbits/core/signers/` and mirrored in tracker. Four implementations: + `BotSigner`, `RemoteBunkerSigner` (steady state), `ClientSideOnly + Signer` (sovereignty exit), `LocalSigner` (transitional migration + helper). +- **Community a-tag** — NIP-72 community reference, `34550:: + `. The way every community-organizer event scopes itself to + a community. +- **Capture / clarify** — GTD framing. `!add` is capture (frictionless, + inbox); the rules classifier (or future LLM) does the clarify step + that sorts into a kind. +- **Universal verb vs community shortcut** — the spec mandates universal + verbs (`!add`, `!task`, `!journal`, `!remind`, `!done`, `!list`). + Communities configure their own shortcuts (`!buy`, `!steward`, …) + per room. +- **Spec-first** — the working convention for cross-system features: + write the protocol doc before the implementation. See community- + organizer-spec.md as the worked example.