Interface specification
This ecosystem has no palette upstream. The living guide is generated from BLACK_MANTA_DESIGN.md, which is authoritative for interface behaviour, not colour.
Profiles
The two deployment shapes the interface has to survive.
Module map
Every route in the suite, as declared by the source document.
Module table not found in the source document.
Terminal surface
xterm.js + PTY/ConPTY/WSL behind a full audit trail.
Security model
Interface atoms
Atoms are inherited from the shared system shell and recoloured per ecosystem.
<button class="ax-btn ax-btn-primary">Primary</button>
<button class="ax-btn ax-btn-secondary">Secondary</button>
<button class="ax-btn ax-btn-outline">Outline</button>
<button class="ax-btn ax-btn-ghost">Ghost</button>
<button class="ax-btn ax-btn-danger">Danger</button>
<button class="ax-btn ax-btn-primary ax-btn-sm">Small</button>
<button class="ax-btn ax-btn-primary">Medium</button>
<button class="ax-btn ax-btn-primary ax-btn-lg">Large</button>
<input class="ax-input" placeholder="Search tokens...">
<label class="ax-checkbox"><input type="checkbox" checked> Enable token sync</label>
<span class="ax-badge ax-badge-accent">Accent</span>
<span class="ax-badge ax-badge-success"><span class="ax-status-dot active"></span> Operational</span>
--ax-accentInterface molecules
<div class="ds-search-molecule">…</div>
| Token | Value | Status |
|---|---|---|
--ax-accent | brand accent | authored |
--ax-surface-2 | derived ramp | derived |
Interface organisms
Black Manta
Dynamic server-admin suite · authoritative interface design
| Surface | Token set | Typography | Assets |
|---|---|---|---|
| Web | full | display + mono | svg + png |
| Desktop | full | display + mono | icns + png |
| CLI | mono only | mono | — |
Full authoritative document
Rendered verbatim from the source markdown.
#Black Manta — Dynamic Server-Admin Suite: Authoritative Design
Status: AUTHORITATIVE (v1.0 — initial design, 2026-09-15) Scope:local-liteon Windows / WSL, macOS, Linux ↔server-heavyon Linux. Stack (locked): Backend FastAPI + HTMX + Alpine.js + Tailwind CSS · DB SQLite ↔ Postgres · Redis optional. Constraint: Write code files only underdocs/for this step. No implementation yet. Do not run as root.
#1. Goals & Non-Goals
#1.1 Goals
- One app, two profiles: run as a lightweight local admin GUI (
local-lite) on a dev laptop (Windows/WSL, macOS, Linux) and as a full server-admin suite (server-heavy) on Linux servers. - Dynamic capability model: every module probes the host OS / available providers at startup and degrades gracefully (full → read-only → hidden), driven by
profiles+feature flags. - No-SSH-required daily ops: overview, terminal, files, users, services, firewall, backups, files, TLS, logs,
behind a dark HTMX + Alpine.js UI and a FastAPI backend with OIDC SSO.
- Single-binary distribution:
PyInstallerone-file binary per OS + thin OS installers (systemd unit / launchd plist / Windows service + shortcuts). - Secure by default: never run as root; dedicated app user + minimal
sudo-allowlist helper; session auth + TOTP; full audit log; CSRF/CSP/rate-limits baked in. - Zero-to-managed in < 10 min: guided setup wizard (domain, TLS, ports, auth+TOTP, DB, firewall, backups, monitoring) that converges a fresh host to a managed state idempotently.
#1.2 Non-Goals (v1)
- Not a config-management replacement (no Ansible/Puppet parity, no fleet orchestration — single-host focus; multi-host is future).
- Not a full SIEM / APM (basic metrics + logs + alerts only; export to Prometheus/Loki where needed).
- Not a container orchestrator (manage local Docker/Podman only, no Swarm/K8s scheduler).
- Not a secrets vault HA cluster (single-host encrypted store + env/file sync; HashiCorp Vault interop later).
#1.3 Design principles
- Provider abstraction over
if platform ==spaghetti. Each OS concern is aProviderinterface with per-OS backends. - HTMX-first UI: server-rendered Jinja fragments + HTMX swaps + Alpine.js islands; JSON API only where the terminal/uploads/polling need it.
- SQLite default, Postgres when serious: same SQLAlchemy models + Alembic migrations run on both.
- Idempotent actions: every mutating operation is
plan → apply → verify, re-runnable from wizard, API, or CLI. - Audit everything: every privileged or mutating action logs
who/when/what/args/result.
#2. Profiles & Feature Flags
#2.1 Profiles
| Profile | Where | What it is | Defaults |
|---|---|---|---|
local-lite | Windows (native + WSL2), macOS, desktop Linux | Personal admin console on 127.0.0.1; no public TLS, no LE, no public firewall editing; safe local ops | auth.local_only=true, tls.mode=local-selfsigned-or-http, firewall.mode=read-only (Win/macOS) or limited (Linux desktop), jobs.scheduler=apscheduler-in-process, db=sqlite, redis=off, monitoring.retention=24h |
server-heavy | Linux server (systemd; Debian/Ubuntu primary, RHEL-family best-effort) | Full suite on 0.0.0.0:443/80(+custom) behind TLS; LE, UFW/nftables, backups, monitoring retention, Postgres option | auth.totp_required=true, tls.mode=letsencrypt, firewall.mode=enforce, jobs.scheduler=systemd-or-apscheduler, db=sqlite→postgres, redis=optional-on, monitoring.retention=30d |
Profile selection: --profile local|server CLI flag → persisted in config.toml ([profile] name=...) → overridable per-module by feature flags. Auto-detect suggests a profile at first run (Windows/macOS → local; Linux with systemd + CAP_SYS_ADMIN/server hints → ask server vs local).
#2.2 Feature-flag registry (config.toml [features] + env BLACK_MANTA_FEATURE_*)
[features]
overview=true; terminal=true; files=true; users=true; services=true
network=true; firewall=true; packages=true; jobs=true; tls=true
backups=true; logs=true; monitoring=true; containers=true
databases=true; sites=true; secrets=true
# capability modifiers
[features.modes]
firewall="enforce|limited|read-only|off" # local-lite Win/macOS defaults read-only
services_manager="auto" # auto | systemd | launchd | winsvc | wsl-passthrough | off
package_manager="auto" # auto | apt | dnf | brew | winget | off
terminal="on|off" # kill-switch for hardened servers
containers="docker|podman|off"
monitoring_retention="24h|7d|30d"Resolution order per request: env override > config.toml > profile default > provider capability probe. UI hides disabled modules; API returns 403 FEATURE_DISABLED. A /api/capabilities endpoint exposes the resolved matrix so HTMX nav renders dynamically.
#2.3 Runtime capability probe
On boot (and on-demand "Re-probe" button) CapabilityService runs:
os → distro → init (systemd/launchd/service.msc) → fw (ufw/nft/pf/netsh) →
pkg (apt/dnf/brew/winget) → runtime (docker/podman) → le (certbot binary/webroot) →
pty (posix/conpty/wsl) → systemd-creds/keychain availabilityResult cached in SQLite (capabilities table) + surfaced in Overview → "Host capabilities" card.
#3. Tech Stack (Locked)
- Backend: FastAPI (async where I/O-bound; sync subprocess wrappers in
anyio.to_thread). - Frontend: Jinja2 server templates + HTMX partial swaps + Alpine.js islands + Tailwind CSS (CDN in dev, compiled+vendored in binary).
- Terminal frontend: xterm.js (+
xterm-addon-fit,xterm-addon-web-links); no Guacamole/extra deps. - DB: SQLAlchemy 2.0 + Alembic; SQLite (WAL) default, Postgres via
DATABASE_URLfor server-heavy. No raw SQL in routes — service layer only. - Cache/queue: Redis optional (session spillover, pub/sub for log tail + metrics fan-out, rate-limit backend). Absence → in-process
asyncioqueues + SQLite-backed locks. Feature-detect at boot. - Auth: session cookies (
itsdangeroussigned) +pyotpTOTP +argon2-cphpassword hashes. No JWT for browser sessions (JSON API tokens only for CLI, future). - Privileged helper: single
black-manta-helperbinary/script invoked via passwordlesssudoallowlist (exact argv whitelist in/etc/sudoers.d/black-manta). Never run the app itself as root/Administrator. - Packaging: PyInstaller
--onefileper OS (see §8).
Directory sketch (future, informative — not created in this step):
app/ # fastapi factory, routes, services, providers/
templates/ # jinja + htmx fragments
static/ # tailwind build, alpine, xterm
alembic/ # migrations (sqlite+postgres compatible)
black-manta-helper # privileged allowlist wrapper
config.toml # profile + features + wizard outputs#4. OS Provider Matrix
All OS interactions go through Provider ABCs. Routes/services never call subprocess/os directly.
| Concern | Interface | Linux (server-heavy ★) | Linux desktop | macOS | Windows native | WSL2 path |
|---|---|---|---|---|---|---|
| Services | ServiceProvider list/start/stop/restart/enable/logs | ★ systemd (systemctl, journal) | systemd --user + system | launchd (launchctl, plist in ~/Library/LaunchAgents + /Library/LaunchDaemons read-only) | winsvc (sc.exe / PowerShell Get-Service), read-only for system svcs by default | WSL passthrough: query wsl.exe systemctl from Win side; full control inside WSL distro itself |
| Firewall | FirewallProvider status/rules/allow/deny | ★ UFW primary, nftables fallback/direct | UFW limited (no lockout guardrails relaxed) | pf read-only default (pfctl -s), apply = edit /etc/pf.conf + reload via helper only | Defender Firewall via netsh advfirewall read-only default; apply behind explicit flag | Manage inside WSL (UFW/nft) separately from Windows host FW |
| Packages | PackageProvider search/install/update/list | ★ apt (Debian/Ubuntu), dnf best-effort | same as server | brew | winget | WSL distro's apt/dnf; Win side winget listed separately |
| Users | UserProvider list/create/disable/ssh-keys | ★ useradd/usermod, /etc/passwd UID≥1000 filter | same | dscl read + create (helper) | net user / PowerShell LocalUser, read-only default | WSL /etc/passwd |
| Jobs/Cron | JobProvider | ★ systemd timers preferred, cron fallback | cron + APScheduler | launchd plists + cron | Task Scheduler read-only v1 (schtasks /Query) | cron inside WSL |
| Network | NetworkProvider addrs/ports/listeners | ip + ss | same | ifconfig + netstat/-p | ipconfig + Get-NetTCPConnection | both namespaces shown, labeled |
| TLS/LE | TlsProvider | ★ certbot webroot (see §10 context) | self-signed local CA | self-signed local CA | self-signed (certmgr) | certbot inside WSL/Linux only |
| PTY | PtyProvider | posix fork+exec | same | posix (login shell) | ConPTY (Win10+) via pywinpty, else WSL bridge | wsl.exe bridge from Win app |
| Secrets store | SecretBackend | file+SQLite (0600) + systemd-creds opt | file+SQLite | Keychain (read/write user scope) | DPAPI/CredMan user scope | file+SQLite inside WSL |
Rules:
autoresolves per probe; admin can pin (services_manager=systemdetc.) to fail fast instead of silently degrading.- Destructive cross-OS actions (firewall apply on Win/macOS, service stop on
services.mscsystem set) require typing the target name (confirm-gate) + audit entry. - WSL is always labeled: UI shows
WINvsWSL:Ubuntubadges; actions explicitly target one side.
#5. Module Army (17 Modules)
Common module contract: GET /<mod> page + GET /api/<mod>/ fragments/JSON + POST /api/<mod>/ mutations (HTMX or JSON) + capability gate + audit hook. All list views HTMX-paginated; all mutations return an HTMX toast fragment + HX-Trigger: audit,refresh.
#5.1 Overview (/, always on)
Host card (OS, kernel, uptime, load), capability matrix, resource sparklines (CPU/RAM/disk), attention list (failed systemd units, expiring certs, updates pending, firewall off on server profile → red banner), quick links. Poll via HTMX hx-trigger="every 5s" → /api/overview/stats (Redis pub/sub if present, else direct).
#5.2 Terminal (/terminal)
See §6. Kill-switch features.terminal. Hidden entirely when off.
#5.3 Files (/files)
Sandboxed browser rooted at allowlisted roots (/srv, /var/www, /home/*, C:\srv / %USERPROFILE% equivalents). Upload/download (chunked), editor (Monaco-lite/textarea v1), perms view, zip/unzip via helper. No symlink escape (realpath check). HTMX tree + Alpine preview.
#5.4 Users (/users)
List human users (UID 1000+ Linux / LocalUser Win / dscl macOS), create/disable, SSH key management (~/.ssh/authorized_keys), sudo-group visibility (read-only; membership change via helper + confirm-gate). Context: live system uses UIDs 1000–1004 — design the table to show UID, shell, last login, sudo membership.
#5.5 Services (/services)
systemd units (active/failed/enabled), journal tail (journalctl -u), start/stop/restart/enable via helper; launchd/Win mappings per matrix. Failed-units alert feeds Overview.
#5.6 Network (/network)
Interfaces/addresses, listeners (ss -tulpn / Win equivalent), port-check card, reverse-DNS hover. Read-only v1 except "open port in firewall" deep-link to Firewall module.
#5.7 Firewall (/firewall)
UFW-first UX (allow 22,66,80,443/tcp style presets + custom), dry-run → apply two-step on server profile, lockout guard (refuse to close current management port/session IP without override + countdown revert). Win/macOS/pf default read-only with "export rules" action. Context: live UFW allows 22, 66, 80, 443 — preset "ServeBeer standard" encodes exactly this.
#5.8 Packages (/packages)
Updates-pending count, search/install/upgrade via provider, reboot-required flag (/var/run/reboot-required), activity log. Server profile holds unattended-upgrades status card.
#5.9 Jobs (/jobs)
Cron + systemd timers (+ Task Scheduler/launchd read views) unified list; create cron entries via validated builder (no raw crontab paste in UI v1); run-now + last-exit-code; APScheduler for app-internal tasks (cert renew check, backup schedule).
#5.10 TLS/LE (/tls)
Domains → certs table (issuer, SANs, expiry bar), issue/renew via certbot webroot against configured webroot (context: /var/www/acme), self-signed generator for local-lite, deploy hooks (reload nginx/caddy/app), expiry alerts at 21/7/1 days. Private keys never leave server; UI shows metadata only.
#5.11 Backups (/backups)
Sources → destinations (local dir | rsync/ssh | S3-compatible), schedule, retain-last-N, one-click restore to staging path + checksum verify, pre/post hooks (e.g. pg_dump). v1: restic if present else tar+zst+gpg fallback via helper.
#5.12 Logs (/logs)
Journal/file tail with follow (SSE), grep filter, download slice. Audit log is separate, always-on, immutable-append (audit table + optional JSONL mirror).
#5.13 Monitoring (/monitoring)
CPU/RAM/disk/net sparklines, systemd-failed + cert-expiry + disk->90% alerts, uptime checks (HTTP ping list), notification channels (email/webhook v1). Retention by profile (24h local → 30d server). Prometheus /metrics exposition optional-behind-flag.
#5.14 Containers (/containers)
Docker/Podman (whichever socket present): containers/images/volumes, start/stop/logs/exec (exec reuses terminal pipeline, container-scoped), compose-file viewer (no apply in v1). Off when no runtime.
#5.15 DBs (/databases)
SQLite browser (app + user DBs under allowlisted paths) + Postgres (connection manager, pg_dump/restore, table row browser paginated). Credentials live in Secrets module; connection test button; destructive SQL requires confirm-gate + audit + (server profile) TOTP re-auth.
#5.16 Sites (/sites)
Reverse-proxy vhosts (nginx/caddy) list, enable/disable, docroot mapping, per-site LE selector (→ TLS module), basic-auth toggle, config test (nginx -t) + reload via helper. Context: dashboard site adm.servebeer.com on 443/80 (+ custom 66) is the reference vhost shape; static stdlib dashboard stays as-is, Black Manta links to it.
#5.17 Secrets (/secrets)
Env-file editor per site/app (scoped, masked), password generator, file-permission fixer (0600), sync-to-service (restart site worker). Backends per matrix (file+SQLite / Keychain / DPAPI). No plaintext in logs; values redacted in audit.
#6. Terminal Design (xterm.js + PTY/ConPTY/WSL, Audit)
- Frontend:
xterm.jscanvas + fit + web-links addons, Alpine store for session list, HTMX only for the terminal page shell — the PTY stream itself is WebSocket (/ws/pty/{id}), binary-msgpack or JSON{t:"i|o|r", d:...}frames. - Backends (
PtyProvider): - Linux/macOS:
forkpty-styleptymodule + login shell ($SHELL -l),curses-safeTERM=xterm-256color, resize viaTIOCSWINSZ. - Windows native: ConPTY via
pywinpty(powershell.exedefault,cmd.exefallback); Win7/older → hidden module with "use WSL" hint. - WSL bridge:
wsl.exe -d <distro> -- <shell>spawned from Win app; labeledWSL:…in tab. - Multiplexing: one FastAPI WS per tab; server-side session registry (max 4 tabs local / 8 server default, configurable); idle kill (15 min) + absolute cap (8 h); reconnect resumes scrollback (last 10k lines ring buffer in Redis if present else memory).
- AuthZ: requires authenticated session + (server profile) fresh TOTP (≤15 min) or re-prompt;
features.terminal=offkills WS with4403. - Audit: per-session transcript (output truncated at 1 MB/session, env/secrets redaction filters) + command-level log (shell integration via
PROMPT_COMMAND/precmdbest-effort; keystroke log explicitly out of scope). Audit viewer replays scrollback read-only. - Safety: no root shell — PTY runs as app user; privilege escalation only via
sudo black-manta-helper …inside the shell (still allowlisted + logged). Paste-confirm for multi-line pastes;rm -rf /-style denylist warns (not blocks, to avoid false security).
#7. Setup Wizard (/setup, first-run + re-runnable)
Trigger: no config.toml OR setup_complete=false → redirect all routes to /setup. Steps are idempotent; each step = probe → form (prefilled) → plan-preview → apply → verify.
- Welcome + profile: detect OS, recommend
local-litevsserver-heavy; choose + set data dir. - Domain & ports: hostname, admin domain (e.g.
adm.example.com), HTTP/HTTPS + extra port (note: reference system uses443/80+ custom66— wizard supports one extra TCP port field, validated against firewall), local-lite skips to127.0.0.1:<port>. - TLS:
letsencrypt-webroot | self-signed | bring-your-own; webroot field (prefill context default/var/www/acmeon Linux server), email, staging-toggle; verify = fetch cert metadata. - Auth + TOTP: create admin user (argon2), enforce TOTP on server profile (QR + backup codes), session lifetime, fail2ban/rate-limit toggle.
- Database:
sqlite(path) vspostgres(host/port/db/user/secret-ref) + connection test + migrate. - Firewall: show probed provider; preset buttons (
ServeBeer standard: 22/66/80/443,Web: 80/443,Custom); dry-run diff; lockout-guard acknowledgment; apply (server) or document-only (local read-only). - Backups: destination, schedule, retention, test-snapshot.
- Monitoring: retention, alert email/webhook, uptime checks seed.
- Review & apply: single plan summary → Apply all → verify checklist → mark
setup_complete=true, writeconfig.toml, emit auditsetup.complete.
CLI parity: black-manta setup --non-interactive --config seed.toml for scripting.
#8. Packaging (PyInstaller Single-Binary per OS + Installers)
- Build:
PyInstaller --onefile --name black-manta-<os>-<arch>from a versioned tag; vendorstatic/(compiled Tailwind, Alpine, xterm), Jinja templates, Alembic,black-manta-helper; bundlecertbotNOT included (system package dependency on server). - Matrix:
linux-x86_64(★ primary),linux-aarch64,macos-arm64,macos-x86_64,win-x64(+ WSL note). GH Actions one workflow per target; smoke test = boot--profile local --port 0+ hit/api/health. - Installers (thin, per OS):
- Linux:
.deb(or install script) →/opt/black-manta/black-manta+black-mantaapp user +systemdunit +/etc/sudoers.d/black-mantaallowlist +/etc/black-manta/config.toml. - macOS:
.pkg/zip →/Applications/Black Manta.app-ish (binary + plist forlaunchd, runs as user, port127.0.0.1). - Windows: Inno Setup / WiX →
C:\Program Files\Black Manta\+ service entry (or autostart tray) + Defender FW loopback note + "open WSL distro" option. - Updates: in-app version check + "download + verify sig + restart" button (server) / installer re-run (desktop). Config + DB + secrets survive upgrades (paths versioned, migrations forward-only).
#9. Security Model
- No run-as-root, ever. App runs as dedicated unprivileged user (
black-mantaon Linux server; user account on desktop). CI asserts:if os.geteuid()==0 → refuse to start unless --allow-root-for-tests. - Privilege boundary: exactly one escalation path —
sudo -n /opt/black-manta/black-manta-helper <verb> <allowlisted-args>. Helper is a small audited script (argparse, no shell, path-pinned binaries like/usr/sbin/ufw,/bin/systemctl), logs every invocation to the audit trail + syslog. Sudoers allows only those verbs:
systemctl * black-manta-managed-units, ufw ..., certbot ..., nginx -t/-s reload, restic/tar ..., useradd/usermod (constrained), reboot-required-check.
- Auth: session cookie (
HttpOnly; SameSite=Lax; Secureon server profile) + argon2 passwords + mandatory TOTP onserver-heavy(optional on local). Rate-limit login (5/min/IP, Redis-backed if present else in-memory), lockout + auditauth.failed. CLI/API tokens scoped + hashed at rest. - Web hardening: CSRF tokens on all HTMX POSTs, strict CSP (no inline scripts except Alpine nonces),
X-Frame-Options: DENY, secure static caching, upload MIME/size caps, path-traversal + symlink guards on Files/Backups/Sites roots. - Secrets hygiene: masked in UI/API/logs; encrypted-at-rest option (Fernet key from OS store: systemd-creds/Keychain/DPAPI, fallback file 0600); rotation helper for DB + site keys.
- Audit: append-only
audittable (ts, actor, ip, module, action, args-redacted, result) + tamper-evident hash chain (each row hashes previous); admin can export, never edit/delete from UI.
#10. Reference: Existing Live System (Context Only — Do Not Enshrine)
The currently-running server is context for defaults and migration, not the target architecture. Black Manta must interoperate with it, not reimplement it:
- Dashboard:
https://adm.servebeer.com— plain-stdlib Python page (no framework); listens on443, 66, 80(custom66is load-bearing — wizard/firewall presets must include an "extra port" field defaulting to support it). - Firewall: UFW allowing
22, 66, 80, 443— encode as presetServeBeer standard(§5.7, §7 step 6). - TLS/LE: webroot
/var/www/acmeforcertbot— prefill as default webroot on Linux server profile (§5.10, §7 step 3). - Users: human UIDs 1000–1004 — Users module table must handle this range cleanly (UID column, sudo flags, last-login).
- Posture: Black Manta links to (not replaces) the stdlib dashboard during migration; Sites module models
adm.servebeer.comas the reference vhost; no behavior of the live box may be broken by install (lockout-guard + dry-run mandatory).
#11. Open Decisions & Roadmap
- v1 cut: Overview, Terminal, Files, Services, Firewall(UFW), TLS(webroot), Users, Jobs(cron), Logs, Packages(apt), Backups(tar/restic), Monitoring(basic), Sites(nginx), Secrets(file), Containers(read-mostly), DBs(sqlite+pg-basic), Network(read).
- v1.1: Postgres hardening, S3 backups, Win/macOS firewall apply, Task Scheduler write, Podman parity.
- Future: multi-host view, OIDC/SSO, Vault backend, Prometheus-native metrics, mobile-friendly terminal.
- Decisions needed: helper language (Python vs Go for static binary), xterm stream encoding (JSON vs msgpack), HTMX+SSE vs WS for log tail.
End of authoritative design. Implementation must follow profiles (§2), provider matrix (§4), terminal (§6), wizard (§7), packaging (§8), and security model (§9).