docs: align public copy with current runtime model

This commit is contained in:
Sam & Claude 2026-03-14 20:22:57 +01:00
parent 72cdf8960d
commit 2133db601f
15 changed files with 280 additions and 169 deletions

View file

@ -7,7 +7,7 @@ After a fresh `initdb`, PostgreSQL ships with:
- One superuser: `postgres` (no password)
- Auth method: `trust` for localhost only
- `listen_addresses = 'localhost'` — only accepts connections from inside the jail
- No remote access — other jails (cms, controlplane) cannot connect
- No remote access — the host orchestrator and cms jail cannot connect
This is fine for initial setup but must be hardened before any remote jail connects.
@ -22,18 +22,29 @@ Set a password anyway as defense in depth:
```sh
. /home/clawdie/clawdie-ai/.env
sudo jexec db su - postgres -c "psql -c \"ALTER USER postgres WITH PASSWORD '$POSTGRES_ADMIN_PASSWORD';\""
sudo jexec ${AGENT_NAME}-db su - postgres -c "psql -c \"ALTER USER postgres WITH PASSWORD '$POSTGRES_ADMIN_PASSWORD';\""
```
### Role: clawdie_brain (Clawdie memory)
### Role: `SKILLS_DB_USER` (Agent System Skills)
For the Clawdie agent memory database:
For the skills database:
```sh
. /home/clawdie/clawdie-ai/.env
sudo jexec db su - postgres -c "createuser clawdie_brain"
sudo jexec db su - postgres -c "psql -c \"ALTER USER clawdie_brain WITH PASSWORD '$CLAWDIE_DB_PASSWORD';\""
sudo jexec db su - postgres -c "createdb -O clawdie_brain ai_brain"
sudo jexec ${AGENT_NAME}-db su - postgres -c "createuser ${SKILLS_DB_USER}"
sudo jexec ${AGENT_NAME}-db su - postgres -c "psql -c \"ALTER USER ${SKILLS_DB_USER} WITH PASSWORD '$SKILLS_DB_PASSWORD';\""
sudo jexec ${AGENT_NAME}-db su - postgres -c "createdb -O ${SKILLS_DB_USER} ${SKILLS_DB_NAME}"
```
### Role: `MEMORY_DB_USER` (User-Agent Memory)
For the user/agent memory database:
```sh
. /home/clawdie/clawdie-ai/.env
sudo jexec ${AGENT_NAME}-db su - postgres -c "createuser ${MEMORY_DB_USER}"
sudo jexec ${AGENT_NAME}-db su - postgres -c "psql -c \"ALTER USER ${MEMORY_DB_USER} WITH PASSWORD '$MEMORY_DB_PASSWORD';\""
sudo jexec ${AGENT_NAME}-db su - postgres -c "createdb -O ${MEMORY_DB_USER} ${MEMORY_DB_NAME}"
```
### Role: strapi_cms (CMS)
@ -62,7 +73,7 @@ host all all ::1/128 trust
### Target
Keep localhost trust for admin. Add password-authenticated remote access
for specific jails only:
for the host on `warden0` and for the future cms jail only:
```
# local admin — trust (postgres superuser only uses this)
@ -70,11 +81,12 @@ local all all trust
host all all 127.0.0.1/32 trust
host all all ::1/128 trust
# controlplane jail — Clawdie memory
host ai_brain clawdie_brain 10.0.0.100/32 scram-sha-256
# host-side orchestrator on warden0 gateway
host ${SKILLS_DB_NAME} ${SKILLS_DB_USER} 10.0.0.1/32 scram-sha-256
host ${MEMORY_DB_NAME} ${MEMORY_DB_USER} 10.0.0.1/32 scram-sha-256
# cms jail — Strapi CMS
host strapi_cms strapi_cms 10.0.0.3/32 scram-sha-256
host strapi_cms strapi_cms 10.0.0.5/32 scram-sha-256
# deny everything else from the subnet
host all all 10.0.0.0/24 reject
@ -88,7 +100,7 @@ no other jail on the subnet can connect.
```sh
# edit pg_hba.conf inside db jail (use the commands above)
# then reload — no restart needed
sudo jexec db service postgresql reload
sudo jexec ${AGENT_NAME}-db service postgresql reload
```
## Listen addresses (postgresql.conf)
@ -104,7 +116,7 @@ Change from:
To:
```
listen_addresses = 'localhost, 10.0.0.2'
listen_addresses = 'localhost, 10.0.0.3'
```
This makes PostgreSQL listen on both localhost (for admin) and the VNET
@ -113,7 +125,7 @@ interface (for remote jails). Do NOT use `'*'` — only bind to known IPs.
Reload after changing:
```sh
sudo jexec db service postgresql reload
sudo jexec ${AGENT_NAME}-db service postgresql reload
```
## Verification
@ -121,21 +133,21 @@ sudo jexec db service postgresql reload
### From inside db jail (should work — trust)
```sh
sudo jexec db su - postgres -c "psql -c 'SELECT 1;'"
sudo jexec ${AGENT_NAME}-db su - postgres -c "psql -c 'SELECT 1;'"
```
### From controlplane jail (should work — password)
### From host (should work — password)
```sh
sudo jexec controlplane psql -h 10.0.0.2 -U clawdie_brain -d ai_brain -c "SELECT 1;"
psql "$MEMORY_DB_URL" -c "SELECT 1;"
```
Will prompt for password. Use the `clawdie_brain` password.
Will use the password embedded in `MEMORY_DB_URL`.
### From cms jail (should work — password)
```sh
sudo jexec cms psql -h 10.0.0.2 -U strapi_cms -d strapi_cms -c "SELECT 1;"
sudo jexec ${AGENT_NAME}-cms psql -h 10.0.0.3 -U strapi_cms -d strapi_cms -c "SELECT 1;"
```
Will prompt for password. Use the `strapi_cms` password.
@ -143,7 +155,7 @@ Will prompt for password. Use the `strapi_cms` password.
### From a random jail or host (should be rejected)
```sh
psql -h 10.0.0.2 -U postgres -d postgres -c "SELECT 1;"
psql -h 10.0.0.3 -U postgres -d postgres -c "SELECT 1;"
```
Should fail with: `FATAL: pg_hba.conf rejects connection`
@ -154,17 +166,24 @@ All database passwords live in `/home/clawdie/clawdie-ai/.env`:
```
POSTGRES_ADMIN_PASSWORD=<generated>
CLAWDIE_DB_PASSWORD=<generated>
SKILLS_DB_PASSWORD=<generated>
MEMORY_DB_PASSWORD=<generated>
STRAPI_DB_PASSWORD=<generated>
```
The setup flow derives PostgreSQL-safe names from `AGENT_NAME`. Example:
`AGENT_NAME=clawdie-ai` becomes `SKILLS_DB_USER=clawdie_ai_reader` and
`MEMORY_DB_USER=clawdie_ai_brain`.
Generate with: `python3 -c "import secrets; print(secrets.token_urlsafe(24))"`
Source before running setup commands: `. /home/clawdie/clawdie-ai/.env`
When jails need their own `.env`, copy only the relevant variable:
- controlplane: `CLAWDIE_DB_PASSWORD`
- cms: `STRAPI_DB_PASSWORD`
When a future Strapi layer needs its own `.env`, copy only the relevant
variables:
- cms / Strapi DB: `STRAPI_DB_PASSWORD`
- cms / Strapi app: `STRAPI_APP_KEYS`, `STRAPI_API_TOKEN_SALT`,
`STRAPI_ADMIN_JWT_SECRET`, `STRAPI_TRANSFER_TOKEN_SALT`, `STRAPI_JWT_SECRET`
Never commit passwords to git. Never store them in skill files.
@ -172,13 +191,13 @@ Never commit passwords to git. Never store them in skill files.
1. ZFS snapshot db jail first
2. Set postgres superuser password
3. Create clawdie_brain role + ai_brain database
4. Create strapi_cms role + strapi_cms database
3. Create skills + memory roles/databases
4. Create strapi_cms role + strapi_cms database (reserved for later Strapi use)
5. Update pg_hba.conf with remote access rules
6. Update listen_addresses in postgresql.conf
7. Reload PostgreSQL
8. Test from inside db jail (trust)
9. Test from controlplane jail (password)
9. Test from host-side memory access (password)
10. Test from cms jail (password — after cms jail exists)
11. ZFS snapshot db jail after successful setup

View file

@ -120,6 +120,18 @@ Use `warden0` as the canonical host bridge name for Bastille/Warden networking.
- Runtime source of truth lives in `src/jail-config.ts`.
- If a historical reference must remain, mark it explicitly as archived.
## Internal DNS Naming
Use `home.arpa` as the canonical internal namespace for host and jail-local
resolution.
- `AGENT_INTERNAL_DOMAIN` should default to `<agent>.home.arpa`.
- Do not introduce new `.local` defaults for internal service names.
- `.local` is reserved for mDNS and can create resolver ambiguity or name
leakage on the local link.
- `AGENT_DOMAIN` is public-facing and should use a real public domain or the
safe placeholder `<agent>.invalid`, not an internal hostname.
## ZFS Snapshot Naming
**User-facing manual snapshots** must use `DD.mmm.YYYY` rendered through the

View file

@ -1,7 +1,7 @@
BSD 3-Clause License
Copyright (c) 2026, Sam (Samo Blatnik) and Clawdie contributors
Built on NanoClaw — Copyright (c) 2024 Peter Steinberger (MIT License)
Built on NanoClaw — Copyright (c) 2024 Gavriel (MIT License)
All rights reserved.
Redistribution and use in source and binary forms, with or without

View file

@ -10,14 +10,14 @@ provider is configured.
This means:
- Brain A ships prebuilt
- Brain A imports into the `db` jail during bootstrap
- Brain B remains separate and evolves later
- Agent System Skills ship prebuilt
- Agent System Skills import into the `db` jail during bootstrap
- User-Agent Memory remains separate and evolves later
- provider keys are not required for initial install
## Definitions
### Brain A
### Agent System Skills
Built-in Clawdie knowledge:
@ -26,7 +26,7 @@ Built-in Clawdie knowledge:
- product skills
- curated support content shipped with the repo/release
### Brain B
### User-Agent Memory
Runtime/user memory:
@ -36,14 +36,14 @@ Runtime/user memory:
## Default Strategy
1. Build Brain A outside the user install flow.
2. Ship Brain A as a versioned artifact.
3. Import Brain A into the local `db` jail during bootstrap.
1. Build Agent System Skills outside the user install flow.
2. Ship Agent System Skills as a versioned artifact.
3. Import Agent System Skills into the local `db` jail during bootstrap.
4. Add production LLM keys later for live agent execution.
## Step-by-Step
### Step 1: Select Brain A source content
### Step 1: Select Agent System Skills source content
Start with a fixed allowlist:
@ -82,13 +82,13 @@ Allowed paths:
The install path should not depend on either being available on the target host.
### Step 4: Export a Brain A artifact
### Step 4: Export an Agent System Skills artifact
Ship an importable artifact, not a live PostgreSQL data directory.
Recommended first format:
- SQL dump for Brain A tables
- SQL dump for Agent System Skills tables
- metadata JSON next to it
Metadata must include:
@ -107,14 +107,14 @@ During install:
1. create `{agent}-db`
2. install PostgreSQL + pgvector
3. apply schema
4. import Brain A SQL artifact
4. import Agent System Skills SQL artifact
5. verify metadata and row counts
This is the moment Brain A becomes available locally.
This is the moment Agent System Skills become available locally.
### Step 6: Defer provider setup
Only after Brain A import succeeds:
Only after Agent System Skills import succeeds:
- prompt for production provider keys
- write keys to `.env`
@ -123,16 +123,16 @@ Only after Brain A import succeeds:
This keeps onboarding useful even before provider setup.
### Step 7: Support local Brain A rebuild later
### Step 7: Support local Agent System Skills rebuild later
After install, allow local updates from git/Gitea changes.
Recommended evolution path:
1. detect changed Brain A source files
1. detect changed Agent System Skills source files
2. re-chunk changed files only
3. re-embed changed chunks locally
4. update Brain A overlay/current index
4. update the Agent System Skills overlay/current index
Preferred local backend direction:
@ -142,17 +142,17 @@ Storage remains in the `db` jail.
## First Implementation Rules
- default = import prebuilt Brain A
- default = import prebuilt Agent System Skills
- no mandatory online provider during install
- no mandatory local model during install
- keep Brain A and Brain B separate
- keep Agent System Skills and User-Agent Memory separate
- keep the `db` jail as storage/query layer
- do not store raw Postgres data directories in git
## Immediate Follow-Up
1. Define the exact Brain A source allowlist
1. Define the exact Agent System Skills source allowlist
2. Define the first chunking policy
3. Define artifact schema + metadata contract
4. Fold built-in knowledge import into the default `db` bootstrap sequence
5. Add provider setup after Brain A import, not before
5. Add provider setup after Agent System Skills import, not before

View file

@ -1,5 +1,9 @@
# Starlight Starter Kit: Basics
Prototype note: this Starlight example is not the recommended first deployment
path for Clawdie on FreeBSD. The v1 `cms` jail route should prefer a minimal
Astro site without `sharp` or Astro image optimization.
[![Built with Starlight](https://astro.badg.es/v2/built-with-starlight/tiny.svg)](https://starlight.astro.build)
```

