arizuko › components › dashd
dashd
What it is
In plain terms, dashd is the control panel for whoever runs the instance. Open it in a browser to see groups, messages, and tasks, and to edit a few of the agent’s files.
dashd is the operator dashboard: an HTMX server that renders pages over messages.db and a fixed set of markdown files. Reads are the default; the only write paths are PUT and DELETE on that markdown list under each group’s folder.
Routes live at /dash/*. The portal at /dash/ links to status, tasks, activity, groups, memory, and profile pages, each rendered server-side as one HTML page plus HTMX partials for live sub-views.
Why it exists
Without dashd, operators inspect an arizuko instance through sqlite3 messages.db, journalctl -u arizuko_<inst>, and the arizuko CLI. That works for an engineer, but it asks a lot of anyone else, and it never gives a one-screen answer to “what is this instance doing right now”.
dashd owns no tables of its own. It reads from tables other daemons own — groups, routes, scheduled_tasks, messages, sessions, channels, auth_users, written by routd, authd, timed, and onbod — and renders them. The plan in specs/5/5-uniform-mcp-rest.md is to move those direct reads onto the sibling daemons’ /v1/* APIs, leaving dashd a pure client.
How it fits
operator browser
|
v /dash/...
|
proxyd requireAuth: JWT or refresh-token cookie
| stamp X-User-Sub, X-User-Groups, HMAC sig
v
dashd render HTML / HTMX partial
| read: messages.db (today)
| write: PUT|DELETE /dash/memory/{folder}/{rel}
v
filesystem: <groups>/<folder>/MEMORY.md, diary/*.md, ...
dashd reads identity from signed headers set by proxyd’s requireAuth, and it checks the signature itself: every /dash/* handler — read views included — runs through guard, which calls auth.RequireSigned(PROXYD_HMAC_SECRET). A request that skips proxyd carries no valid X-User-Sig, so it never reaches a handler. Only /health, /openapi.json, and the htmx asset stay public. Write verbs go further: every TIER 1 mutation runs requireAdmin, which calls auth.Authorize with action admin against the caller’s groups, plus a same-origin CSRF guard.
Two write paths today. The memory editor accepts MEMORY.md, .claude/CLAUDE.md, and flat *.md under diary/, facts/, users/, and episodes/. Reads are capped at 1 MiB, and the path joiner blocks any symlink that escapes the group folder. TIER 1 admin — routes editor, groups CRUD, per-user secrets — sits behind the admin check above.
Standalone usage
dashd needs a SQLite file and a port. Set PROXYD_HMAC_SECRET to the same value proxyd uses and dashd verifies every /dash/* request’s signature itself — an unsigned request gets redirected to login. Leave it unset for local dev and the guard passes through, which is why you still front dashd with proxyd on the public internet: proxyd terminates TLS, runs the login flow, and stamps the signed headers dashd trusts.
export DATA_DIR=/srv/data/arizuko_demo
export DASH_PORT=:8080
export INSTANCE_NAME=demo
./dashd
# browse http://localhost:8080/dash/
DATA_DIR resolves the database at $DATA_DIR/store/messages.db and the groups tree at $DATA_DIR/groups/. Set DB_PATH to override the database location independently. INSTANCE_NAME appears in the portal header.
Health: GET /health returns 200 when the database is reachable. With PROXYD_HMAC_SECRET set, exposing dashd directly still rejects unsigned requests — but proxyd owns TLS and the login flow, so front it with proxyd anyway.
The page set
Read views (each one HTML the browser renders directly):
/dash/— portal index linking the other pages./dash/status/— groups, sessions, channels at a glance./dash/activity/— the last 50 messages across all groups./dash/tasks/— scheduled tasks fromscheduled_tasks./dash/groups/— group folders with their channel routes./dash/memory/— the editable markdown view per group./dash/profile/— the calling user’sauth_usersrow.
HTMX partials (tasks/x/list, activity/x/recent) feed live sub-views.
TIER 1 admin pages (admin auth + CSRF):
/dash/routes/— routes editor: list, create, edit, delete rules./dash/groups/new+/dash/groups/{folder}/settings— group create / settings / delete./dash/me/secrets— per-user secret set, list, delete./dash/channels/whatsapp/pair— self-service WhatsApp re-pair page. Requires the**operator super-grant. Calls whapd/v1/pair/start; the pairing code is returned once, expires in 60s, rate-limited to 5/hr. Every start writes an audit row inmessages.
The migration to /v1/*
Today every page queries SQLite directly. The plan is to swap each read for a call to the sibling daemon that owns the table:
/dash/groups/→routd/v1/groups+routd/v1/routes./dash/tasks/→timed/v1/tasks(with a form POST for create)./dash/activity/→routd/v1/messages?limit=50&order=desc./dash/profile/→onbod/v1/users/{sub}.
After the migration dashd holds an operator session token (issued by proxyd at OAuth login), forwards it as Authorization: Bearer ... on every /v1/* call, and stops touching messages.db at all.
What dashd does not do
dashd does not send messages, schedule tasks, mint tokens, or admit users — those belong to routd, timed, proxyd, and onbod. Writes are scoped to the surfaces listed above: memory editor and TIER 1 admin. Everything else is a view.
Go deeper
- reference/env — dashd — full env var table.
- components/proxyd — the auth gate that sits in front.
specs/5/5-uniform-mcp-rest.md— the/v1/*migration plan.specs/4/Q-dash-memory.md— memory view and edit surface.