Living document. Update as you build. Replaces codebase re-analysis each session.
InfraCanvas is a multi-VM infrastructure discovery and visualization platform. Two distinct products share this repo:
- OSS (
infracanvasCLI) — self-hosted, single-binary, Cloudflare tunnel - SaaS (
saas-backend/+ SaaS frontend) — multi-tenant, hosted onbytestrixVM
┌─────────────────────────────────────────────────────────┐
│ BROWSER │
│ Next.js 14 SPA — React Flow canvas, Zustand state │
└──────────────┬──────────────────────────────────────────┘
│ WebSocket /ws/canvas HTTP /api/v1/*
│
┌──────────────▼──────────────────────────────────────────┐
│ RELAY / API SERVER (bytestrix) │
│ │
│ OSS mode: pkg/server (:8080, in Docker container) │
│ SaaS mode: saas-backend/pkg/api (:8090, systemd) │
│ │
│ Postgres (port 5432) Redis (port 6379) │
└──────────────┬──────────────────────────────────────────┘
│ WebSocket /ws/agent
│
┌──────────────▼──────────────────────────────────────────┐
│ AGENT (on each VM) │
│ pkg/agent — discovers host/docker/k8s, streams diffs │
└─────────────────────────────────────────────────────────┘
| Subcommand | Purpose |
|---|---|
serve |
All-in-one: relay + agent + embedded UI in one process |
agent |
Agent-only, connects to external relay |
discover |
One-shot discovery, prints JSON |
get / export |
Query running instance |
logs |
Stream container logs |
url / diagnose |
Operational helpers |
- Bind HTTP on
127.0.0.1:7777(with tunnel) or0.0.0.0:7777(no-tunnel) - Start Cloudflare quick-tunnel → print public URL with
?token=<ui_token> - Start in-process agent connecting to
ws://127.0.0.1:7777/ws/agent - Browser auto-pairs (local mode, no pair code needed)
Wire protocol — all messages use {"type": "...", "data": {...}} JSON envelope.
| Direction | Message | Meaning |
|---|---|---|
| Agent → Server | HELLO |
hostname + scope |
| Agent → Server | GRAPH_SNAPSHOT |
full node/edge graph |
| Agent → Server | GRAPH_DIFF |
incremental changes |
| Agent → Server | ACTION_RESULT |
action completed |
| Agent → Server | HEARTBEAT |
keepalive |
| Server → Agent | PAIR_CODE |
assigned pair code |
| Server → Agent | ACTION_REQUEST |
browser-triggered action |
| Browser → Server | PAIR |
connect using pair code |
| Browser → Server | BROWSER_ACTION |
trigger action on agent |
| Server → Browser | AGENT_CONNECTED |
agent metadata |
| Server → Browser | GRAPH_SNAPSHOT / GRAPH_DIFF |
relayed from agent |
Session lifecycle:
- Agent connects → server creates
Session(UUID + pair code e.g.DUSK-DEER-755033) - Browser sends
PAIR {code}→ bound to session - Last snapshot cached for late-joining browsers
- Local mode: browser auto-binds, no pair code needed
WSAgentmaintains WebSocket connection to relay- On connect: sends
HELLO, getsPAIR_CODE - Every
RefreshSeconds: runs orchestrator → computes diff vs previous snapshot → sendsGRAPH_DIFF - On first run or reconnect: sends full
GRAPH_SNAPSHOT - Handles
ACTION_REQUESTfrom browser (docker stop/start, k8s ops, exec sessions, log streaming)
Three scopes, discovered independently:
| Scope | Source | Key data |
|---|---|---|
host |
/proc, sysinfo |
CPU, memory, disk, network interfaces |
docker |
Docker socket | containers, images, networks, volumes |
kubernetes |
~/.kube/config or in-cluster |
pods, deployments, services, namespaces |
Output: GraphOutput {nodes: GraphNode[], edges: GraphEdge[]} from pkg/output/
Triggered by browser via BROWSER_ACTION → relay → ACTION_REQUEST on agent:
- docker: start/stop/restart container, pull image, exec into container
- host: exec shell command, PTY terminal session
- kubernetes: scale deployment, update image, rollout restart
Exec sessions: agent opens PTY or Docker exec, streams stdin/stdout via EXEC_DATA messages.
Packages:
pkg/api/auth/— JWT auth, registration, login, forgot-passwordpkg/api/agents/— agent registry, list agents per orgpkg/api/websocket/— SaaS relay (agent ↔ browser via org/agent ID)pkg/api/organizations/— org management, API keyspkg/api/billing/— billing integrationpkg/middleware/— JWT validation, org contextpkg/database/— Postgres connection (pg driver)pkg/models/— User, Org, Agent, APIKey structs
Auth flow:
- Register/login → JWT
- API keys for agent authentication (
/api/v1/org/api-keys) - Agent connects with API key → registered under org
- Browser connects with JWT → sees agents for their org
SaaS relay flow (different from OSS):
- Agent authenticates with org API key
[relay/agent] HELLO agent=ByteStrix-5bcfabe8 org=bytestrix-657a9cc0- Browser connects → sees all agents in org
- Relay bridges browser ↔ specific agent by agent ID (not pair code)
Next.js 14 App Router SPA. Deployed as dev server on :3001.
Key routes (frontend/app/):
/→ redirect to/dashboard/auth/login,/auth/register,/auth/forgot-password/dashboard— main canvas page
State management (frontend/store/vmStore.ts):
- Zustand store:
vms: Record<code, VMState> - VMState:
{status, hostname, scope, graph, error, lastUpdated} applyVMDiff()— merges incremental diffs into graph state
WebSocket client (frontend/lib/wsManager.ts):
- Module-level singleton (survives React navigation)
- Exponential backoff reconnect (1s → 30s, ±20% jitter)
- Derives WS URL from
window.location(same-origin/ws/canvas) - OSS mode: sends
PAIR {code: "local"}for auto-pair - SaaS mode: sends auth token, then selects agent by ID
Canvas rendering (frontend/components/canvas/):
- React Flow for graph layout
frontend/lib/graphPreprocess.ts— transformsGraphOutput→ React Flow nodes/edgesfrontend/lib/layout.ts— hierarchical layout algorithm
| Process | Port | Binary |
|---|---|---|
infracanvas-server (Docker) |
8080 | OSS relay server |
infracanvas-api (systemd) |
8090 | SaaS backend |
| Next.js dev server | 3001 | SaaS frontend |
| Postgres | 5432 | DB for SaaS |
| Redis | 6379 | Sessions |
| nginx | 3000 | Proxy → 3001 + 8090 |
/ws/ → localhost:8090 (SaaS relay WebSocket)
/api/ → localhost:8090 (SaaS REST API)
/ → localhost:3001 (Next.js frontend)
Port 80 is hijacked by k8s iptables DNAT → do not use port 80 for InfraCanvas.
http://13.200.198.166:3000
Agent on bytestrix connects to localhost:8090 (same VM). Registered as ByteStrix-5bcfabe8 under org bytestrix-657a9cc0. Install script downloaded from /api/v1/install/binary/linux-amd64.
{
id: string // e.g. "/container:abc123"
type: string // "host" | "container" | "pod" | "service" | ...
label: string
layer: string // "host" | "docker" | "kubernetes"
metadata: Record<string, any>
}{
id: string
source: string // node id
target: string // node id
type: string // "runs" | "connects" | "exposes" | ...
}{
timestamp: string
addedNodes: GraphNode[]
modifiedNodes: GraphNode[]
removedNodeIds: string[]
addedEdges: GraphEdge[]
removedEdgeIds: string[]
}OSS (infracanvas serve) |
SaaS | |
|---|---|---|
| Auth | UI token (random hex) | JWT + API keys |
| Multi-VM | No (one relay = one VM) | Yes (org → many agents) |
| Pairing | Pair code or auto (local) | Agent ID selection |
| Hosting | User's own machine | bytestrix VM |
| Tunnel | Cloudflare quick-tunnel | Direct (nginx proxy) |
| DB | None | Postgres + Redis |
# Local dev (OSS) — run everything in one command
./bin/infracanvas serve
# SaaS frontend dev (separate repo on bytestrix)
# ssh bytestrix, then:
cd ~/infracanvas-saas-frontend && npm run dev -- --port 3001
# OR managed by systemd:
sudo systemctl restart infracanvas-frontend
# SaaS backend dev (on bytestrix)
sudo systemctl restart infracanvas-api
sudo journalctl -u infracanvas-api -f
# Check agent connected
curl http://localhost:8090/api/v1/agents -H "Authorization: Bearer <key>"