Skip to content

Configuring an agent

Every setting in agent.env — what it does, what it defaults to, and which three of them enrolment writes.

The agent reads one file, /etc/runjet-agent/agent.env, which systemd hands it as EnvironmentFile=. In a container these are the same variables passed differently. It is read at startup: a change applies after systemctl restart runjet-agent.

What enrolment writes

Three values are written into the file by enrolment itself, with 0600 permissions. Where the file already exists it is left alone and the three lines are printed for you to add by hand.

/etc/runjet-agent/agent.env
# Written by `runjet-agent enroll`.
#
# There is no ENROLLMENT_TOKEN here on purpose: it was spent during enrollment
# and is of no further use. See agent.env.example for the optional settings.
RUNJET_URL=https://api.runjet.dev
RUNJET_PUBLIC_KEYS=rj-1:BASE64KEY
AGENT_NAME=web-01

Required settings

VariableWhat it means
RUNJET_URLRunjet's address. Over HTTPS everywhere but local development: the signature on a dispatch proves who sent it, but only TLS keeps a command and its output private on the way. Without it the agent will not start: RUNJET_URL is required.
RUNJET_PUBLIC_KEYSThe Runjet keys the agent trusts, as id:base64. A command signed by anything else is refused rather than run. During a key rotation there may be several, comma-separated. Without it: RUNJET_PUBLIC_KEYS is required: without it the agent cannot tell its scheduler from anyone else who can reach it.
AGENT_NAMEMust match the name the agent was registered under.

ENROLLMENT_TOKEN is deliberately not in the file. enroll spends it while it runs and writes it nowhere. The one place the agent reads it from the environment is a first start with no identity — which is the path for a container. After enrolment, remove it from the environment; nothing needs it any more.

Optional settings

VariableDefaultWhat it means
AGENT_STATE_DIR/var/lib/runjet-agentWhere identity.json with the private key lives, 0600 inside a 0700 directory. Under systemd, StateDirectory= provides it.
AGENT_SECRETS_FILEunsetA KEY=value file with 0600 permissions holding the secrets the jobs on this host need. The job names a value, the agent fills it in, Runjet never sees it. Values are read verbatim, so they may contain spaces. Unset, the agent looks for values in its own environment — in detail on the secrets on the host page. Set to a file that does not exist, the agent will not start.
AGENT_PASS_ENVemptyThe exact names of variables from the agent's environment that job commands should be given too. See below.
AGENT_MAX_PARALLEL4How many different jobs may run on the host at once. The same job never overlaps itself, whatever this value is.
AGENT_POLL_WAIT30sHow long one ask for work waits before the agent reconnects. Longer means fewer requests; Runjet accepts up to 60s.
AGENT_JOB_USERrunjet-jobThe account job commands run under. See below.
AGENT_JOB_GROUPemptyA numeric gid, for when AGENT_JOB_USER is a numeric uid with no entry in /etc/passwd. Otherwise not read.

AGENT_PASS_ENV

A job does not inherit the agent's environment. It is given a fixed base — PATH, TZ, LANG, LC_*, and HOME, USER, LOGNAME, SHELL derived from the account it runs under — plus the secrets it asked for, and nothing else. That is exactly what keeps the settings in this file out of every command the agent starts. Anything more a job needs has to be named here:

AGENT_PASS_ENV=JAVA_HOME,HTTPS_PROXY,NO_PROXY

Two things the agent refuses, failing to start rather than ignoring them quietly:

  • a pattern such as AWS_* — a glob is how an allowlist turns back into a leak, because in a year it will pass on whatever happens to be named that way,
  • any of its own settings (RUNJET_URL, RUNJET_PUBLIC_KEYS, ENROLLMENT_TOKEN, AGENT_*) — passing those on would give jobs precisely what the boundary is protecting them from.

Why this is an allowlist rather than a list of forbidden names is on the job isolation page.

AGENT_JOB_USER

Three states, and they differ:

  • Unset — the runjet-job account you created during installation.
  • Set to a name or a uid — that account. The agent is a static binary with no cgo and reads /etc/passwd itself, so it does not see an account that exists only in a directory (LDAP, SSSD); for one of those, give a numeric uid and a gid in AGENT_JOB_GROUP. A name that cannot be resolved stops the agent — if it fell back quietly to its own account, you would believe you had an isolation you do not.
  • Set to empty (AGENT_JOB_USER=) — jobs run as the agent, and you accept that they can then read its key and its secrets file. The agent says so at every start, and the Agents screen shows it as not isolated.

Before taking any work the agent checks that it really can switch to that account; without CAP_SETUID and CAP_SETGID it will not start, and it says which line to add to the unit.

Ceilings from Runjet

Two of the values above have a counterpart arriving from Runjet with every answer to an ask for work, signed the same way a dispatch is: how many runs the agent may start at once, and how long an ask should wait. They are ceilings, not orders. The agent may take fewer and never more: for parallelism the smaller of the two numbers holds, for waiting the longer of the two durations. So a plan cannot be got around by editing agent.env, and Runjet cannot force a host to run more work than its own operator allowed. A lowered ceiling kills nothing — what is running finishes, and nothing further starts.