docs: project documentation — architecture, getting-started, packs, mcp, regtest
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7f4a71b98d
commit
2baad92089
9 changed files with 1392 additions and 0 deletions
221
docs/ARCHITECTURE.md
Normal file
221
docs/ARCHITECTURE.md
Normal file
|
|
@ -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.<name>.enable` — media, development, gaming, office,
|
||||||
|
containers, lnbits, aiolabs, bitcoin. Exposed as reusable
|
||||||
|
`flake.nixosModules.<pack>`; `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
|
||||||
|
<name> <name>.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>
|
||||||
|
↓
|
||||||
|
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.
|
||||||
25
docs/README.md
Normal file
25
docs/README.md
Normal file
|
|
@ -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.<name>.enable`, the pack catalog, enabling packs (on a
|
||||||
|
host, via presets/features, or standalone on another NixOS via
|
||||||
|
`nixosModules.<pack>`), 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`.
|
||||||
137
docs/deploy-strategy.md
Normal file
137
docs/deploy-strategy.md
Normal file
|
|
@ -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=<name>` builds the system closure locally or in CI.
|
||||||
|
2. **Push** — `make cache HOST=<name>` 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 .#<host>` 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.
|
||||||
201
docs/getting-started.md
Normal file
201
docs/getting-started.md
Normal file
|
|
@ -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/<yourhost>/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 .#<host>` 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/<youruser>/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.<name>.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 .#<host>
|
||||||
|
```
|
||||||
|
|
||||||
|
> **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 .#<host> \
|
||||||
|
> --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.
|
||||||
105
docs/mcp.md
Normal file
105
docs/mcp.md
Normal file
|
|
@ -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.<name>` option block in `modules/mcp.nix`.
|
||||||
|
2. Append a corresponding `optionalAttrs cfg.servers.<name>.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.
|
||||||
54
docs/moonlander.md
Normal file
54
docs/moonlander.md
Normal file
|
|
@ -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.
|
||||||
249
docs/packs.md
Normal file
249
docs/packs.md
Normal file
|
|
@ -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.<name>.enable`.
|
||||||
|
> - `lnbits` / `aiolabs` / `bitcoin` are **standalone-importable** — usable
|
||||||
|
> on a non-Omnixient NixOS via `inputs.omni.nixosModules.<pack>`.
|
||||||
|
> - 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.<name>.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/<name>/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.<x>.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/<name> 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/<name>.nix` using the canonical template:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{ config, lib, pkgs, ... }:
|
||||||
|
let cfg = config.omni.packs.<name>; in {
|
||||||
|
options.omni.packs.<name>.enable =
|
||||||
|
lib.mkEnableOption "<one-line description>";
|
||||||
|
|
||||||
|
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.<name> = import ./modules/packs/<name>.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.<core>`**. (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 <worktree>` — 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.
|
||||||
101
docs/regtest.md
Normal file
101
docs/regtest.md
Normal file
|
|
@ -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.
|
||||||
299
docs/system-map.md
Normal file
299
docs/system-map.md
Normal file
|
|
@ -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.<domain>/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/<host>/` |
|
||||||
|
|
||||||
|
### 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/<channel>` | 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/<project>/ changes go here first
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
`aiolabs/<project>` (forgejo) push to the canonical remote
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
`aiolabs/server-deploy` flake.lock bump the input pointing at <project>
|
||||||
|
│ (`nix flake lock --update-input <project>`)
|
||||||
|
▼
|
||||||
|
deploy.sh <host> 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:<pubkey>:
|
||||||
|
<d-tag>`. 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.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue