arizuko

arizukocomponents › 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 the owner daemons’ databases (routd’s, onbod’s and runed’s) 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 each daemon's /v1, 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. Which of those reads go over the owner daemon’s /v1/* API and which stay direct is settled in specs/5/16-mcp-rest-unification.md.

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: routd's + onbod's tables
        |      write: PUT|DELETE /dash/memory/{folder}/{rel}
        v
      filesystem: <groups>/<folder>/MEMORY.md, diary/*.md, ...

dashd reads identity from headers set by proxyd’s requireAuth, and it proves the request really came through proxyd: every /dash/* handler — read views included — runs through guard, which calls auth.ProxydTransit. That checks a service:proxyd ES256 bearer against authd’s keyset. The bearer proves transit and nothing else; the end-user identity is the X-User-Sub and X-User-Groups headers, and dashd trusts those only once the transit proof holds. A request that skips proxyd carries no valid bearer, so it never reaches a handler. Only /health, /openapi.json, and the htmx asset stay public. Write verbs go further: every admin 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. Admin writes — routes editor, groups CRUD, per-user secrets — sit behind the admin check above.

Standalone usage

dashd needs a port and a way to reach routd's tables — the store endpoint the instance is served from, or that owner's file. Point AUTHD_URL at authd and dashd verifies proxyd’s transit bearer against its keyset on every /dash/* request — a request that arrives without one gets redirected to login. Leave authd unwired 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 X-User-* headers dashd trusts.

export DATA_DIR=/srv/data/arizuko_demo
export DASH_PORT=:8080
export INSTANCE_NAME=demo
./dashd
# browse http://localhost:8080/dash/

On a file instance DATA_DIR resolves each owner at $DATA_DIR/store/<owner>/ — dashd reads routd's and onbod's — and the groups tree at $DATA_DIR/groups/; on a served instance the same reads go to the endpoint and no file is opened. Set DB_PATH to override routd's file independently. Pointing it at a retired database is refused rather than served stale. INSTANCE_NAME appears in the portal header.

Health: GET /health returns 200 when the database is reachable. With authd wired, exposing dashd directly still rejects any request that carries no proxyd transit bearer — 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):

HTMX partials (tasks/x/list, activity/x/recent) feed live sub-views.

Admin pages (admin auth + CSRF):

The migration to /v1/*

Most pages still read routd's tables directly. The plan is to swap each read for a call to the sibling daemon that owns the table. /dash/proxyd/ already works this way and is the shape the rest are heading for:

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 opening the owner databases at all. The audit page already works this way — it fans out to routd, runed and authd over HTTP rather than reading their files.

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 the admin pages. Everything else is a view.

Go deeper