New /admin service — single Node file + static dashboard shell + systemd
unit + nginx snippet. Receives error reports from the mobile wallet
posted to silentmode.st/apps/deviant/errors and renders them at
silentmode.st/apps/deviant-admin/ behind HTTP basic auth over TLS.
Receiver (server.js, no deps, Node 20+ built-ins only):
- POST /ingest - 32 KB cap, JSON-only, 60 req/min/IP sliding window,
atomic write to $DATA_DIR/<id>.json via tmp+rename. CORS *.
Required fields: id, ts, version, scope, message. Server adds
receivedAt + sourceIp before persist.
- GET /api/errors - server-paginated list, filter by scope/version/
message substring/ts range. Reverse-chronological.
- GET /api/errors/<id> - single report, full JSON.
- GET /, /errors/<id>, /app.{js,css} - static dashboard shell.
Dashboard (public/, vanilla HTML + CSS + JS, no build):
- Reverse-chrono list with id/ts/scope/version/message/UA columns.
- Debounced search + scope/version/ts-range filters.
- Row click opens a drawer with id, timestamps, source IP, message,
stack, extra as a labelled grid, and raw JSON pretty-printed.
- Deep-link /apps/deviant-admin/errors/<id> opens that one report.
- Keyboard: R refresh, C clear, Esc close drawer.
Hardening posture: service runs as www-data; systemd ReadWritePaths
locks writes to just the data dir; PrivateTmp, ProtectHome, no new
privs, filesystem /proc protections mirror admin-hermes / coinspectrum-
admin. No auth in app code (nginx owns basic auth).
Field policy enforced client-side (mobile shell writes `extra`
explicitly) and reinforced here: raw payload is only shown behind the
drawer's click-to-expand sections. No private keys / mnemonics / PINs
/ cookies / session fingerprints anywhere in the pipeline.
Live and smoke-verified: POST → 201, admin list → 1 report, drawer
renders stack + extra + raw JSON. Install notes + patch snippet are in
admin/README.md and admin/nginx-snippet.conf.
39 lines
1.1 KiB
Desktop File
39 lines
1.1 KiB
Desktop File
[Unit]
|
|
Description=Deviant admin — error inbox receiver + dashboard API
|
|
Documentation=file:///opt/silent-mode/apps/deviant-admin/server.js
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=www-data
|
|
Group=www-data
|
|
WorkingDirectory=/opt/silent-mode/apps/deviant-admin
|
|
ExecStart=/usr/bin/node /opt/silent-mode/apps/deviant-admin/server.js
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
|
|
Environment=DEVIANT_ADMIN_PORT=7792
|
|
Environment=DEVIANT_ADMIN_HOST=127.0.0.1
|
|
Environment=DEVIANT_ADMIN_DATA=/opt/silent-mode/data/deviant/errors
|
|
Environment=DEVIANT_ADMIN_PUBLIC=/opt/silent-mode/apps/deviant-admin/public
|
|
|
|
# Hardening — mirror the admin-hermes / coinspectrum-admin posture, scoped to
|
|
# only the paths this process actually writes.
|
|
NoNewPrivileges=true
|
|
PrivateTmp=true
|
|
PrivateDevices=true
|
|
ProtectKernelTunables=true
|
|
ProtectKernelModules=true
|
|
ProtectControlGroups=true
|
|
ProtectHome=true
|
|
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
|
RestrictNamespaces=true
|
|
LockPersonality=true
|
|
RestrictRealtime=true
|
|
RestrictSUIDSGID=true
|
|
ProtectSystem=strict
|
|
ReadWritePaths=/opt/silent-mode/data/deviant/errors
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|