-
Notifications
You must be signed in to change notification settings - Fork 1
Architecture
┌──────────────────────────────────────────────────────────────────┐
│ docker compose stack │
│ │
│ ┌────────────────────────────────────┐ ┌──────────────────┐ │
│ │ vibe-coder-server │ │ vibe-coder- │ │
│ │ (Ubuntu 24.04 LTS, port 17880) │ │ postgres │ │
│ │ • Ktor / Netty │◄─►│ (postgres:17- │ │
│ │ • Routes (admin SSR + JSON API) │ │ alpine) │ │
│ │ • Exposed ORM → PostgreSQL │ │ • Port 5432 │ │
│ │ • WebSocket log hub │ │ (internal) │ │
│ └─────────────┬──────────────────────┘ └──────────────────┘ │
│ │ spawns │
│ ┌───────┼────────┬────────┐ │
│ ▼ ▼ ▼ ▼ │
│ claude gradlew git vibe-doctor │
│ (per- (per- (read- (Android SDK, │
│ project build) only) MCP install) │
│ persistent │
│ child) │
└──────────────────────────────────────────────────────────────────┘
All external commands are wrapped in a TaskQueue + LogHub so progress
streams uniformly to WebSocket clients (browser / Android). The PostgreSQL
sidecar (v0.14.0+) holds admin / projects / builds / artifacts /
uploaded_files. The server connects via JDBC over the internal docker
network — no port is exposed to the host by default.
vibe-coder-server/
├── shared/ # JVM library (DTOs, ApiPath, WsFrame)
│ └── src/main/kotlin/.../shared/
│ ├── ApiPath.kt # All REST/WS routes as constants
│ ├── ws/WsFrame.kt # Sealed class hierarchy for WS frames
│ └── dto/Dtos.kt # @Serializable request/response types
│
└── server/ # Ktor app body
└── src/main/kotlin/.../server/
├── ServerMain.kt # Bootstrap, DI wiring
├── Module.kt # Routing + plugin install
├── auth/ # Bearer + session + setup + CSRF
├── audit/ # AuditLogger + /audit page (v0.15.0+)
├── claude/ # ClaudeSessionManager (stream-json)
├── env/ # EnvSetupService, MCP, Claude auth
├── git/ # GitReader, GitCloneService, credentials
├── projects/ # ProjectService, KeystoreGenerator
├── build/ # BuildService (Gradle assembleDebug)
├── artifacts/ # APK storage
├── files/ # Upload routes + ProjectFileBrowser
├── prompts/ # Prompt template store + /prompts page
├── admin/ # SSR routes + HTML templates
├── tasks/ # TaskQueue (background work)
├── ws/ # LogHub (WebSocket broadcaster)
├── config/ # ServerConfig + ConfigPersistence
└── db/ # VibeDb (PostgreSQL via Exposed)
-
Client sends
POST /api/projects/{id}/claude/console/promptwith text. -
ConsoleRoutes finds or spawns the
claudechild for that project (ClaudeSessionManager.spawnSession). Stream-json mode (--output-format stream-json --input-format stream-json). - The user prompt is written as a stream-json frame to the child's stdin.
- Claude responds line-by-line on stdout.
ClaudeStreamParserdecodes each line and turns it into aWsFramesubtype:console_session_started-
console_assistant(withisPartial) -
console_tool_use/console_tool_result -
console_done/console_error
-
LogHubbroadcasts the frame to all WS subscribers on/ws/projects/{id}/console/logs. - Browser console UI renders incrementally. Android client does the same with the same JSON shape.
To cancel a turn (v0.13.0+): POST .../claude/console/cancel — server
sends SIGTERM to the child but keeps the saved session-id, so the next
prompt resumes the same conversation.
All persistent state lives under one host directory (v0.7.0+) and the PostgreSQL data directory is part of that tree (v0.14.0+). See Data Volumes & Backup for the full mapping.
./vibe-coder-data/
├── workspace/ # project sources + APKs
├── postgres/ # PostgreSQL data dir (v0.14.0+)
├── server/ # server logs + build metadata
├── dev-tools/ # Android SDK, Gradle, npm-global (MCP), npm cache, ...
└── claude/ # Claude OAuth credentials + MCP registrations
The image itself contains only the server body (~600 MB) and is replaced on
upgrade. Every persistent path is a bind mount; no Docker named volumes are
used by default. The PostgreSQL directory is owned by UID 70 inside the
container (the postgres user in alpine images) — see Data-Volumes for
backup procedures.
-
Engine: PostgreSQL 17 (
postgres:17-alpinesidecar container). - ORM: Exposed 0.55.0 + Hikari connection pool (default size 10).
-
Tables:
admin_users,devices,projects,builds,artifacts,uploaded_files. Schema is created/migrated on boot viaSchemaUtils.createMissingTablesAndColumns. -
Cascade: Foreign keys reference
projects.id. PostgreSQL enforces these (unlike SQLite's default off).ProjectService.deletedoes explicit cascade cleanup foruploaded_files,artifacts,buildsbefore deleting the project row. - Connection retry: On boot, the server retries 30× / 2 s = 60 s total to give the postgres container time to become healthy.
-
Future:
conversation_turnstable (Roadmap §F.☆ #1) — JSONB column fortool_useinput/output, GIN tsvector for full-text search. -
Audit log (v0.15.0+):
audit_logtable records IAM-level actions (auth / project / build / MCP / settings / git / console). See the Audit Log page for the schema and filter URL recipes.
Pre-v0.14.0 used SQLite single-writer; see the Upgrade Guide v0.13 → v0.14 section for the migration story.
-
First boot: empty DB →
/setupform creates admin (orVIBECODER_ADMIN_USERNAME/PASSWORDenv auto-bootstrap). -
Login:
/api/auth/loginreturns bearer token +vibe_sessioncookie. -
Subsequent requests: either auth header (
Authorization: Bearer ...) or the cookie. Both paths converge in the sameinstallAuthplugin. -
CSRF: All SSR POST forms carry an HMAC-SHA256-derived CSRF token in
a hidden
_csrfinput (v0.12.4+). REST API (Bearer header) is exempt. - Passwords: BCrypt cost 12 hash. 10 failures → 15-min account lock, 30 failures from same IP / 24 h → 24-h IP block (v0.12.4+). Timing-safe dummy verify on missing users.
shared/ is the contract between server and Android client. All wire
changes (ApiPath / DTO / WsFrame) must be reflected in the Android
companion repo's shared/ copy. CHANGELOG marks them with Wire change:
Yes/No. Recent additions:
- v0.10.0 —
RegisterProjectRequestDtoclone fields + 19 env-setup APIs. - v0.13.0 —
ApiPath.claudeConsoleCancel(projectId)for turn cancel.