Dorian de353878f6 feat(api): nostr nip-98 login, jwt sessions, datum digest poller
Backend in @gashboard/api (Express 5 + TS, ~1.2k LoC):

Auth (NIP-98 over HTTP, lifted from indeehub pattern):
  - Client signs a kind-27235 event with method+URL, base64s as
    Authorization: Nostr <event>. Server verifies sig, freshness
    (±120s), method/URL tags via constant-time string compare.
  - npub allowlist decoded to hex once at boot, fail-closed if any
    entry is malformed or list is empty.
  - HS256 JWT sessions returning {token, npub, expiresAt}.
  - express-rate-limit on POST /api/auth/login (10/min/IP).

Datum integration (the trickier half):
  - HTTP Digest *SHA-256* client (community-fork Datum uses sha-256,
    not md5; node has no first-class support — hand-rolled in
    digest.ts: parse challenge → ha1=sha256(user:realm:pw),
    ha2=sha256(method:URI), response=sha256(...) → retry).
  - HTML parsers for /clients (per-worker) and /threads (auth-less
    fallback) using node-html-parser.
  - Profile matcher: UserAgent contains "NerdQAxe" → NerdQAxe;
    else worker-name suffix on auth username → workerNameMatchers.
    Live UA strings observed: NerdQAxe self-IDs; Bitaxe / Avalon
    Nano 3 / Avalon Mini 3 all report cgminer/4.11.1, must match
    via workername.
  - 5s poll interval, 10s AbortController timeout per upstream call,
    in-memory snapshot, /api/datum/stats + SSE /api/datum/stream.

Hardened-by-default Express setup:
  helmet CSP (frame-ancestors 'none', script-src 'self'),
  pino with redaction (auth header, *.password, *.token, *.jwt,
  *.sig), AppError class + central errorHandler, zod env validation,
  graceful shutdown on SIGTERM/SIGINT.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 15:58:35 +01:00

gashboard

a custom dashboard for the datum gateway running on umbrel. for the four little boards that probably won't find a block, but are trying their best.

Polls a local Datum gateway (OCEAN's open-source mining gateway), shows live per-miner hashrate, share counts, lottery odds, and a tongue-in-cheek read on how unlikely it all is. Designed for a small fleet of solo-style hobby miners (Bitaxe, NerdQAxe, Avalon Nano 3, Avalon Mini 3) hashing into one Datum on Umbrel.

What it shows

  • Pool hero — combined hashrate, current block height being templated, mempool fee snapshot, datum connection status.
  • Per-miner cards — nickname, ASIC type, location, live hashrate, accepted/rejected shares, last-share age, status light, firmware string from useragent.
  • Lottery widget — P(block in 24h) at current network difficulty, with rotating self-deprecating commentary.
  • Live share ticker — sparkline + scrolling feed of recent accepted shares.
  • Sats-today counter — earnings projection from current hashrate × network reward.
  • Map panel — pins for each location (split by remote IP from Datum's /clients), pulse-per-share.
  • Block celebration — confetti + sound the day it finally happens.

Auth

Nostr-only login (NIP-98 over HTTP). Two signers supported:

  • Remote signer (NIP-46) — covers Primal app, Amber, nsecbunker.
  • Browser extension (NIP-07) — Alby, nos2x, etc.

Allowlist of npubs is set via NOSTR_ALLOWED_NPUBS. Anything else is rejected before a session token is issued.

Stack

  • Frontend — Vue 3 + Vite + Pinia + TypeScript
  • Backend — Express + TypeScript
  • Authnostr-tools (NIP-98 verify), applesauce-* (signer abstraction, lifted from indeehub)
  • Container — multi-stage node:22.12.0-alpine (pinned by SHA256 in Dockerfile)

Deploy on Portainer (the way it'll actually run)

  1. Enable Datum admin API on your Umbrel — edit ~/umbrel/app-data/datum-gateway/data/datum_gateway/datum_gateway_config.json and add "admin_password": "<openssl rand -hex 32>" inside the "api" block. Restart the Datum app.

  2. Push this repo to your gitea/whatever:

    git push -u origin main
    
  3. In Portainer → Stacks → Add stack → Repository:

    • Repository URL: https://git.tx1138.com/lfg2025/gashboard
    • Compose path: docker-compose.yml
    • Add env vars (see .env.example)
    • Set the external network in the compose file to match your Datum app's docker network (find with docker network ls on the Umbrel)
  4. Open the dashboard, log in with one of the allowed npubs, watch your boards lose at hashing in style.

Local dev

pnpm install
pnpm dev    # runs api + web concurrently

License

MIT.

Description
No description provided
Readme 519 KiB
Languages
TypeScript 52.6%
Vue 43.3%
CSS 1.8%
JavaScript 1.2%
Dockerfile 0.7%
Other 0.4%