Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The sithbit-console admin TUI

sithbit-console is the terminal management console for a SithBit deployment: a keyboard-driven UI that lets an operator inspect accounts, mailboxes, and messages (with their on-chain delivery states), watch the job queues, and requeue or discard dead-lettered jobs — without ever touching the store or the chain state that the mail servers own.

Its defining design constraint: the console talks only to the account API’s /v1/admin routes. Every read and every action round-trips through the API, never the database directly. That keeps the operator surface behind the same authentication and admin-wallet allowlist the API already enforces, works identically against every storage backend (SQLite or any of the cloud stores), and means the console can run from any machine that can reach the API — it needs no store credentials at all. The one deliberate exception is the read-only balances pane, which reads public chain state through the mail-grpc gateway and a Solana RPC node.

Prerequisites

  1. A running account API (standalone account-api, or the one inside your sithbitd deployment) reachable from the console’s machine.
  2. Your wallet on the API’s admin allowlist. The console logs in with a Solana wallet keypair; the API only serves /v1/admin routes to wallets listed in its admin_wallets setting. An empty admin_wallets list disables the admin surface entirely — every admin call (and therefore every console pane) replies 403.
  3. The wallet’s keypair file on the console machine (a standard Solana 64-byte JSON keypair; default ~/.config/solana/id.json).
  4. Optionally, for the balances pane: a reachable mail-grpc gateway and a Solana JSON-RPC node. A console used only for the admin panes can ignore both.

Running it

cargo run -p mail-console --bin sithbit-console

With no configuration at all, the console targets the loopback dev stack: the account API at http://127.0.0.1:8180, the mail-grpc gateway at http://127.0.0.1:50051, and a local RPC node at http://127.0.0.1:8899, signing in with ~/.config/solana/id.json.

On startup the console:

  1. loads its configuration (next section),
  2. loads the keypair and performs the wallet-challenge login (/v1/auth/nonce → signature → /v1/auth/token → JWT) — a failure here (API down, malformed keypair) exits with the error before any UI appears,
  3. connects the balance client (the gateway channel dials lazily, so a down gateway does not block startup — only a syntactically bad endpoint URL does), and
  4. opens the terminal UI on the Accounts tab, loading the account list.

Note that the login succeeding does not yet prove the wallet is an admin: the allowlist is checked per admin route, so a non-admin wallet gets a working UI whose every pane reports a 403 error in the status bar. Fix the API’s admin_wallets and press r.

Configuration

Config file sithbit_console.toml in the working directory (or the path in SITHBIT_CONSOLE_CONFIG), env prefix SITHBIT_CONSOLE, resolved through the standard layering (in-code default → TOML → .env.env.$APP_ENV → environment). Every entry defaults, so an empty or absent file runs against the loopback dev stack:

# api_url = "http://127.0.0.1:8180"
# keypair_file = "~/.config/solana/id.json"
# gateway_endpoint = "http://127.0.0.1:50051"
# rpc_url = "http://127.0.0.1:8899"

The four keys are documented in the configuration reference.

The two tabs

The console has two top-level tabs, switched with Tab:

  • Accounts — the wallet list, drilling down through mailboxes to messages, plus the balances pane.
  • Queues — job-queue depths and the dead-letter table with its requeue/discard actions.

A status bar along the bottom shows the last result or error on the left and the active tab’s key hints on the right. All data is fetched on demand; r reloads the pane you are looking at.

Tutorial: inspecting accounts and mail

Accounts

The Accounts tab opens on the wallet list — every account the API knows, one base58 wallet address per row. j/k (or the arrow keys) move the selection; the status bar shows the total (N accounts).

Mailboxes

Press Enter on a wallet to list its mailboxes (INBOX, folders created over IMAP, and so on). Unselectable hierarchy-only entries are marked (noselect). Esc steps back to the wallet list.

Messages

Press Enter on a mailbox to open its message table:

ColumnMeaning
uidThe message’s IMAP UID in this mailbox
sizeStored size in bytes
dateThe message’s internal date (first 19 chars, YYYY-MM-DDTHH:MM:SS)
chainThe delivery-pipeline chain statelocal (dark gray) for a copy with no chain record
cidThe IPFS CID of the sealed body, once pinned (empty until then)

Rows are color-coded by chain state: terminal states draw attention, in-flight ones stay calm. This is the fastest way to answer “did that message actually make it on-chain, and what CID did it get?” for a specific user without querying the store by hand.

The balances pane

Press b on a wallet (in the wallet list, or anywhere deeper in that wallet’s drill-down) to open its on-chain balances:

  • Summary — native SOL (in SOL and lamports), the mailbox’s default stamp price in lamports, and its lifetime mail count.
  • Senders — one row per other loaded wallet: the prepaid stamps it holds toward this mailbox and the per-mail postage it owes (from / stamps / required (lamports)).

This is the console’s only direct-to-chain view: the summary and stamp rows come from the mail-grpc gateway (GetMailbox, GetFrombox) and the SOL balance from the JSON-RPC node — all read-only public chain state, nothing signed. If neither endpoint is configured and reachable, the pane reports an error while every admin pane keeps working.

Esc returns from any drill-down level toward the wallet list.

Tutorial: queues and dead letters

Switch to the Queues tab with Tab. The pane stacks two tables:

  • Queues — one row per job queue (queue / depth / oldest), where oldest is the age of the oldest waiting job in seconds. A depth the backend cannot report cheaply shows as ?.
  • Dead letters — jobs that exhausted their retries (queue / type / attempts / died / reason), where type is the payload’s job type tag.

Two actions work the dead-letter table, both requiring confirmation:

  • urequeue the selected job (put it back on its queue for a fresh attempt, e.g. after fixing the outage that killed it).
  • xdiscard the selected job permanently.

Either key arms the action and the status bar asks requeue <queue> job (<reason>)? y/n — press y to run it, any other key to cancel. On cloud stores a listed dead job stays claimed for about five minutes; requeue/discard echo the claim token back, so if the claim has lapsed (or another operator acted first) the action fails cleanly and a reload (r) shows the current truth.

Key reference

KeyContextAction
j / everywhereMove selection down
k / everywhereMove selection up
EnterAccounts tabDrill down (wallet → mailboxes → messages)
bAccounts tabBalances pane for the selected wallet
EscAccounts tabStep back up one level
TabeverywhereSwitch between the Accounts and Queues tabs
reverywhereReload the current pane
uQueues tabRequeue the selected dead job (asks y/n)
xQueues tabDiscard the selected dead job (asks y/n)
yconfirmationConfirm the armed requeue/discard
qeverywhereQuit

What the console deliberately does not do

  • No store access. It links no storage backend; it cannot see rows the API does not expose. (The /v1/admin surface is the contract — anything you can script against it, the console can show.)
  • No mail content. Message bodies are sealed to their recipients; the console shows metadata and chain state only.
  • No chain writes. The balances pane is read-only; the console signs nothing but its own login challenge.
  • No server management. Starting, stopping, and configuring the daemons stays with your process supervisor and config files.

Troubleshooting

SymptomLikely cause
Exits immediately with logging in to <url>Account API down or unreachable at api_url, or the keypair file is missing/malformed
Every pane shows a 403 errorThe logged-in wallet is not in the API’s admin_wallets allowlist (or the list is empty, which disables the admin surface)
Balances pane errors, admin panes finegateway_endpoint / rpc_url not reachable — expected on an admin-only setup; the pane needs both
Requeue/discard fails after sitting on the paneThe cloud store’s ~5-minute dead-job claim lapsed; press r and act on the fresh listing