Architecture

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.

LocationGitHub repoRole
~/dev/turbopanel/dev./console, bootstrap installer, Ink dev UI
~/daemon/turbopanel/turbopaneldRemote daemon + Ansible (installed by console)
~/instance/turbopanel/turbopanelHono API (Workers wrangler + Deno self-hosted)
~/ui/turbopanel/uiExpo web / native UI
~/website/turbopanel/websiteMarketing + 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 console

To run again from an existing checkout:

cd ~/dev
./console                                  # Node runtime + Ink console

Start 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.

ConcernDevelopmentProduction (managed / FHS)
Source repos$HOME/{daemon,instance,ui,website}No source checkout (compiled release)
Daemon entrypointdeno run main.ts from ~/daemonNative /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 unitturbopaneld.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 areaWhat it drives
StatusNode runtime, $HOME source checkouts, systemd unit health
InstanceTURBOPANEL_INSTANCE_RUNTIME (Deno vs Workers), unit restart
DeveloperFleet, services, shell, database, servers (via instance API over Unix socket)
ActionsInstall 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.

  1. Generate + apply migrations: edit instance/src/lib/db/schema.ts, run pnpm drizzle-kit generate --name <summary>, review SQL under migrations/, then TURBOPANEL_DATABASE_URL=… pnpm migrate (or ./scripts/bootstrap-dev-db.sh).
  2. Push (code → DB, Deno dev iteration only): after a migrated baseline exists, ./scripts/sync.sh from the dev checkout can apply schema edits via drizzle-kit push for quick local iteration — not a fresh-DB bootstrap.
  3. Pull (DB → code): change tables in Drizzle Studio, then ./scripts/introspect.sh from the dev checkout.

See Database troubleshooting for resets and connection issues.

Port allocation

ServiceDefault portNotes
Caddy HTTPS8443Primary browser entrypoint (TLS)
Caddy HTTP (dev only)8880Plaintext mirror of :8443; requires TURBOPANEL_DEV_HTTP_CONTROL_PLANE=1; co-located dev only
Website19820Next.js (optional local run)
Wrangler (Workers mode)18787Internal only
Expo web8081Proxied by Caddy in dev
Postgres5432TCP in Workers mode; Unix socket in Deno
Mailpit web8025Dev email UI
Mailpit SMTP1025Dev 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

These support both Cloudflare Workers (cloud-hosted) and Deno + Caddy (self-hosted) deployment models described in Introduction.

Edit on GitHub

Last updated on

On this page