Development architecture
This page describes how the TurboPanel multi-repo dev environment is orchestrated. Before you start, see Prerequisites. For day-to-day commands, see Local development and Dev console troubleshooting.
Repository model
TurboPanel split from a single monorepo into sibling repositories. The dev repo is the entry point — it does not contain platform source; it installs and drives them. Local checkout path is ~/dev, independent of the GitHub repo name.
| Location | GitHub repo | Role |
|---|---|---|
~/dev/ | turbopanel/dev | ./console, bootstrap installer, Ink dev UI |
~/daemon/ | turbopanel/turbopaneld | Remote daemon + Ansible (installed by console) |
~/instance/ | turbopanel/turbopanel | Hono API (Workers wrangler + Deno self-hosted) |
~/ui/ | turbopanel/ui | Expo web / native UI |
~/website/ | turbopanel/website | Marketing + Fumadocs (this site) |
Paths resolve via TURBOPANEL_DEV_ROOT and per-repo TURBOPANEL_<DIR>_REPO overrides (defaults: $HOME/{daemon,instance,ui,website}).
Bootstrap flow:
curl -fsSL dev.turbopanel.sh | sh # clone/update ~/dev, launch consoleTo run again from an existing checkout:
cd ~/dev
./console # Node runtime + Ink consoleStart dev stack in the console installs the daemon (if missing) and hands off to Ansible for instance, UI, Caddy, and Postgres. Platform repos clone on branch trunk via SSH.
Dev vs production path model
The console drives a dev layout where source repos live under $HOME and mutable data uses FHS paths owned by the dev user. Production uses the same FHS tree with dedicated service users. The daemon's src/paths/layout.ts is the single source of truth and picks the mode automatically (development when a resolvable daemon checkout is present, otherwise production); every path is env-overridable.
| Concern | Development | Production (managed / FHS) |
|---|---|---|
| Source repos | $HOME/{daemon,instance,ui,website} | No source checkout (compiled release) |
| Daemon entrypoint | deno run main.ts from ~/daemon | Native /opt/turbopanel/bin/turbopaneld, or turbopaneld.js via vendored Deno when the native binary cannot load |
| Orchestration assets | <checkout>/orchestration + dev/orchestration/ overlay | /opt/turbopanel/share/orchestration |
| Vendored runtimes | /opt/turbopanel/vendor | /opt/turbopanel/vendor |
| Config | /etc/turbopanel/daemon.env (dev-user-owned) | /etc/turbopanel/daemon.env |
| State / identity | /var/lib/turbopanel (dev-user-owned) | /var/lib/turbopanel |
| Logs | /var/log/turbopanel (dev-user-owned) | /var/log/turbopanel |
| Sockets / runtime | /run/turbopanel (dev-user-owned) | /run/turbopanel (2770 tp:tp) |
| systemd unit | turbopaneld.service (dev user) | turbopaneld.service (tp) |
Both modes use the turbopaneld.service systemd unit. Production code never names the dev checkout root — a CI guard (deno task check:layout) and src/orchestration/paths.test.ts pin the two trees and the shared DENO_VERSION (2.9.4).
Dev console orchestration
The Ink console (./console) is the single operator surface:
| Console area | What it drives |
|---|---|
| Status | Node runtime, $HOME source checkouts, systemd unit health |
| Instance | TURBOPANEL_INSTANCE_RUNTIME (Deno vs Workers), unit restart |
| Developer | Fleet, services, shell, database, servers (via instance API over Unix socket) |
| Actions | Install daemon, start stack, follow logs, build mode toggle, quit |
Service graph (Deno mode, default)
Caddy terminates TLS on 8443 and proxies /api/* and /ws/* to the instance Unix socket; everything else goes to Expo (dev) or static export (production build mode).
On co-located dev hosts, Caddy also listens on 8880 (CADDY_HTTP_PORT) as a plaintext mirror of every route on :8443. It is gated by TURBOPANEL_DEV_HTTP_CONTROL_PLANE=1 (injected automatically when turbopanel_dev_user is set) and is never enabled on managed or production installs. Both TURBOPANEL_INSTANCE_RUNTIME=deno and =workers sit behind the same Caddy proxy, so :8880 works identically for either runtime — useful when bypassing self-signed TLS or attaching a co-located daemon without CA setup.
Instance runtime switch
TURBOPANEL_INSTANCE_RUNTIME in the daemon .env is deno (default) or workers. Switch via the Instance area in the console. Workers mode stops the systemd instance unit and expects pnpm dev (wrangler) in the instance checkout with Postgres on TCP.
Database schema (development)
Schema changes are versioned in instance/migrations/. Versioned pnpm migrate is the only fresh-database bootstrap path — co-located dev converge runs scripts/bootstrap-dev-db.sh (pnpm migrate), and Workers deploy runs the same command. Applied versions are tracked in public.migration. An unmigrated database is an operational failure (missing relations propagate); it must not be treated as install mode / needsInstall.
- Generate + apply migrations: edit
instance/src/lib/db/schema.ts, runpnpm drizzle-kit generate --name <summary>, review SQL undermigrations/, thenTURBOPANEL_DATABASE_URL=… pnpm migrate(or./scripts/bootstrap-dev-db.sh). - Push (code → DB, Deno dev iteration only): after a migrated baseline exists,
./scripts/sync.shfrom the dev checkout can apply schema edits via drizzle-kit push for quick local iteration — not a fresh-DB bootstrap. - Pull (DB → code): change tables in Drizzle Studio, then
./scripts/introspect.shfrom the dev checkout.
See Database troubleshooting for resets and connection issues.
Port allocation
| Service | Default port | Notes |
|---|---|---|
| Caddy HTTPS | 8443 | Primary browser entrypoint (TLS) |
| Caddy HTTP (dev only) | 8880 | Plaintext mirror of :8443; requires TURBOPANEL_DEV_HTTP_CONTROL_PLANE=1; co-located dev only |
| Website | 19820 | Next.js (optional local run) |
| Wrangler (Workers mode) | 18787 | Internal only |
| Expo web | 8081 | Proxied by Caddy in dev |
| Postgres | 5432 | TCP in Workers mode; Unix socket in Deno |
| Mailpit web | 8025 | Dev email UI |
| Mailpit SMTP | 1025 | Dev email SMTP |
Browser traffic should use Caddy on :8443 (HTTPS) or, on co-located dev only, http://<host>:8880 when TURBOPANEL_DEV_HTTP_CONTROL_PLANE=1 — not wrangler or the raw Expo port.
Technology choices
- Hono — Shared HTTP app for Workers and Deno (
instance/src/app.ts). - Expo / React Native — Cross-platform UI (
ui/). - Next.js + Fumadocs — Marketing and documentation (
website/). - Drizzle + Postgres — Schema in
instance/src/db/schema.ts; Hyperdrive in Workers production. - Caddy — TLS termination and reverse proxy in dev and self-hosted production.
- Ansible (daemon) — Installs and updates runtimes, instance, UI, and Caddy on managed hosts.
- Ink — Terminal dev console in turbopanel/dev.
- Command Pipeline — Typed command/job records in Postgres (
commandtable); Cloudflare Queues (Workers) or RabbitMQ (Deno) for durable async transport; Daemon Cell (Durable Object per server on Workers, Redis on self-hosted) for live WS delivery and request correlation. Status reads stay Postgres-only — never fan UI polling into the cell. Production daemon commands are typed handlers only — never arbitrary shell strings. Seeinstance/AGENTS.md(Daemon Cell + Command Pipeline) andinstance/src/lib/commands//daemon/src/instance/commands/.
These support both Cloudflare Workers (cloud-hosted) and Deno + Caddy (self-hosted) deployment models described in Introduction.
Last updated on