agent¶
Contributor documentation
This section explains how the Manager, Nodes, and cluster work internally and how to contribute to the opensource cluster itself. If you are a developer who just wants to run your own code on the cluster, see the Users documentation instead.
Node.js (TypeScript) check-in agent that reports host/service status to the manager and applies nginx and dnsmasq configuration. Installed on agent and manager containers. For deployment instructions, see Deploying Agents.
Architecture¶
A systemd timer launches the oneshot agent every 30 seconds. Each run checks in with the manager (POST /api/v1/agents), applies any config changes, and exits.
TypeScript sources live in agent/src (compiled to dist/), the locally rendered EJS templates in agent/templates, and the systemd service/timer units in agent/contrib/systemd. The agent/Makefile builds the opensource-agent package (see Release Pipeline). Service reloads and state queries go through the systemd D-Bus API; config validation uses nginx -t and dnsmasq --test.
Check-in Protocol¶
sequenceDiagram
participant Timer as systemd timer
participant Agent
participant Manager
Timer->>Agent: start (every 30s)
Agent->>Manager: POST /api/v1/agents<br/>{siteId, hostname, ipv4Address, services}<br/>If-None-Match: {etag}
alt Config changed
Manager-->>Agent: 200 OK + config snapshot + ETag
Agent->>Agent: Render templates, validate<br/>(nginx -t, dnsmasq --test)
Agent->>Agent: Apply + reload (rollback on failure)
Agent->>Manager: POST again (reports apply results)
Manager-->>Agent: 304 Not Modified
else No changes
Manager-->>Agent: 304 Not Modified
end
Agent->>Timer: exit
The check-in body carries system info, per-service status, and — when the agent provisioned volume directories this pass — per-volume results:
{
"siteId": 1,
"hostname": "agent",
"currentTime": 1771234560,
"ipv4Address": "172.17.0.2",
"services": {
"nginx": { "state": "active", "lastApply": "success" },
"dnsmasq": { "state": "active", "lastApply": "success" }
},
"volumes": {
"42": { "applied": true },
"43": { "applied": false, "message": "mkdir: permission denied" }
}
}
The manager records every check-in in the Agents table (shown on the web client's /agents page) and responds with the site's config snapshot as JSON. A strong ETag covers the snapshot; the agent stores it in /var/lib/opensource-agent/state.json and sends it back via If-None-Match, so unchanged configs cost a single 304 round trip.
Volumes¶
The config snapshot includes, at the site level, the volume directories the
agent must ensure exist (site.volumes[] = { id, hostPath, mode, uid, gid }).
There is one agent per site, and the shared volumes root is bind-mounted into the
agent container, so this single agent provisions volumes for every node in the
site. Each pass the agent first checks, via /proc/self/mountinfo, that the
path lies on a mount other than its own root filesystem (otherwise the volumes
root isn't bind-mounted and the volume is reported failed rather than created
inside the agent). It then mkdir -ps the directory, chowns it to the
supplied uid/gid (the consuming containers' id-mapped root, 100000)
best-effort, and chmods it per mode. The chown is a tolerated no-op
(EINVAL/EPERM) in an unprivileged agent guest — the norm, since pct create
defaults to --unprivileged 1, where the id-map already yields the right owner
and 100000 is outside the guest's mapped range — and the actual fix in a
privileged agent guest (deliberately created with --unprivileged 0), where the
agent is host root and mkdir would otherwise create a root-owned directory. Per-volume
results are reported back keyed by volume id via the volumes field above; the
manager writes them into Volume.status (ready / failed). The container
create job blocks on Volume.status = 'ready' before attaching the Proxmox
mpN bind mount. The agent only ever creates directories — it never removes
them, so volume data is retained across container
delete + recreate. See Volumes and
Deploying Agents → Volume storage.
Environment Variables¶
Read from the process environment (systemd loads /etc/environment). Set via container runtime (Docker ENV, Proxmox LXC config) — the base image's environment.sh service propagates them on boot.
| Variable | Required | Description |
|---|---|---|
SITE_ID |
Yes | Numeric site ID from the manager |
MANAGER_URL |
Yes | Base URL of the manager (e.g., http://192.168.1.10:3000) |
API_KEY |
No | Admin API key for remote agents. Not needed on the manager (localhost is trusted). |
STATE_DIRECTORY |
No | State directory, set by systemd via StateDirectory= (default /var/lib/opensource-agent) |
The agent Dockerfile defaults to SITE_ID=1 and MANAGER_URL=http://localhost:3000 so the manager container works without configuration.
Managed Services¶
| Service | Files | Test | Reload |
|---|---|---|---|
| nginx | /etc/nginx/nginx.conf |
nginx -t |
systemctl reload-or-restart nginx |
| dnsmasq | /etc/dnsmasq.conf, /var/lib/dnsmasq/{dhcp-hosts,hosts,dhcp-opts,servers} |
dnsmasq --test |
restart when /etc/dnsmasq.conf changed, otherwise SIGHUP |
Apply flow per service: render templates, skip if nothing changed, stage new files, run the test command, roll back on failure, then reload. Failures are reported as lastApply: "failure" at the next check-in.
To add a managed service, add a ManagedService entry in agent/src/apply.ts plus its template(s) under agent/templates/, and extend the config snapshot in create-a-container/utils/agent-config.js.
Running Manually¶
systemctl status opensource-agent.timer # timer state
journalctl -u opensource-agent.service # past runs
node /opt/opensource-server/agent/dist/index.js # single run by hand