Commit Graph

45 Commits

Author SHA1 Message Date
Dorian
7a85e805a7 Identify BigPapa as Bitaxe 2026-05-08 21:51:23 +01:00
Dorian
54fd5a278c Restore Datum current-IP deployment 2026-05-08 11:05:19 +01:00
Dorian
8af87bfb95 Use Umbrel Docker DNS for Datum 2026-05-08 10:59:06 +01:00
Dorian
0a6571a590 Use room key encryption for private chat 2026-05-06 21:32:47 +01:00
Dorian
4dd83b6387 Encrypt private API chat with NIP-44 2026-05-06 21:10:25 +01:00
Dorian
0ff7bc46fc Move chat to private authenticated API 2026-05-06 21:04:50 +01:00
Dorian
cb21c693d0 Make Nostr chat private 2026-05-06 20:56:29 +01:00
Dorian
3b69ed26ed Add chat replies and reactions 2026-05-06 20:46:27 +01:00
Dorian
166fc468b2 Surface image stickers in chat picker 2026-05-06 20:39:33 +01:00
Dorian
de63107420 Add image stickers and fix desktop chat composer 2026-05-06 20:32:37 +01:00
Dorian
d7976c8d14 Prevent chat composer clipping 2026-05-06 20:28:00 +01:00
Dorian
db92857667 Make sticker modal opaque 2026-05-06 20:24:53 +01:00
Dorian
a5bae77f8a Fix mobile tabs and chat sticker modal 2026-05-06 20:23:22 +01:00
Dorian
b23c9006b7 Persist signer sessions and expand chat stickers 2026-05-06 20:19:27 +01:00
Dorian
7b7685f0e8 Tidy chat drawer composer layout 2026-05-06 20:12:30 +01:00
Dorian
e74f33f971 Fix chat signer state and add stickers 2026-05-06 20:05:08 +01:00
Dorian
1e74719932 Add release notes and tidy mobile UI 2026-05-06 19:58:03 +01:00
Dorian
b715c3f27d Add Nostr chat drawer 2026-05-06 19:52:20 +01:00
Dorian
b2f81a1f8d Stabilize hero stat layout 2026-05-06 19:48:09 +01:00
Dorian
1f86bf9cff Make odds labels readable 2026-05-06 19:47:29 +01:00
Dorian
fa707e2464 Add network stats and rotating mining jokes 2026-05-06 19:43:28 +01:00
Dorian
7e1f7a1a4b Scale heat theme to real hashrate range 2026-05-06 19:30:36 +01:00
Dorian
243cd0daf3 Make Boomer Heater more dead 2026-05-06 19:28:51 +01:00
Dorian
d6f8fd7686 Add Boomer Heater to status 2026-05-06 19:24:56 +01:00
Dorian
0c8e26f597 Add PWA support and miner race 2026-05-06 19:21:06 +01:00
Dorian
360b1ebe66 Rename unknown miner BigPapa 2026-05-06 19:00:04 +01:00
Dorian
c2c376f8b9 Add miner shameboard calculations 2026-05-06 18:47:51 +01:00
Dorian
c77c74612d Add graph telemetry and remote signer login 2026-05-06 18:09:58 +01:00
Dorian
aee42e9c5f fix(deploy): use datum service alias 2026-05-06 17:27:18 +01:00
Dorian
e47b07f780 fix(deploy): use datum container dns 2026-05-06 17:25:14 +01:00
Dorian
83b3a60497 fix(api): reject datum proxy shell responses 2026-05-06 17:19:19 +01:00
Dorian
2c80340039 fix(api): tame noisy logs and overlapping polls 2026-05-06 17:06:49 +01:00
Dorian
aae760331c fix(api): allow LAN HTTP asset loading 2026-05-06 16:56:54 +01:00
Dorian
00a1258458 fix(docker): make Portainer repository deploy buildable 2026-05-06 16:54:27 +01:00
Dorian
b15c24f1f2 fix(docker): SPA copy path + simpler external network ref
Two bugs that surfaced on first real deploy:

1. SPA was COPYed to /app/apps/api/public but server.ts looks at
   /app/apps/web/dist (relative to apps/api/dist via fileURLToPath).
   Static handler returned 404 for /. Move the COPY to match.

2. The aliased external network (`umbrel_main` -> name:
   umbrel_main_network) wasn't attaching in Portainer Stacks → DNS
   lookup of datum_datum_1 fails with ENOTFOUND. Use the network's
   real name directly as the alias to avoid any Portainer indirection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 16:48:23 +01:00
Dorian
b827f89dcd fix: serve SPA from apps/web/dist (matches server default) + use Datum IP
Two production-blocker bugs from the first deploy:

1. Static SPA never served — Dockerfile copied apps/web/dist into
   apps/api/public, but server.ts default static dir resolves to
   apps/web/dist. Mismatch meant every route fell through to Express'
   bare 404 ("Cannot GET /"). Aligning Dockerfile to the default path.

2. DNS for the Datum container name failed (getaddrinfo ENOTFOUND
   datum_datum_1) — gashboard's Docker DNS doesn't reliably alias
   external-network container names across compose stacks. Switch the
   default DATUM_URL to the container's known IP on umbrel_main_network
   (10.21.0.11, captured during earlier diagnostics). If the IP changes
   the user can override DATUM_URL in env. If gashboard isn't actually
   joined to umbrel_main_network, the next failure will be a much more
   diagnostic ECONNREFUSED instead of an opaque ENOTFOUND.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 16:47:56 +01:00
