Skip to main content
Version: next

Agent configuration reference

Where the config lives

The agent reads config.json from its state directory, which the installer creates at /opt/tokentimer-agent/state/config.json with mode 0600, owned by the tokentimer-agent user.

The directory is resolved in this order:

  1. an explicit path passed to the agent,
  2. the TOKENTIMER_AGENT_CONFIG_DIR environment variable,
  3. the OS default.

Restart the agent after editing: sudo systemctl restart tokentimer-agent.

Invalid config fails loudly

Every field is validated at load time and a malformed value aborts startup with a descriptive error naming the field. This is deliberate: a typo in an allowlist must never be silently ignored, because silently ignoring it is what would permit unsafe work.

Connection

FieldTypeDefaultDescription
serverUrlstringrequiredControl-plane base URL. Must be https: unless allowInsecureLocalHttp is set and the host is loopback.
agentIdstring | nullnullSet by the agent after registration. Do not hand-edit.
protocolVersionstringcurrentAgent protocol version advertised at registration.
heartbeatIntervalMsinteger30000Heartbeat frequency. Also how quickly the agent notices retirement and signing-key rotation.
pollIntervalMsinteger15000How often the agent polls for claimable jobs.
caBundlePathstring | nullnullPEM CA bundle used to trust the control plane, for a private CA or a TLS-inspecting egress proxy. Overridden by TOKENTIMER_AGENT_CA_BUNDLE.
allowInsecureLocalHttpbooleanfalsePermits plain http: for loopback hosts only. Development use.

Capability declaration

These tell the control plane what this agent is willing and able to do, and are what job routing matches against:

FieldTypeDefaultDescription
declaredTargetSelectorsstring[][]Targets (hosts, domains) this agent handles.
declaredCommandProfileNamesstring[][]Command profiles this agent offers, for example a certbot or acme.sh profile.

Declaring a capability does not grant it. The local policy block still has to allow the concrete command, path, CA endpoint, and DNS zone.

Execution

FieldTypeDefaultDescription
execution.enabledbooleanfalseMaster switch for doing real work. While false the agent still registers, heartbeats, and reports.
execution.dryRunbooleantruePlan and report the steps a job would run, with no filesystem or process side effects.
execution.keysDirstring<configDir>/keysWhere locally generated private keys are written, mode 0600. Keys never leave the host.
execution.replayStorePathstring<configDir>/replay-store.jsonPersisted nonce cache that makes replayed jobs unexecutable across restarts.
execution.outboxDirstring<configDir>/outboxDurable queue for results that could not be delivered yet.
execution.clockDriftToleranceMsinteger30000Accepted skew when validating a signed job's validity window.
Both switches must be flipped to execute

A fresh install has enabled: false and dryRun: true. Setting enabled: true alone still yields dry-run planning. Real execution requires enabled: true and dryRun: false. Validate a rollout in dry-run first; it exercises the entire trust chain (claim, signature verification, replay check, policy evaluation) without touching the host.

Policy (default-deny)

The policy block is the agent's own authorization boundary. It always wins over control-plane intent: a job that exceeds it is rejected locally, and the rejection is reported back as evidence.

FieldTypeDescription
policy.allowedCommandsobjectMap of profile name to { argv: [...] }. The argv template is the only shape the agent will execute.
policy.allowedPathsstring[]Absolute paths the agent may write certificates into.
policy.allowedCaEndpointsstring[]ACME CA endpoints the agent may talk to, including internal PKI.
policy.allowedDnsZonesstring[]DNS zones the agent may publish challenge records in.
policy.allowedDnsProvidersstring[]DNS provider ids, exact-match. See DNS-01 providers.
{
"policy": {
"allowedCommands": {
"certbot-csr": { "argv": ["/usr/bin/certbot", "certonly", "--csr"] }
},
"allowedPaths": ["/etc/nginx/certs"],
"allowedCaEndpoints": ["https://acme-v02.api.letsencrypt.org/directory"],
"allowedDnsZones": ["example.com"],
"allowedDnsProviders": ["cloudflare"]
}
}
Key export cannot be enabled

Requests to export private key material are rejected unconditionally. There is no policy field, config flag, or control-plane setting that permits it. That is the zero-custody invariant, not a default you can relax.

policy.allowedPaths and the systemd ReadWritePaths list are two independent gates, and a deploy needs both. The installer's --write-path manages the systemd side; this field manages the agent side.

Trust pinning

FieldTypeDefaultDescription
pinnedSigningKeyobject | nullnull{ signingKeyId, publicKeyPem } of the control-plane job-signing key the agent trusts.

The agent pins the signing key on first contact and then refuses jobs signed by anything else. It is written by the agent, not by you. During a rotation the agent re-pins automatically after learning about the new key on heartbeat; see Rotate the job-signing key.

Discovery

FieldTypeDefaultDescription
discovery.directoriesstring[]noneDirectories scanned for existing certificates to report into inventory.
discovery.intervalMsinteger3600000Scan interval.

Discovery is read-only: it reports what it finds and never modifies or moves certificates.

DNS and ACME

FieldTypeDefaultDescription
dnsProvidersobject | nullnullMap of provider id to { credentialsFile } plus the reserved zoneProviderMap.
dnsPropagationobjectsee belowPropagation wait behaviour after publishing and cleanup.
acmeAccountsobject | nullnullMap of account reference to { credentialsFile }.

Credentials are always referenced by absolute path to a 0600 file, never inlined into config.json. The agent refuses a credentials file that is group- or other-readable on POSIX hosts.

Full per-provider fields, zone routing, and propagation tuning: DNS-01 providers.

Minimal working example

{
"serverUrl": "https://tokentimer.example.com",
"declaredTargetSelectors": ["web01.example.com"],
"declaredCommandProfileNames": ["certbot-csr"],
"execution": {
"enabled": true,
"dryRun": false
},
"policy": {
"allowedCommands": {
"certbot-csr": { "argv": ["/usr/bin/certbot", "certonly", "--csr"] }
},
"allowedPaths": ["/etc/nginx/certs"],
"allowedCaEndpoints": ["https://acme-v02.api.letsencrypt.org/directory"],
"allowedDnsZones": ["example.com"],
"allowedDnsProviders": ["cloudflare"]
},
"dnsProviders": {
"cloudflare": { "credentialsFile": "/etc/tokentimer-agent/dns/cloudflare.json" }
}
}