Files
zopu-code/deploy/vds

Zopu VDS agents deployment

This directory holds the systemd unit, environment template, and Caddy reverse-proxy config that run the single Zopu agents Node process on the VDS. It supersedes the former container deployment for the agents process.

Runtime topology

One Node process (zopu-agents.service) runs the whole intelligence runtime:

zopu-agents.service
  └─ node /srv/zopu/current/packages/agents/dist/server.mjs
       ├─ Hono server            127.0.0.1:3000
       ├─ Flue 2.0 agents        SQLite at /srv/zopu/data/flue/flue.db
       └─ AgentOS registry       in-process, serverless, at /internal/rivet/*

The Rivet Engine is a separate private service on 127.0.0.1:6420, managed by its own unit/compose. This unit does not start it. There is no standalone AgentOS runner: the registry handler runs inside the agents process.

The two Rivet endpoints (not interchangeable)

Variable Role Value on this host
RIVET_ENDPOINT Engine control plane this process connects to for actor metadata http://127.0.0.1:6420
RIVET_SERVERLESS_ENDPOINT Address the engine calls back into this process's /internal/rivet handler http://127.0.0.1:3000/internal/rivet

Pointing both at the engine, or dropping the /internal/rivet path, breaks the registry. See agents.env.example for the authoritative comments.

Files

  • zopu-agents.service — systemd unit. Runs /srv/zopu/current/.../server.mjs as user zopu, waits for engine health before binding, hardens the service, and restarts on failure.
  • agents.env.example — template for /etc/zopu/agents.env. Contains the full validated env schema with no secrets.
  • ../../scripts/release-agents.sh — local build, upload, and atomic release promotion command. Each release includes this directory so the unit's Documentation= target remains valid.
  • Caddyfile — source-controlled public ingress for agents.zopu.puter.wtf. Terminates TLS and forwards only /health, /internal/project-setup, and /internal/agents/* to 127.0.0.1:3000; the private /internal/rivet/* registry handler is never proxied.

Prerequisites on the VDS

  1. Node 24 installed at /opt/zopu/node/bin/node (the verified VDS Node; the unit's ExecStart and PATH are anchored on this path). The release bundle ships its own node_modules; only the Node binary is expected from the host.
  2. curl and bash on PATH (used by the ExecStartPre engine health probe).
  3. A dedicated service user and group:
    sudo useradd --system --no-create-home --shell /usr/sbin/nologin zopu
    
  4. The two writable state trees, owned by zopu:
    sudo mkdir -p /srv/zopu/data/flue /srv/zopu/workspaces
    sudo chown -R zopu:zopu /srv/zopu/data /srv/zopu/workspaces
    
  5. The Rivet Engine running and answering http://127.0.0.1:6420/health.
  6. A release promoted by scripts/release-agents.sh to /srv/zopu/releases/<release-id>, with /srv/zopu/current symlinked to it. The release must contain packages/agents/dist/server.mjs and deploy/vds/.

Install

# 1. Secrets
sudo install -d -m 0750 -o root -g zopu /etc/zopu
sudo cp agents.env.example /etc/zopu/agents.env
sudo chown root:zopu /etc/zopu/agents.env
sudo chmod 0600 /etc/zopu/agents.env
sudo "$EDITOR" /etc/zopu/agents.env        # fill in every REPLACE-WITH-...

# 2. Unit
sudo install -m 0644 zopu-agents.service /etc/systemd/system/zopu-agents.service
sudo systemctl daemon-reload
sudo systemctl enable --now zopu-agents.service

Verify

systemctl status zopu-agents.service
curl -fsS http://127.0.0.1:3000/health        # {"service":"zopu-agents","status":"ok"}

Public ingress (Caddy)

Caddy terminates TLS for agents.zopu.puter.wtf and forwards only the three paths the Convex backend calls to the internal agents worker (127.0.0.1:3000). The in-process serverless registry at /internal/rivet/* is reachable only over loopback (the Engine's RIVET_SERVERLESS_ENDPOINT callback), never through Caddy. All other paths return 404.

Install or update from this checked-in artifact:

# 1. Materialize the config
sudo install -m 0644 Caddyfile /etc/caddy/Caddyfile

# 2. Validate before applying
sudo caddy validate --config /etc/caddy/Caddyfile --adapter caddyfile

# 3. Reload the running Caddy (no restart needed)
sudo systemctl reload caddy

Verify the narrow public surface:

curl -fsS https://agents.zopu.puter.wtf/health   # {"service":"zopu-agents","status":"ok"}
curl -i  https://agents.zopu.puter.wtf/internal/rivet/anything   # 404 — registry stays private

Release switch

From the repository root on the build host, create and promote a release:

scripts/release-agents.sh <vds-host> <release-id>

The script atomically swaps /srv/zopu/current; it deliberately does not restart the service. After the upload succeeds, restart the single agents unit:

sudo systemctl restart zopu-agents.service

Roll back by atomically replacing current with a symlink to a prior release, then restarting the same service.

Optional: explicit engine ordering

The unit already gates startup on the engine health endpoint via ExecStartPre. If the engine is itself a named systemd unit on this host, you may add a hard ordering by uncommenting/adding in the [Unit] section:

After=rivet-engine.service
Wants=rivet-engine.service