View file

@ -28,10 +28,10 @@ on FreeBSD. She lives on your own hardware, connects through Telegram,
remembers everything in a Postgres database, and is accessible via a tmux
glass-pane console. No corporate cloud. No subscription. No data leaving your server.
She is built on the **OpenClaw** ecosystem —
the open source personal AI assistant framework created by
Peter Steinberger (https://github.com/steipete) —
and adapted to run on FreeBSD with a Tailscale-secured infrastructure.
She is built on the **OpenClaw** line.
Peter Steinberger's OpenClaw set the broader ecosystem,
NanoClaw carried that line into a lean upstream assistant,
and Clawdie adapts it to run on FreeBSD with a Tailscale-secured infrastructure.
Building a working AI assistant is constant work. Clawdie is actively developing.
Our mission is always one step ahead — connecting the assistant to the physical
@ -58,9 +58,10 @@ built independently by developers and hardware makers around the world.
We stand on their shoulders.
### OpenClaw
The original. Personal AI assistant you run on your own devices.
The original direction. Personal AI assistant you run on your own devices.
Multi-channel (Telegram, WhatsApp, Slack, Discord), voice, browser automation.
Created by Peter Steinberger — the foundation Clawdie is built on.
Created by Peter Steinberger — the broader foundation that later inspired
NanoClaw and, eventually, Clawdie.
TypeScript · Node ≥22 · MIT · openclaw.ai
https://github.com/openclaw/openclaw

View file

@ -347,13 +347,13 @@ npm run wizard</code></pre>
<span class="installer-badge">Auto-advance: 9s</span>
</div>
<div class="installer-screen active"><div class="installer-panel"><div class="installer-titlebar">01 / 11 — Welcome</div><div class="installer-body"><p>This wizard locks in the operator account, SSH bootstrap, deployment profile, local code hosting, service jails, and provider settings before writing <span class="installer-inline">.env</span>.</p><p class="installer-note">Architecture choices stay early so jail roles and reserved IPs remain stable.</p><div class="installer-footer"><span>Enter = continue</span><span>Esc = exit</span></div></div></div></div>
<div class="installer-screen"><div class="installer-panel"><div class="installer-titlebar">02 / 11 — Operator User</div><div class="installer-body"><p>Choose the operator account that will exist inside controlplane for SSH access, repo checkout, screenshots, and operator tooling.</p><p>Username:<span class="installer-input">clawdie</span></p><p class="installer-note">Default is <span class="installer-inline">clawdie</span>. Replace it if you want to use your own operator account name.</p><div class="installer-footer"><span>Type = edit</span><span>Allowed: a-z 0-9 _ -</span></div></div></div></div>
<div class="installer-screen"><div class="installer-panel"><div class="installer-titlebar">03 / 11 — Controlplane SSH Method</div><div class="installer-body"><p>Host → controlplane SSH is bootstrapped separately from the later controlplane → host automation key.</p><div class="installer-list"><div class="installer-option"><span class="installer-pointer"></span><div><strong>Generate dedicated host key</strong><br>Recommended when the operator differs from the installer user.</div></div><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>Use existing public key</strong><br>Only <span class="installer-inline">.pub</span> files are listed. Private keys are never copied.</div></div><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>Paste public key manually</strong><br>Fallback when autodetect finds nothing useful.</div></div></div><div class="installer-footer"><span>Authorized key target</span><span>/home/&lt;operator-user&gt;/.ssh/authorized_keys</span></div></div></div></div>
<div class="installer-screen"><div class="installer-panel"><div class="installer-titlebar">04 / 11 — Deployment Profile</div><div class="installer-body"><div class="installer-list"><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>Minimal</strong><br>Controlplane + database only.</div></div><div class="installer-option"><span class="installer-pointer"></span><div><strong>Standard</strong><br>Controlplane + database + local git + CMS.</div></div><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>Full</strong><br>Standard + full GUI desktop.</div></div></div><div class="installer-footer"><span>Default profile</span><span>Standard</span></div></div></div></div>
<div class="installer-screen"><div class="installer-panel"><div class="installer-titlebar">02 / 11 — Operator User</div><div class="installer-body"><p>Choose the operator account used for host-side setup, repo checkout, screenshots, and service administration.</p><p>Username:<span class="installer-input">clawdie</span></p><p class="installer-note">Default is <span class="installer-inline">clawdie</span>. Replace it if you want to use your own operator account name.</p><div class="installer-footer"><span>Type = edit</span><span>Allowed: a-z 0-9 _ -</span></div></div></div></div>
<div class="installer-screen"><div class="installer-panel"><div class="installer-titlebar">03 / 11 — Operator Access Method</div><div class="installer-body"><p>Host-side setup can reuse an existing public key or generate a dedicated one for operator access and later service automation.</p><div class="installer-list"><div class="installer-option"><span class="installer-pointer"></span><div><strong>Generate dedicated host key</strong><br>Recommended when the operator differs from the installer user.</div></div><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>Use existing public key</strong><br>Only <span class="installer-inline">.pub</span> files are listed. Private keys are never copied.</div></div><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>Paste public key manually</strong><br>Fallback when autodetect finds nothing useful.</div></div></div><div class="installer-footer"><span>Authorized key target</span><span>/home/&lt;operator-user&gt;/.ssh/authorized_keys</span></div></div></div></div>
<div class="installer-screen"><div class="installer-panel"><div class="installer-titlebar">04 / 11 — Deployment Profile</div><div class="installer-body"><div class="installer-list"><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>Minimal</strong><br>Worker + database only.</div></div><div class="installer-option"><span class="installer-pointer"></span><div><strong>Standard</strong><br>Worker + database + local git + CMS.</div></div><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>Full</strong><br>Standard + full GUI desktop.</div></div></div><div class="installer-footer"><span>Default profile</span><span>Standard</span></div></div></div></div>
<div class="installer-screen"><div class="installer-panel"><div class="installer-titlebar">05 / 11 — Code Hosting</div><div class="installer-body"><div class="installer-list"><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>External only</strong><br>Bootstrap and clone from a remote Git URL.</div></div><div class="installer-option"><span class="installer-pointer"></span><div><strong>Plain local git</strong><br>Bare repositories in a dedicated <span class="installer-inline">git</span> jail.</div></div><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>Local Gitea</strong><br>Plain local git plus a lightweight web UI.</div></div></div><div class="installer-footer"><span>Standard / Full default</span><span>Plain local git</span></div></div></div></div>
<div class="installer-screen"><div class="installer-panel"><div class="installer-titlebar">06 / 11 — Remote Git URL</div><div class="installer-body"><p>This step only appears when <strong>External only</strong> is selected for code hosting.</p><p>Remote URL:<span class="installer-input">https://codeberg.org/Clawdie/Clawdie-AI.git</span></p><p class="installer-note">Use this when bootstrap should come from Codeberg, a LAN mirror, or another self-hosted remote.</p><div class="installer-footer"><span>Examples</span><span>https://... or ssh://git@...</span></div></div></div></div>
<div class="installer-screen"><div class="installer-panel"><div class="installer-titlebar">07 / 11 — Service Toggles</div><div class="installer-body"><div class="installer-list"><div class="installer-option"><span class="installer-pointer"></span><div><strong>CMS</strong><br>Astro + Strapi in the dedicated <span class="installer-inline">cms</span> jail.</div></div><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>Ollama</strong><br>Optional local inference and embeddings in a separate jail.</div></div><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>GUI desktop</strong><br>Only available for the <span class="installer-inline">Full</span> profile.</div></div></div><div class="installer-footer"><span>Default</span><span>CMS on, Ollama off</span></div></div></div></div>
<div class="installer-screen"><div class="installer-panel"><div class="installer-titlebar">08 / 11 — Network Configuration</div><div class="installer-body"><p>Lower IP numbers are reserved for more foundational services.</p><div class="installer-list"><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>.2 controlplane</strong><br>Operator runtime and glasspane.</div></div><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>.3 db</strong><br>PostgreSQL + pgvector.</div></div><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>.4 git</strong><br>Local repositories.</div></div><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>.5 cms</strong><br>Astro + Strapi.</div></div><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>.6 ollama</strong><br>Optional local inference.</div></div><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>.7 browser</strong><br>Reserved browser automation / GUI desktop.</div></div></div><div class="installer-footer"><span>Default subnet</span><span>10.0.0.0/24</span></div></div></div></div>
<div class="installer-screen"><div class="installer-panel"><div class="installer-titlebar">08 / 11 — Network Configuration</div><div class="installer-body"><p>Lower IP numbers are reserved for more foundational services.</p><div class="installer-list"><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>.2 reserved</strong><br>Compatibility slot only, no active jail role.</div></div><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>.3 db</strong><br>PostgreSQL + pgvector.</div></div><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>.4 git</strong><br>Local repositories.</div></div><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>.5 cms</strong><br>Astro + Strapi.</div></div><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>.6 ollama</strong><br>Optional local inference.</div></div><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>.150 browser</strong><br>Reserved browser automation / GUI desktop.</div></div></div><div class="installer-footer"><span>Default subnet</span><span>10.0.0.0/24</span></div></div></div></div>
<div class="installer-screen"><div class="installer-panel"><div class="installer-titlebar">09 / 11 — Identity and Provider</div><div class="installer-body"><p>Set the assistant name, choose the provider, and add the matching API key before Telegram and protected-path credentials.</p><div class="installer-list"><div class="installer-option"><span class="installer-pointer"></span><div><strong>Assistant identity</strong><br>Default name is <span class="installer-inline">Clawdie</span>.</div></div><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>AI provider</strong><br>z.ai, OpenRouter, OpenAI, Anthropic, or Claude subscription.</div></div><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>API key</strong><br>Stored in <span class="installer-inline">.env</span>, never in source code.</div></div></div></div></div></div>
<div class="installer-screen"><div class="installer-panel"><div class="installer-titlebar">10 / 11 — Telegram and Protected Paths</div><div class="installer-body"><p>Finish the operator-facing services:</p><div class="installer-list"><div class="installer-option"><span class="installer-pointer"></span><div><strong>Telegram bot</strong><br>Bot token from <span class="installer-inline">@BotFather</span> goes into <span class="installer-inline">.env</span>.</div></div><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>Protected paths</strong><br>Generate or set the password for <span class="installer-inline">/screenshots/</span> and similar endpoints.</div></div></div><div class="installer-footer"><span>Secrets</span><span>Stored outside git</span></div></div></div></div>
<div class="installer-screen"><div class="installer-panel"><div class="installer-titlebar">11 / 11 — Summary and Install</div><div class="installer-body"><p>Review the final configuration, write <span class="installer-inline">.env</span>, then hand over to the setup steps that create jails, install packages, and configure services.</p><div class="installer-list"><div class="installer-option"><span class="installer-pointer"></span><div><strong>Summary</strong><br>Operator user, SSH bootstrap, profile, code hosting, service toggles, network, and provider.</div></div><div class="installer-option"><span class="installer-pointer">&nbsp;</span><div><strong>Installation</strong><br>Environment, network, jail, mounts, register, service, verify.</div></div></div><div class="installer-footer"><span>Controls</span><span>Dots = jump • 9s auto-cycle</span></div></div></div></div>
@ -377,7 +377,7 @@ npm run wizard</code></pre>
</div>
<div class="ip-row">
<span class="ip-addr">.2</span>
<span class="ip-desc"><strong>controlplane</strong> &mdash; AI agent runtime, glasspane console, Telegram interface</span>
<span class="ip-desc"><strong>reserved</strong> &mdash; legacy compatibility slot, not an active current-main jail role</span>
</div>
<div class="ip-row">
<span class="ip-addr">.3</span>
@ -396,21 +396,13 @@ npm run wizard</code></pre>
<span class="ip-desc"><strong>ollama</strong> &mdash; Local LLM inference (optional)</span>
</div>
<div class="ip-row">
<span class="ip-addr">.7</span>
<span class="ip-addr">.101+</span>
<span class="ip-desc"><strong>workers</strong> &mdash; Jailed agent execution starts in the high range to avoid colliding with foundational services</span>
</div>
<div class="ip-row">
<span class="ip-addr">.150</span>
<span class="ip-desc"><strong>browser</strong> &mdash; Reserved for Playwright/CDP</span>
</div>
<div class="ip-row">
<span class="ip-addr">.8</span>
<span class="ip-desc"><strong>cnc</strong> &mdash; TODO: future CNC operations jail</span>
</div>
<div class="ip-row">
<span class="ip-addr">.9</span>
<span class="ip-desc"><strong>monitor</strong> &mdash; TODO: future observability jail</span>
</div>
<div class="ip-row">
<span class="ip-addr">.10</span>
<span class="ip-desc"><strong>runner</strong> &mdash; TODO: future background jobs jail</span>
</div>
</div>
<div class="info-box">
@ -440,27 +432,31 @@ npm run wizard</code></pre>
<div class="screenshot-card">
<img src="/img/install-01-credentials.png" alt="Password generation and .env configuration">
<div class="screenshot-caption">
<h4>Step 1 &mdash; Credential Generation</h4>
<p>
Three database passwords generated with Python <code>secrets</code>,
appended to <code>.env</code>. Passwords are visible once in the screenshot,
then read from environment variables in all subsequent steps.
</p>
</div>
</div>
<h4>Step 1 &mdash; Credential Generation</h4>
<p>
Historical screenshot from the earlier prototype. Current
<code>main</code> generates or preserves the split-brain database
secrets in <code>.env</code> and rewrites the derived
<code>SKILLS_DB_URL</code> and <code>MEMORY_DB_URL</code> values
from the chosen <code>AGENT_NAME</code>, subnet, and passwords.
</p>
</div>
</div>
<div class="screenshot-card">
<img src="/img/install-02-database.png" alt="PostgreSQL role and database creation">
<div class="screenshot-caption">
<h4>Step 2 &mdash; Database Setup</h4>
<p>
PostgreSQL roles created inside the <code>db</code> jail via <code>jexec</code>:
<code>postgres</code> admin, <code>clawdie_app</code>, and <code>strapi_user</code>.
Passwords sourced from <code>.env</code> &mdash; no hardcoding.
Verified with <code>\du</code> and <code>\l</code> showing 3 roles and 2 databases.
</p>
</div>
</div>
<h4>Step 2 &mdash; Database Setup</h4>
<p>
Historical screenshot from the earlier prototype. Current
<code>main</code> provisions a dedicated split-brain
<code>{agent}-db</code> jail, creates separate skills and memory
roles/databases, sets passwords from <code>.env</code>, binds
PostgreSQL to the jail IP, and verifies host-side connectivity to
both required databases before the step succeeds.
</p>
</div>
</div>
<div class="screenshot-card">
<img src="/img/install-03-zfs-snapshots.png" alt="ZFS snapshots for rollback safety">
@ -526,25 +522,27 @@ npm run wizard</code></pre>
Each can be invoked independently:
</p>
<pre><code><span style="color:var(--grey)"># Full sequence after wizard</span>
npm run setup --step environment <span style="color:var(--grey)"># Detect platform, check prereqs</span>
npm run setup --step network <span style="color:var(--grey)"># Write IP configuration to .env</span>
npm run setup --step jail <span style="color:var(--grey)"># Create Bastille jails on ZFS</span>
npm run setup --step groups <span style="color:var(--grey)"># Sync Telegram groups to DB</span>
npm run setup --step register <span style="color:var(--grey)"># Register agent-controlled channels</span>
npm run setup --step mounts <span style="color:var(--grey)"># Configure nullfs mounts for jail</span>
npm run setup --step telegram-auth <span style="color:var(--grey)"># Authenticate with Telegram bot</span>
npm run setup --step service <span style="color:var(--grey)"># Create rc.d service file</span>
npm run setup --step verify <span style="color:var(--grey)"># Final health check</span></code></pre>
<pre><code><span style="color:var(--grey)"># Full sequence after wizard</span>
npm run setup -- --step environment <span style="color:var(--grey)"># Detect platform and prerequisites</span>
npm run setup -- --step pi-config <span style="color:var(--grey)"># Validate pi/provider configuration</span>
npm run setup -- --step jails --create <span style="color:var(--grey)"># Provision the default worker jail</span>
npm run setup -- --step db <span style="color:var(--grey)"># Provision the mandatory DB jail and databases</span>
npm run setup -- --step git <span style="color:var(--grey)"># Provision the default local git jail and bare repository mirror</span>
npm run setup -- --step cms <span style="color:var(--grey)"># Provision the cms jail, nginx, Astro, and internal Strapi seed baseline</span>
npm run setup -- --step hosts <span style="color:var(--grey)"># Write the managed home.arpa hosts block for host and jails</span>
npm run setup -- --step mounts <span style="color:var(--grey)"># Validate and configure allowed mounts</span>
npm run setup -- --step telegram-auth <span style="color:var(--grey)"># Verify Telegram bot token</span>
npm run setup -- --step service <span style="color:var(--grey)"># Create rc.d service file</span>
npm run setup -- --step verify <span style="color:var(--grey)"># Final health check</span></code></pre>
<div class="info-box success">
<span class="info-label">Jail creation</span>
<p>
The <code>jail</code> step runs Bastille with thin jails (<code>-T</code>)
and ZFS backing (<code>-B</code>). Jails are created at
<code>/usr/local/bastille/jails/controlplane/</code>, each with a static IP
on the <code>warden0</code> bridge, a hostname like
<code>controlplane.agent.local</code>, and FreeBSD 15.0-RELEASE as the base.
The current <code>jails</code> step provisions the default
<code>{AGENT_NAME}-worker</code> profile from <code>src/jail-config.ts</code>.
The bridge name is <code>warden0</code>; jail naming and hostnames derive
directly from <code>AGENT_NAME</code> and the internal <code>home.arpa</code>
zone.
</p>
</div>
</section>
@ -559,7 +557,7 @@ npm run setup --step verify <span style="color:var(--grey)"># Final hea
<div class="step-num">&triangle;</div>
<div class="step-content">
<h3>Verify Jails</h3>
<p><code>bastille list</code> should show controlplane and db running. Jails live at <code>/usr/local/bastille/jails/</code>. Access with <code>bastille console controlplane</code>.</p>
<p><code>bastille list</code> should show your derived worker jail, typically <code>{agent}-worker</code>. Jails live at <code>/usr/local/bastille/jails/</code>. Access with <code>bastille console &lt;agent&gt;-worker</code>.</p>
</div>
</div>
<div class="step-card">
@ -573,14 +571,14 @@ npm run setup --step verify <span style="color:var(--grey)"># Final hea
<div class="step-num">&triangle;</div>
<div class="step-content">
<h3>Glasspane Console</h3>
<p>Attach with <code>tmux attach -t clawdie</code> to see the 3-pane console: gateway, shell, and btop.</p>
<p>Use <code>npm run doctor</code> after service start to check the host, pipeline, most recent jail run timestamps, and split-brain DB/artifact readiness.</p>
</div>
</div>
<div class="step-card">
<div class="step-num">&triangle;</div>
<div class="step-content">
<h3>Configure nginx</h3>
<p>Set up vhosts for your domain. Let's Encrypt certificates auto-renew via <code>acme.sh</code>.</p>
<h3>Verify PostgreSQL</h3>
<p>The current setup flow expects the dedicated <code>{agent}-db</code> jail. Confirm that <code>SKILLS_DB_URL</code> and <code>MEMORY_DB_URL</code> point at your configured database IP before you start the service for real use.</p>
</div>
</div>
</div>
@ -593,12 +591,13 @@ npm run setup --step verify <span style="color:var(--grey)"># Final hea
<div class="info-box warning">
<span class="info-label">Common issues</span>
<p><strong>Gateway missing:</strong> Jails created without default gateway. Fix with <code>bastille cmd controlplane route add default 10.0.0.1</code></p>
<p><strong>PostgreSQL SYSVIPC:</strong> Database jail needs <code>allow.sysvipc=1</code> in <code>/usr/local/bastille/jails/db/jail.conf</code>. Restart jail after change.</p>
<p><strong>Sharp/image errors:</strong> Astro builds fail on FreeBSD due to native deps. Install <code>vips</code> inside CMS jail: <code>bastille pkg cms install graphics/vips</code></p>
<p><strong>Wrong setup syntax:</strong> Use <code>npm run setup -- --step ...</code>, not <code>npm run setup --step ...</code>.</p>
<p><strong>Worker jail missing:</strong> The default <code>jails</code> step is a status check. Add <code>--create</code> when you actually want Bastille to provision <code>{agent}-worker</code>.</p>
<p><strong>Gateway missing:</strong> If networking is incomplete inside the worker jail, verify the <code>warden0</code> bridge and rerun <code>npm run setup -- --step jails --create</code>.</p>
<p><strong>Astro image pipeline:</strong> For the first FreeBSD deployment, avoid <code>sharp</code> entirely. Keep images in <code>public/</code> and use the minimal Astro template. Only if you later adopt the heavier Starlight/prototype path should you consider adding <code>graphics/vips</code> to the <code>cms</code> jail.</p>
<p><strong>pf blocking:</strong> Ensure jail subnet (<code>10.0.0.0/24</code>) is in <code>nat</code> rules. Check with <code>pfctl -s nat</code>.</p>
<p><strong>Passwords lost:</strong> All secrets live in <code>.env</code> (gitignored). If lost, regenerate with <code>python3 -c "import secrets; print(secrets.token_urlsafe(32))"</code> and update PostgreSQL roles.</p>
<p><strong>Jail not starting:</strong> Check <code>/usr/local/bastille/jails/controlplane/</code> exists. If not, re-run <code>npm run setup --step jail</code>.</p>
<p><strong>Jail not starting:</strong> Check that <code>/usr/local/bastille/jails/&lt;agent&gt;-worker/</code> exists. If not, re-run <code>npm run setup -- --step jails --create</code>.</p>
</div>
</section>

View file

@ -31,23 +31,24 @@
<div class="page-header">
<h1>Split <span>Brain</span></h1>
<p class="subtitle">Built-in local knowledge first, dynamic user memory later</p>
<p class="subtitle">Agent System Skills first, User-Agent Memory kept separate</p>
</div>
<p>
Clawdie is moving toward a split-brain memory model. The goal is to make
first install useful before any production LLM provider is configured, while
keeping user memory flexible and separate.
Clawdie uses a split-brain runtime model so first install is useful
before any production provider is configured, while user memory stays
flexible and separate from built-in operating knowledge.
</p>
<div class="divider"></div>
<section>
<h2>Brain A</h2>
<h2>Agent System Skills</h2>
<p>
Brain A is the built-in local knowledge that ships with Clawdie:
install guides, operator docs, and product skills. It is prepared before
user install and imported into the <code>db</code> jail during bootstrap.
Agent System Skills are the built-in local knowledge that ships with
Clawdie: install guides, operator docs, product skills, and curated
support content. They are prepared before user install and imported
into the <code>db</code> jail during bootstrap.
</p>
<ul>
<li>No provider keys required for first install</li>
@ -59,10 +60,11 @@
<div class="divider"></div>
<section>
<h2>Brain B</h2>
<h2>User-Agent Memory</h2>
<p>
Brain B is the dynamic side: user preferences, operator notes, and future
agent-specific memories such as <code>mevy</code> or <code>bob</code>.
User-Agent Memory is the dynamic side: user preferences, operator
notes, and future agent-specific memories such as
<code>mevy</code> or <code>bob</code>.
</p>
<ul>
<li>Separate lifecycle from built-in knowledge</li>
@ -76,18 +78,18 @@
<section>
<h2>Why it matters</h2>
<p>
Most AI products ask for provider keys too early. Clawdie is moving
in the opposite direction: import built-in local knowledge first, then
add live provider configuration when the operator is ready.
Most AI products ask for provider keys too early. Clawdie goes in
the opposite direction: import built-in operating knowledge first,
then add live provider configuration when the operator is ready.
</p>
<pre><code>bootstrap system
-> import Brain A into db jail
-> import Agent System Skills into db jail
-> local built-in knowledge is ready
-> add production LLM keys later</code></pre>
<p>
The bootstrap step that imports Brain A lives in
The bootstrap step that imports Agent System Skills lives in
<a href="https://codeberg.org/Clawdie/Clawdie-AI/src/branch/main/setup/skills-memory.ts" target="_blank" rel="noopener"><code>setup/skills-memory.ts</code></a>.
</p>
</section>

View file

@ -35,11 +35,13 @@
</div>
<p>
Clawdie is built on <a href="https://codeberg.org/NanoClaw/NanoClaw" target="_blank" rel="noopener">NanoClaw</a>
— the open source personal AI assistant framework by
<a href="https://github.com/steipete" target="_blank" rel="noopener">Peter Steinberger</a>,
adapted for FreeBSD. The upstream toggle lets you see what new commits are
available in the NanoClaw project and decide what to apply to your installation.
<a href="https://codeberg.org/NanoClaw/NanoClaw" target="_blank" rel="noopener">NanoClaw</a>
is an upstream project by Gavriel in the broader OpenClaw line inspired by
<a href="https://github.com/steipete" target="_blank" rel="noopener">Peter Steinberger's</a>
<a href="https://github.com/openclaw/openclaw" target="_blank" rel="noopener">OpenClaw</a>.
Clawdie is the FreeBSD-first fork in that lineage. The upstream toggle lets
you see what new commits are available in NanoClaw and decide what to
apply to your installation.
</p>
<div class="info-box">
@ -61,6 +63,16 @@
</p>
</div>
<div class="info-box">
<span class="info-label">Built on giants' shoulders</span>
<p>
OpenClaw set the broader direction. NanoClaw carried that line into a
lean upstream personal assistant. Clawdie takes the same line into
FreeBSD and the wider <a href="https://osa.smilepowered.org" target="_blank" rel="noopener">OSA</a>
stack.
</p>
</div>
<div class="divider"></div>
<section>

View file

@ -707,12 +707,13 @@
No subscription. No data leaving your server.
</p>
<p>
Built on <strong>NanoClaw</strong> — the open source personal AI assistant
framework originally conceived by
<a href="https://github.com/steipete" target="_blank" rel="noopener">Peter Steinberger</a>
— adapted for FreeBSD with native jail isolation, ZFS snapshots,
and a pi-driven setup flow. We follow NanoClaw upstream closely
and contribute FreeBSD-specific work back.
Built on giants' shoulders: <a href="https://github.com/steipete" target="_blank" rel="noopener">Peter Steinberger</a>'s
<strong>OpenClaw</strong> set the broader direction, <strong>NanoClaw</strong>
by Gavriel carried that line into a lean personal-assistant upstream,
and Clawdie takes it onto FreeBSD with native jail isolation, ZFS
snapshots, and a pi-driven setup flow. The same stack ties into the
wider <a href="https://osa.smilepowered.org" target="_blank" rel="noopener">OSA</a>
mission.
</p>
<p>
The FreeBSD deployment path is actively being built and proven in

View file

@ -47,12 +47,14 @@
<h2>Why BSD-3 over MIT</h2>
<p>
Our upstream,
Our MIT-licensed upstream base comes from
<a href="https://codeberg.org/NanoClaw/NanoClaw" target="_blank" rel="noopener">NanoClaw</a>
by <a href="https://github.com/steipete" target="_blank" rel="noopener">Peter Steinberger</a>,
is MIT licensed. BSD-3-Clause is fully compatible with MIT — you can use
MIT code in a BSD-3 project without restriction.
We chose BSD-3 because it adds one thing MIT does not:
by Gavriel. NanoClaw itself sits in the broader
<a href="https://github.com/openclaw/openclaw" target="_blank" rel="noopener">OpenClaw</a>
line inspired by <a href="https://github.com/steipete" target="_blank" rel="noopener">Peter Steinberger</a>.
BSD-3-Clause is fully compatible with MIT — you can use MIT code in a
BSD-3 project without restriction. We chose BSD-3 because it adds one thing
MIT does not:
</p>
<div class="info-box">
@ -196,8 +198,8 @@
<td>BSD-2-Clause · BSD-3-Clause</td>
</tr>
<tr>
<td>NanoClaw</td>
<td>Peter Steinberger, 2024</td>
<td>NanoClaw upstream</td>
<td>Gavriel, 2024</td>
<td>MIT — preserved in our LICENSE file</td>
</tr>
<tr>
@ -261,7 +263,7 @@
<pre><code>BSD 3-Clause License
Copyright (c) 2026, Sam (Samo Blatnik) and Clawdie contributors
Built on NanoClaw — Copyright (c) 2024 Peter Steinberger (MIT License)
Built on NanoClaw — Copyright (c) 2024 Gavriel (MIT License)
All rights reserved.
Redistribution and use in source and binary forms, with or without

View file

@ -1,7 +1,7 @@
# docs.clawdie.si Deployment
**Status:** FreeBSD-first site assets ready
**Date:** 13.mar.2026
**Status:** Transitional static bridge deploy
**Date:** 14.mar.2026
## Purpose
@ -11,8 +11,8 @@ The site explains:
- Clawdie on FreeBSD
- the split-brain model
- Brain A built-in local knowledge
- Brain B user and future-agent memory
- Agent System Skills built-in local knowledge
- User/Agent Memory for user and future-agent memory
- the relationship to NanoClaw upstream
## Layout
@ -29,7 +29,20 @@ html/docs-clawdie-si/
└── docs.clawdie.si.conf
```
## FreeBSD deployment shape
## Design direction
This checked-in HTML deploy path is a bridge pattern, not the long-term target
architecture.
The target deployment model is:
- Astro + Strapi live in the `cms` jail
- nginx also lives in the `cms` jail
- Clawdie does not require host nginx ownership
- public exposure can happen through host PF, an existing reverse proxy, or a
direct public jail IP
## Current bridge deployment shape
Suggested host paths:
@ -41,7 +54,7 @@ Host prerequisite:
- `rsync` installed (`pkg install rsync`)
## Preferred deploy path
## Preferred bridge deploy path
Run:
@ -57,7 +70,7 @@ The script:
4. runs `nginx -t`
5. reloads nginx
## Manual deploy steps
## Manual bridge deploy steps
1. Copy only public site files to `/usr/local/www/docs.clawdie.si`
2. Install `nginx/docs.clawdie.si.conf` into `/usr/local/etc/nginx/vhosts/`
@ -68,6 +81,7 @@ The script:
## Notes
- This is a static site, not a reverse proxy app.
- It is intended to be the public FreeBSD-facing documentation surface.
- It is intended to keep the docs live while the Astro + `cms` jail refactor is
completed.
- It complements the main `html/clawdie/` site rather than replacing it.
- The homepage badge shows latest repository activity from Codeberg, not the live deploy timestamp.

View file

@ -75,11 +75,13 @@
</div>
<p>
Clawdie is built on <a href="https://codeberg.org/NanoClaw/NanoClaw" target="_blank" rel="noopener">NanoClaw</a>
&mdash; the open source personal AI assistant framework by
<a href="https://github.com/steipete" target="_blank" rel="noopener">Peter Steinberger</a>,
adapted for FreeBSD. The upstream toggle lets you see what new commits are
available in the NanoClaw project and decide what to apply to your installation.
<a href="https://codeberg.org/NanoClaw/NanoClaw" target="_blank" rel="noopener">NanoClaw</a>
is an upstream project by Gavriel in the broader OpenClaw line inspired by
<a href="https://github.com/steipete" target="_blank" rel="noopener">Peter Steinberger's</a>
<a href="https://github.com/openclaw/openclaw" target="_blank" rel="noopener">OpenClaw</a>.
Clawdie is the FreeBSD-first fork in that lineage. The upstream toggle lets
you see what new commits are available in NanoClaw and decide what to
apply to your installation.
</p>
<div class="info-box">
@ -101,6 +103,16 @@
</p>
</div>
<div class="info-box">
<span class="info-label">Built on giants' shoulders</span>
<p>
OpenClaw set the broader direction. NanoClaw carried that line into a
lean upstream personal assistant. Clawdie takes the same line into
FreeBSD and the wider <a href="https://osa.smilepowered.org" target="_blank" rel="noopener">OSA</a>
stack.
</p>
</div>
<div class="divider"></div>
<section>

View file

@ -88,6 +88,37 @@
<div class="divider"></div>
<section>
<h2>Runtime flow</h2>
<pre><code>onboarding
-&gt; configure Stripe now? (or skip)
-&gt; write STRIPE_SECRET_KEY into .env
host jail-runner
-&gt; reads STRIPE_SECRET_KEY from .env
-&gt; passes it to jailed runtime in stdin JSON secrets payload
jailed agent runner
-&gt; merges secret into SDK env only
-&gt; registers Stripe MCP tools
agent chat
-&gt; can use customer, invoice, balance, subscription, and payment-link tools</code></pre>
<div class="info-box">
<span class="info-label">Secret exposure boundary</span>
<p>
The Stripe key is entered during onboarding or added later to
<code>.env</code>. The host reads it, passes it to the jailed runtime
in the stdin JSON payload, and the jailed agent runner exposes it only
to the Stripe SDK/tool runtime. It is not mounted as a file into the jail.
</p>
</div>
</section>
<div class="divider"></div>
<section>
<h2>Setup</h2>

View file

@ -86,12 +86,14 @@
<h2>Why BSD-3 over MIT</h2>
<p>
Our upstream,
Our MIT-licensed upstream base comes from
<a href="https://codeberg.org/NanoClaw/NanoClaw" target="_blank" rel="noopener">NanoClaw</a>
by <a href="https://github.com/steipete" target="_blank" rel="noopener">Peter Steinberger</a>,
is MIT licensed. BSD-3-Clause is fully compatible with MIT &mdash; you can use
MIT code in a BSD-3 project without restriction.
We chose BSD-3 because it adds one thing MIT does not:
by Gavriel. NanoClaw itself sits in the broader
<a href="https://github.com/openclaw/openclaw" target="_blank" rel="noopener">OpenClaw</a>
line inspired by <a href="https://github.com/steipete" target="_blank" rel="noopener">Peter Steinberger</a>.
BSD-3-Clause is fully compatible with MIT &mdash; you can use MIT code in a
BSD-3 project without restriction. We chose BSD-3 because it adds one thing
MIT does not:
</p>
<div class="info-box">
@ -235,8 +237,8 @@
<td>BSD-2-Clause &middot; BSD-3-Clause</td>
</tr>
<tr>
<td>NanoClaw</td>
<td>Peter Steinberger, 2024</td>
<td>NanoClaw upstream</td>
<td>Gavriel, 2024</td>
<td>MIT &mdash; preserved in our LICENSE file</td>
</tr>
<tr>
@ -300,7 +302,7 @@
<pre><code>BSD 3-Clause License
Copyright (c) 2026, Sam (Samo Blatnik) and Clawdie contributors
Built on NanoClaw — Copyright (c) 2024 Peter Steinberger (MIT License)
Built on NanoClaw — Copyright (c) 2024 Gavriel (MIT License)
All rights reserved.
Redistribution and use in source and binary forms, with or without