arizukocomponents › dashd

dashd

What it is

dashd is the operator dashboard: an HTMX server that renders pages over messages.db and allow-listed memory files. Reads are the default; the only write paths are PUT and DELETE on a fixed list of markdown files 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 but does not scale to non-engineers and does not give a one-screen answer to “what is this instance doing right now”.

dashd owns no tables of its own. It is an aggregator: it reads from groups, routes, scheduled_tasks, messages, sessions, channels, and auth_users — tables owned by gated, timed, and onbod — and renders them. The plan in specs/5/5-uniform-mcp-rest.md is to migrate those direct reads onto the sibling daemons’ /v1/* APIs; dashd becomes 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. 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. Read views still depend on proxyd alone for the front gate.

Two write surfaces today. Memory editor accepts MEMORY.md, .claude/CLAUDE.md, and flat *.md under diary/, facts/, users/, episodes/; reads capped at 1 MiB; path joiner blocks symlink escape. TIER 1 admin — routes editor, groups CRUD, per-user secrets — gated by the admin check above.

Standalone usage

dashd needs a SQLite file and a port. The TIER 1 write surface refuses requests without a signed admin identity; read views still need proxyd’s front gate to be safe on the public internet.

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. Exposing dashd directly on the public internet skips the auth gate — do not do this.

The page set

Read views (each one HTML the browser renders directly):

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

TIER 1 admin surfaces (admin auth + CSRF):

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:

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 gated, 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