VPS Control Room
- Next.js 15 (App Router)
- React 19
- TypeScript
- Tailwind CSS v4
- shadcn/ui
- @base-ui/react
- @xterm/xterm (+ addon-fit, addon-webgl)
- node-pty
- ws (WebSocket)
- node-cron
- Node 22 (agent daemon)
- bun (package manager + frontend runtime)
- systemd
- Traefik
- Tailscale
- PWA / service worker
Brief
"A mobile-first PWA dashboard for driving a single VPS through a web browser: multi-pane terminals (up to 16 concurrent ptys), AI agent launchers, host ops, and audit — all behind a single shared secret and a Tailscale-only domain." And the boundary, in the PRD's own words: "It is a single-owner control surface, not a public SaaS or a remote-desktop replacement." The stated goal: "Give a single VPS operator a fast, visual, browser-based control surface for the host — terminals, files, telemetry — without manual SSH for everyday work."
Constraints
- Single-owner by design — "intentionally not a public SaaS or remote-desktop replacement". Explicitly no multi-user model.
- Tailscale only: "production is bound to a Tailscale-only domain via Traefik. Public Internet should never reach the panel."
- "There is no command allowlist — the perimeter is the security boundary, not per-command sandboxing." The authenticated owner runs commands in a real pty by design.
- The agent is the only component with host access; the frontend never shells out directly, and the agent binds loopback (127.0.0.1) by default so the privileged host API is never network-exposed.
- No data layer between the frontend and the agent, and no Convex — the agent persists state to JSON files on the host.
- Workspace state sync is last-write-wins with no realtime push; the other browser only catches up on next page load.
- Auto-deploy via GitHub Actions is intentionally disabled (workflow_dispatch only); day-to-day deploys happen on the host via scripts/deploy.sh.
- Pane templates are persisted to localStorage only (not yet promoted to agent-side JSON like workspaces were).
- The heartbeat glow only fires on AI agent sessions, because activity detection is heuristic on agent output; plain shell sessions stay still.
- Session eviction: opening a 17th pty evicts the least-recently-updated session (LRU) rather than erroring; each session keeps a ring buffer of up to 250k chars.