View Markdown ↗

Deployment and operations#

The supported deployment is a root-managed Ubuntu or Debian server with DNS pointing to the host, ports 80 and 443 open, and working SSH key access.

Installation flow#

flowchart TD Start["Run infra/install.sh with hostname"] --> Validate["Validate root, distro, DNS, and options"] Validate --> Deps["Install pinned host dependencies"] Deps --> Build["Clone or update repo; build frontend and Go backend"] Build --> Proxy["Render, validate, and reload Caddy"] Proxy --> Service["Install and start systemd backend service"] Service --> Health["Poll backend health for up to 30 seconds"] Health --> Image["Build reusable LXD workspace image"] Image --> SSH["Disable SSH password authentication"] SSH --> Heal["Enable minutely LXD IPv4 repair timer"] Heal --> Ready["Open app and claim administrator"]

The curl bootstrap installs Git if needed, clones into /opt/remote.futrx, and re-executes the checked-out installer.

Installed components#

ComponentPurpose
/opt/remote.futrxApplication checkout, built binary, frontend assets, infrastructure scripts, and data
remote.futrx.serviceGo backend on loopback port 7682 by default
CaddyPublic HTTPS, compression, authentication, and proxy routing
LXDProject-container runtime and base-image store
futrx-remote-dev-baseReusable Ubuntu workspace image
.lxd DNS integrationResolves container names through the LXD bridge
lxc-ipv4-heal.timerRepairs running containers that lose IPv4
code-server launcher PWAOne installable entry point for project IDEs

Build flow#

flowchart LR Frontend["Preact and TypeScript source"] --> Vite["tsc and Vite build"] Vite --> Public["backend/public embedded assets"] GoSource["Go source"] --> GoBuild["go build"] Public --> Binary["Single remote binary"] GoBuild --> Binary Binary --> Systemd["remote.futrx.service"]

The backend embeds the compiled frontend, so Caddy only needs to proxy the main origin to the Go process.

Public routing#

flowchart TD Internet["Internet"] --> Caddy Caddy --> Main["host → Go backend"] Caddy --> Launcher["code.host → IDE launcher or slug path"] Caddy --> ProjectIDE["slug.code.host → slug.lxd:8842"] Caddy --> Preview["slug--port.dev.host → slug.lxd:port"] Caddy --> Inspector["preview /__remote_inspector → Go backend"] ProjectIDE --> Auth["forward_auth /auth/verify"] Preview --> Auth Launcher --> Auth ProjectIDE --> TLS["on-demand TLS checked by /internal/tls-ask"] Preview --> TLS

Caddy validates its rendered configuration before replacing the live file. On-demand certificate requests are accepted only for existing project slugs and permitted hostname formats.

Base-image build#

sequenceDiagram participant Builder as build-base-image participant LXD participant Ubuntu as Ubuntu 24.04 builder participant Alias as futrx-remote-dev-base Builder->>LXD: Delete leftover builder if present Builder->>Ubuntu: Launch temporary container Builder->>Ubuntu: Install system tools, Node, GitHub CLI, agent CLIs Builder->>Ubuntu: Install Chromium and Agent Browser Builder->>Ubuntu: Install code-server Builder->>Ubuntu: Stop container Builder->>Alias: Publish reusable image Builder->>LXD: Remove temporary builder

The recipe is generated from the same provider profiles used by runtime CLI repair, keeping agent package versions consistent.

Update flow#

flowchart TD Update["Run infra/update.sh"] --> Pull["Fetch and reset installed checkout to origin/main"] Pull --> Reexec["Re-execute the new updater"] Reexec --> Install["Converge dependencies, rebuild, restart, and health-check"] Install --> Rebuild["Rebuild base image"] Rebuild --> Scan["Read project container names from metadata"] Scan --> Busy{"Active lxc exec agent process?"} Busy -->|"Yes, default"| Skip["Skip busy container"] Busy -->|"No"| Delete["Delete replaceable container"] Delete --> Relaunch["Next start or prompt relaunches from new image"] Relaunch --> Mount["Reattach persistent workspace and reprovision"]

--include-busy forces busy workspace recycling. --skip-workspaces updates only the host and application. upgrade-workspaces.sh --dry-run shows the workspace plan without changing it.

The updater intentionally resets the installed application checkout to origin/main. Persistent application data and project workspaces live outside the tracked source tree.

Startup reconciliation#

When the backend starts, it:

  1. loads file stores and in-memory indexes;
  2. builds the agent and container service graph;
  3. compares project metadata with LXD state;
  4. updates stored project status;
  5. reapplies the fleet resource profile and project overrides;
  6. starts the Agent Browser idle reaper;
  7. begins serving the embedded SPA, API, and WebSockets.

Health and recovery#

flowchart LR Backend["Backend restart"] --> Reconcile["Container status and limits reconcile"] Timer["Every minute"] --> MissingIP{"Running container has no IPv4 after boot grace?"} MissingIP -->|"Yes"| Reconfigure["networkctl reconfigure eth0"] UI["Manual Repair network"] --> Reconfigure Reconfigure --> Inspect["Reinspect for IPv4 up to five times"]

The server-info settings page reports host, CPU, memory, storage, network, and Go-process metrics. The project page reports the corresponding per-container diagnostics.

Security controls#

  • The backend listens on loopback by default; Caddy is the public entry point.
  • Platform sessions use secure, HTTP-only cookies.
  • Preview and IDE requests use forward authentication.
  • Platform cookies are removed before container proxying.
  • Internal Caddy helper routes are denied externally.
  • Secret, auth, access, and user files use restrictive permissions.
  • SSH password and keyboard-interactive authentication are disabled after install.
  • On-demand TLS issuance is restricted to valid, existing project hosts.
  • Project containers are unprivileged and receive host workspaces through mapped ownership.

Operational commands#

bash
systemctl status remote.futrx
systemctl status caddy
journalctl -u remote.futrx -f
sudo bash /opt/remote.futrx/infra/update.sh
sudo bash /opt/remote.futrx/infra/upgrade-workspaces.sh --dry-run

Code map#

remote.futrx documentation