Dorian
0fef84e3eb fix(datum): surface fetch error cause + url in logs
Node's fetch wraps the underlying network error in `.cause`; the bare
`err.message` is just "fetch failed" which tells us nothing about
DNS vs connection refused vs network unreachable. Add formatErr() that
walks .cause and includes its .code, plus the url being attempted, so
logs distinguish between the actual failure modes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 16:37:08 +01:00
Dorian
8f7aeb88b7 chore: serve everything on port 1337
User-facing port unified at 1337 — vibes-aligned and easier to remember
than 8080/8420. Updates: api default PORT, .env.example, docker-compose
mapping (1337:1337), healthcheck target, Dockerfile EXPOSE, Vite dev
proxy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 16:33:01 +01:00
Dorian
98074ff20d chore(docker): map dashboard to port 1337 externally
Per the original brief: 1337 vibe. Internal port stays 8080 (matches
PORT env default and Express trust-proxy assumptions); just remaps the
host port from the 8420 placeholder to 1337.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 16:32:25 +01:00
Dorian
a1ad837957 chore: add .dockerignore
Defensive — keeps node_modules/dist/.vite/.env out of the build context
when running `docker build` locally. Portainer's Stacks→Repository flow
clones from git (which already excludes these via .gitignore), so this
doesn't change Portainer behavior, just hardens local-docker-build paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 16:24:33 +01:00
Dorian
c2b0434a4a fix(docker): copy tsconfig.base.json into the build context
The api and web tsconfig.json files both extend ../../tsconfig.base.json,
but the previous Dockerfile didn't COPY that file into /app. `tsc` then
fails to resolve the extends and exits with code 2 during the
`pnpm --filter @gashboard/api build` stage. Adding the file to the
COPY in the deps stage — both build stages inherit FROM deps, so this
fixes both api and web builds.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 16:15:17 +01:00
Dorian
5c0931e8f2 fix(docker): replace corepack with npm i -g pnpm; copy lockfile
Node 22's bundled Corepack strict-validates pnpm package signatures, and
the Portainer build host couldn't complete `corepack prepare pnpm@9.12.3
--activate` (exit 1). Reproducible failure mode behind networks that
can't reach Corepack's signing-key host or against pinned pnpm versions
whose signatures aren't yet shipped in Corepack's known list.

Switch all three stages to install pnpm via plain `npm i -g pnpm@9.12.3`,
which has none of those constraints. Also copy pnpm-lock.yaml in so
`--frozen-lockfile` actually does what it says (was previously running
`--frozen-lockfile=false` because the lockfile wasn't being copied).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 16:07:09 +01:00
Dorian
c56a47e2c4 feat(web): cyberpunk vue 3 dashboard with primal/amber/extension login
Frontend in @gashboard/web (Vue 3 + Vite + Pinia + TS, ~1.2k LoC):

Auth flow:
  - Two signing paths: NIP-07 browser extension (Alby/nos2x/Primal
    extension via window.nostr) and NIP-46 remote signer (Primal
    app, Amber, nsecbunker via bunker:// URI).
  - applesauce-signers lazy-loaded only on bunker login so users
    with NIP-07 don't pay the cost.
  - NIP-98 event built client-side, posted to /api/auth/login,
    JWT persisted in localStorage. Pinia auth store handles
    login/logout/state restore on reload.

Dashboard (composes the live /api/datum/stats poll, 5s):
  - PoolHero — combined hashrate as the headline number,
    block height, subscribed count, accepted/rejected shares.
  - LotteryWidget — rotating self-deprecating odds copy
    ("you're 0.3× as likely to find a block as get hit by
    lightning today"). Uses ~720 EH/s as the network-hashrate
    constant (TODO: fetch live).
  - ShareTicker — SVG sparkline of the last 60 polls.
  - MinerCard ×N — nickname (QU4CK/P1XEL/N4N0/M1N1), live
    hashrate, last share, lifetime tickets, reject %, status
    glow (green hashing / amber stale / red idle), affectionate
    roast subtitle per ASIC type.
  - BlockCelebration — full-screen overlay with celebration
    copy. Dormant for now (Datum's lastBlockFoundAt isn't
    surfaced yet); preview via window.gashboardCelebrate().

Cyberpunk theme:
  - Pure CSS vars, no Tailwind. Dark bg, neon cyan/magenta
    accents, monospace, glow shadows.
  - Optional CRT scanlines toggle (persists to localStorage).
  - Mobile-aware grid breakpoints.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 15:59:00 +01:00
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
Dorian
2dc9be4678 chore: scaffold pnpm workspace, container, deploy docs
Two-app pnpm workspace for the gashboard (mining dashboard) project:
@gashboard/api (Express 5 + TS) and @gashboard/web (Vue 3 + Vite + TS).
Shared tsconfig.base.json. Multi-stage Dockerfile (node:22.12-alpine,
non-root, healthchecked) and docker-compose.yml ready to deploy as a
Portainer Stack on Umbrel — joins umbrel_main_network so it can reach
the Datum container directly. .env.example documents every var; README
covers the Portainer deploy flow and the security posture.

Note: Dockerfile has a TODO marker to SHA256-pin the base image before
shipping to production.

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