Installing metor

Four ways, from the most convenient to the most manual. The prebuilt box image comes from the GitHub Container Registry: ghcr.io/metor-com/metor-box (tags latest = main, 0.1.0 etc. for releases, plus a short commit sha), built by .github/workflows/box-image.yml for linux/amd64.

Security first

Whoever gets into the interface can read every chat, drive every bot and open every bot’s terminal. The gateway inside the box (port 6010) therefore signs devices in itself (ADR-0012): no passwords - the first browser gets in with a one-time setup link, every further phone or computer is linked by QR code or pairing code from a device that is already signed in, and every device can be removed again. Still:

curl -fsSL https://www.metor.com/install.sh | sudo bash

The same script from the repository:

curl -fsSL https://raw.githubusercontent.com/metor-com/metor/main/deploy/install.sh | sudo bash

The installer asks for a domain (nothing else) and proposes <public-ip>.sslip.io as the default, a name that resolves to the server without owning a domain (see “Without your own domain” below); Enter takes it, local means this machine only without TLS. It installs Docker if it is missing, writes /opt/metor/{compose.yml, Caddyfile, .env}, pulls the image, starts box + Caddy (TLS via Let’s Encrypt as soon as the name resolves to the server) and prints the setup link for your first device, as text and as a QR code. If something already listens on port 80 or 443, the installer leaves its own Caddy off, names the process, and prints the block to add to that proxy (or free the ports and run the installer again - it is idempotent). Open the link, then in the interface click New bot, pick the runtime and sign in to it - Claude Code: open the link, sign in, paste the code the page shows at the end; Codex: device code. Further devices: Devices → Link a device. A new setup link at any time:

cd /opt/metor && docker compose exec box metor auth link

Unattended (one ssh line)

Every question can be answered up front through environment variables; the script asks only for what is missing and stops with a hint when it has no terminal for a question. Nothing in the installation needs a person at the keyboard:

ssh root@<server> 'METOR_DOMAIN=bots.example.com bash -c "curl -fsSL https://www.metor.com/install.sh | bash"'
VariableMeaning
METOR_DOMAINDomain for the interface; unset = <public-ip>.sslip.io; local (or empty) = this machine only, without Caddy
METOR_INSTALL_DOCKERno = stop instead of installing Docker (with the compose plugin) via get.docker.com when it is missing
METOR_GHCR_USER, METOR_GHCR_TOKENGitHub user + token with read:packages, only for a private image (a mirror via METOR_IMAGE); the official package is public
METOR_DIR, METOR_IMAGEInstall directory (default /opt/metor) and image (default ghcr.io/metor-com/metor-box:latest)

The script ends by printing the setup link; the two manual steps that remain are opening that link on your first device and signing in to a runtime in the interface.

Without your own domain

HTTPS needs a host name that resolves to your server: the session cookie is the key to everything and must never travel over plain HTTP on a public address. You do not have to buy a domain for that; in order of preference:

Whatever name you choose, the sessions are bound to it: a different name (or a new IP with sslip.io) means every device signs in again with a fresh setup link.

B) New server - compose by hand

Copy deploy/compose.yml, deploy/Caddyfile.template and deploy/.env.example to the server, fill in .env, replace @DOMAIN@/@USER@/@HASH@ in the template (hash: docker run --rm caddy:2 caddy hash-password) and save it as Caddyfile, then:

docker compose --profile caddy up -d

Then sign in from the interface (“New bot”) or with docker compose exec box claude auth login.

C) Shared server (your own reverse proxy already runs)

Like B, but without the caddy profile: docker compose up -d - the box then listens only on 127.0.0.1:6010 (the installer does this by itself when it finds port 80 or 443 taken). Forward /bots* from your existing proxy; the sign-in is done by the gateway, the proxy only needs TLS, WebSockets and unbuffered responses. Caddy (the block metor box serve prints):

handle /bots* {
	reverse_proxy 127.0.0.1:6010 {
		flush_interval -1
	}
}

nginx (inside the server block of your HTTPS site; certbot --nginx or your usual way for TLS):

location /bots {
	proxy_pass http://127.0.0.1:6010;
	proxy_http_version 1.1;
	proxy_set_header Host $host;
	proxy_set_header X-Forwarded-Proto $scheme;
	proxy_set_header Upgrade $http_upgrade;
	proxy_set_header Connection "upgrade";
	proxy_buffering off;
	proxy_read_timeout 1h;
}

Then docker compose exec box metor auth link for the first device.

D) Development (local, macOS with Colima or any Docker)

See the README: clone the repository, export PATH="$PWD/backend/harness/bin:$PATH", metor box build && metor box up, then sign in from the interface at http://127.0.0.1:6010/bots/ (or docker exec -it metor-box claude auth login). The image is built locally (arm64 on Apple silicon); the ghcr image is amd64 for servers. metor version prints the version of the checkout (docker compose exec box metor version prints the version inside the box).

Runtimes

Every bot runs on one runtime; each runtime is signed in once per box and the login persists in its volume.

A runtime whose login expires stops only its own bots; the others keep running. Sign in again and start the bots.

Operations

Options (.env, see deploy/.env.example)

This page is metor-com/metor/blob/main/INSTALL.md in the metor repository, rendered as it is.