Overview

Run Pagis for a team on one Linux VM with Docker.

One team, one server. The Headless Server is the Pagis server as a Linux container image, and a team runs it on its own VM with the Compose files in deploy/. ADR-0024 holds the decisions.

The shape

One VM. One daemon. One state directory. Postgres for the records. No second daemon.

  • One VM. The memory repositories, the Artifacts, the recordings, the Plugins, the Software, the sealed secrets and the Computer volumes are files on this machine's disk. A server does not scale horizontally, and a second machine is a second installation.
  • One daemon. One process owns every Run of every Person and holds each Run's mid-turn state in its own memory. A restart therefore ends every Run on the server (A restart is the whole server's). Two daemons against one database is not a supported shape.
  • One state directory: /var/lib/pagis, at the same path on the VM and in the daemon's container.
  • Postgres for the records, so the People of the team do not queue behind one writer. Postgres holds the records and nothing else: every file above stays on the disk.
  • The People on it trust each other. A Computer is a container on a shared kernel. That is a reasonable boundary between colleagues, and it is weaker than it looks between strangers.
  • The Plugins of a Workspace are not isolated from each other. Every stdio server of a Workspace runs as one uid in its Plugin Computer, so each one can read the secrets and the data of the others. The Administrator installs together only Plugins that they trust together (Reach).
  • An HTTP Plugin sends requests from the daemon's network. The daemon has the VM's network, so its loopback is the VM's loopback. An HTTP or SSE Plugin server can make the daemon send requests to the services that listen there, for example Postgres, the product port, the Administration Port and the control port of each Computer. It can also make the daemon send requests to every HTTPS host that the VM reaches. The egress policy of the Computers does not apply to these requests (What a Computer reaches). deploy/Caddyfile turns off the admin API of Caddy, so the proxy listens on no port of that loopback.

What makes an installation a server

The process that starts the daemon decides it. The Client App starts a local installation with --local. The Headless Server image starts pagis without it, and that daemon is a server. The configuration cannot decide it: a local installation that other People reach through the owner's proxy has the same Bind Address, Public Origin and Trusted Proxy as the compose deployment.

A server holds no Client Credential. The daemon writes no credential file and removes one that a local run left, refuses the trade at /api/v1/sessions/client, answers nothing at /api/v1/runtime/identity and mints no one-time sign-in link. The start banner in the logs therefore carries no way in, and everybody signs in with an address and a password.

A local installation holds one, whatever its Public Origin. The daemon accepts the trade, the sign-in link and the runtime identity handshake only from a program on the same machine: the socket peer is loopback, the request carries no header that a proxy writes (Forwarded, Via, X-Forwarded-For, X-Forwarded-Host, X-Forwarded-Proto, X-Real-IP), and the Host header names a loopback host. Anything else gets 403. A request through the Trusted Proxy is refused even when the proxy runs on the same machine, because that proxy writes X-Forwarded-For or passes on the public name (ADR-0025).

The same flag decides the Storage Backend. A server keeps its records in Postgres, and it stops at boot when no database URL is set; the error names PAGIS_DATABASE_URL and [database] url. A local installation keeps its records in SQLite at pagis.db in the state directory, whether one person uses it or several, and it stops at boot when a database URL is set. The rule is the same for the Headless Server image and for a server without containers (ADR-0024).

The Public Origin decides who can reach the installation, not the Bind Address: its host is loopback for the People at the machine, and anything else for People on other machines (ADR-0024).

The Headless Server image sets PAGIS_REQUIRE_PUBLIC_ORIGIN. The daemon then refuses to start with --local, and while the Public Origin is empty or has a loopback host, and the error names what to change. A bare docker run of the image stops at once.

Edit on GitHub

On this page