TILLARK · AGENT

A small binary that runs where your data lives.

TillArk never touches your backup bytes; the agent does. It runs on a host that can reach your databases and your storage, claims jobs, runs the backup engines, encrypts before upload, and reports a signed manifest. One agent can serve many sources; run more for more regions or networks.

01What it does

Claim, run, sign, report

text
start ─▶ register keys ─▶ wait for approval ─▶ heartbeat ─▶ claim a job
                                                (what this host can run)
      ┌──────────────────────────────┬──────────────────────────────┐
    backup                         verify                        restore
      │                              │                              │
  open this job's credentials    check the manifest signatures  check the manifest signatures
  check the bucket's object lock restore into a throwaway server check the stored hash
  dump or stream the source      run the checks, then remove it restore into the destination
  encrypt, upload                                                    │
  sign the manifest                  │                              │
      └──────────────────────────── report ────────────────────────┘

Every heartbeat reports which source types the host can back up, verify and restore, from the programs it finds. TillArk sends a job only to an agent that can run it, and routes WAL-G, pgBackRest and clickhouse-backup work for a database on a particular host to the agent on that host. A reference check after a run is restored goes to an agent that reaches both restored stores.

Verify before restore
A restore or verification refuses to start until the manifest’s signatures verify against the keys of the agent that took the backup, and the snapshot it is about to read matches the signed one. A tampered manifest or swapped bucket data stops the job before a byte is restored.
02Install

Install

Builds are published for Linux and macOS on amd64 and arm64: one static binary, no runtime to install. The install script picks the build for your host, verifies the release signature and the checksum, and stops if either fails. It needs curl and OpenSSH 8.2 or newer.

bash
curl -fsSLO https://tilldev.dev/downloads/ark-agent/install.sh
less install.sh     # read it first
sh install.sh       # installs /usr/local/bin/ark-agent

Pin a version with ARK_AGENT_VERSION=<version> sh install.sh, or install somewhere else with ARK_AGENT_DIR=$HOME/bin. To verify by hand:

bash
V=$(curl -fsSL https://tilldev.dev/downloads/ark-agent/LATEST)
F=ark-agent_${V}_linux_amd64           # or linux_arm64, darwin_arm64, darwin_amd64
curl -fsSLO https://tilldev.dev/downloads/ark-agent/allowed_signers \
     -O https://tilldev.dev/downloads/ark-agent/$V/SHA256SUMS \
     -O https://tilldev.dev/downloads/ark-agent/$V/SHA256SUMS.sig \
     -O https://tilldev.dev/downloads/ark-agent/$V/$F

# The signature proves TillDev built this list; the checksum ties your file to it.
ssh-keygen -Y verify -f allowed_signers -I releases@tilldev.dev -n ark-agent-release \
  -s SHA256SUMS.sig < SHA256SUMS
grep " $F\$" SHA256SUMS | sha256sum --check    # macOS: shasum -a 256 --check
sudo install -m 0755 "$F" /usr/local/bin/ark-agent
03Run

Run it as a service

Mint a token with tilldev ark tokens create --scope operator. An operator token lets the agent claim and report jobs but not change your sources, targets or policies. Run the agent as its own unprivileged user: PostgreSQL and MySQL verification refuse to run as root.

ini
# /etc/systemd/system/ark-agent.service
[Unit]
Description=TillArk agent
After=network-online.target
Wants=network-online.target

[Service]
User=ark-agent
EnvironmentFile=/etc/ark-agent/env
ExecStart=/usr/local/bin/ark-agent
Restart=on-failure
RestartSec=5
StateDirectory=ark-agent
StateDirectoryMode=0700
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=full

[Install]
WantedBy=multi-user.target
bash
sudo useradd --system --home-dir /var/lib/ark-agent --shell /usr/sbin/nologin ark-agent
sudo install -d -m 0700 /etc/ark-agent
sudo tee /etc/ark-agent/env >/dev/null <<'EOF'
TILLARK_CP_URL=https://ark.tilldev.dev
TILLARK_AGENT_TOKEN=tark_…
EOF
sudo chmod 0600 /etc/ark-agent/env
sudo systemctl daemon-reload
sudo systemctl enable --now ark-agent
journalctl -u ark-agent | grep key_fingerprint

If the agent restores files, add each of its TILLARK_FILE_ROOTS to the unit as ReadWritePaths=. Outside systemd, run ark-agent with the same environment; it stops cleanly on SIGTERM.

Approve the agent
A new agent registers and waits: it receives no job or credential until an owner or admin approves it. Compare the fingerprint in its log, or from ark-agent fingerprint, with the one under TillArk → Agents, then approve it there or with tilldev ark agents approve <id> --fingerprint <fp>. New keys (a rebuilt host, a wiped data directory) revoke the approval.
04Trust

Work only TillArk signed

The agent acts only on work signed by TillArk’s control-plane key: jobs, WAL archiving leases and clone teardowns. Each reply is signed with Ed25519 and ML-DSA-65 over its body and a nonce the agent sent, so a reply can’t be altered, replayed or moved to another request. Both signatures must verify.

On first contact the agent records the key in cp-key.pin in its data directory and logs the fingerprint. From then on it refuses work under any other key. To pin it before first contact, set TILLARK_CP_KEY to the fingerprint under TillArk → Agents or from tilldev ark cp-key; the install steps on the Agents page put it in the run command.

Key rotation
When TillArk rotates its key, the old key endorses the new one and agents move to it by their next heartbeat, updating cp-key.pin. An agent pinned with TILLARK_CP_KEY follows too, and logs a reminder to update the variable; until you do, TillArk → Agents and tilldev ark cp-key list it as restarting on another key. A key change without that endorsement is refused, and stops the agent at start with exit code 2.
05Config

Environment variables

VariableMeaning
TILLARK_CP_URLTillArk’s agent endpoint, https://ark.tilldev.dev. Must be https. Required.
TILLARK_AGENT_TOKENThe tark_… token. Required.
TILLARK_CP_KEYThe control plane’s key fingerprint, in full or its grouped first 32 characters. Unset, the agent trusts the key it meets first and records it. See Work only TillArk signed.
TILLARK_AGENT_NAMEName shown under Agents. Default: the host name.
TILLARK_AGENT_PLATFORMPlatform label shown under Agents. Default: the OS and architecture.
TILLARK_AGENT_DATA_DIRThe agent key, engine caches and scratch space. Default /var/lib/ark-agent. Never backed up or restored into.
TILLARK_AGENT_KEY_PATHWhere the agent key lives, if not in the data directory.
TILLARK_ENGINESEngines this host runs, comma-separated: restic, walg, pgbackrest, chbackup. Default restic.
TILLARK_LOCAL_SOURCESSource ids whose database or git node runs on this host. Their WAL-G, pgBackRest or clickhouse-backup backups, WAL streaming, and a git node’s backups and verifications, run only here; needs the source’s engine in TILLARK_ENGINES. A restore onto a git node runs on the host listing the destination. See point-in-time recovery, ClickHouse table parts and TillForge git nodes.
TILLARK_FILE_ROOTSAbsolute directories files sources may read and restore into, and bucket backups may restore into. Unset, the agent backs up no files.
TILLARK_BYOK_DIRAbsolute directory of the customer keys this host holds. Default <data directory>/byok.
TILLARK_BYOK_ONLYSet 1 and the agent takes only jobs under a customer key, never one under a key TillArk holds.
TILLARK_FAILOVER_CONFIG_PATHThe failover configuration for sources this host can promote. See failover.
TILLARK_OBSERVEPrimaries this host observes for automatic failover, as source-id=ENV_VAR pairs separated by commas; each variable holds that primary’s connection string. The agent must be approved. See automatic failover.
TILLARK_OBSERVER_VANTAGEapp when this host runs where your application does, else infra (the default).
TILLARK_OBSERVE_INTERVAL_MSTime between observations, 1000 to 15000. Default 5000.
TILLARK_PEER_LISTENIP address and port this host takes observers’ signed reports on, such as 0.0.0.0:7443, over TLS 1.3 with the certificate it signs in with. Set it on a host that watches a replica, so the replica can fail over while TillArk is unreachable.
TILLARK_PEER_ADDRThe host:port observers reach this host at. Required when TILLARK_PEER_LISTEN is 0.0.0.0 or ::.
TILLARK_PRIVATE_TARGETSComma-separated host names, IPs or CIDRs of private storage this host may reach, such as a MinIO on your network. See storage on your network.
TILLARK_CLONE_LISTENIP address masked clones listen on, 0.0.0.0 for every interface. Set, this host builds and serves clones. See hosting clones.
TILLARK_CLONE_HOSTThe name or address people reach clones by; each clone’s certificate names it. Required when TILLARK_CLONE_LISTEN is 0.0.0.0 or ::.
TILLARK_CLONE_PORTSPorts clones are served on, one each. Default 55400-55499.
TILLARK_CLONE_MAXClones served at once, 1 to 16. Default 2.
TILLARK_CA_FILEExtra trusted root certificates, for storage or a control-plane endpoint behind a private CA (such as a TLS-inspecting proxy).
TILLARK_CLIENT_CERT_PATH
TILLARK_CLIENT_KEY_PATH
A client certificate and key to present to TillArk instead of the agent’s own. Set both or neither. Unset, the agent creates client-cert.pem and client-key.pem in its data directory on first start; ark-agent client-cert prints its SHA-256.
TILLARK_PG_BIN_DIRWhere the PostgreSQL programs are, if not on PATH.
TILLARK_PGBACKREST_BIN_DIRWhere pgbackrest is, if not on PATH.
TILLARK_CHBACKUP_BIN_DIRWhere clickhouse-backup is, if not on PATH.
TILLARK_MYSQL_BIN_DIR, TILLARK_MONGO_BIN_DIR, TILLARK_REDIS_BIN_DIR, TILLARK_MEILI_BIN_DIR, TILLARK_CLICKHOUSE_BIN_DIR, TILLARK_FORGE_BIN_DIRThe same, per source type.
TILLARK_JOB_TIMEOUT_MINUTESLongest one job may run. Default 720, minimum 5.
TILLARK_POLL_INTERVAL_MSHow often to ask for work. Default 10000, minimum 2000.
TILLARK_HEARTBEAT_INTERVAL_MSHow often to report in. Default 30000, minimum 5000.
TILLARK_ALLOW_INSECURE_LOCALSet 1 to allow an http control plane, storage, failover provider API, Cloudflare API or switch webhook on localhost, and a control plane that signs no work. Local testing only.

The startup log prints the configuration without the token.

06Tools

What each source type needs on the host

Dumps and files are stored with restic 0.17 or newer, which must be on PATH. PostgreSQL sources on the WAL-G engine use WAL-G 3.0 or newer instead, the PostgreSQL build (wal-g-pg), and those on the pgBackRest engine use pgBackRest 2.51 or newer. ClickHouse sources on the clickhouse-backup engine use clickhouse-backup 2.5 or newer, and their archives are stored with restic. Each source type then needs:

SourceNeeds
PostgreSQLPg_dump and pg_restore; the PostgreSQL server (initdb, postgres) to verify. Set TILLARK_PG_BIN_DIR if they are not on PATH.
MySQL / MariaDBMysqldump and mysql (or mariadb-dump and mariadb); mysqld (or mariadbd and mariadb-install-db) to verify. Set TILLARK_MYSQL_BIN_DIR if they are not on PATH.
MongoDBMongodump and mongorestore (MongoDB Database Tools); mongod as well to verify and restore. Set TILLARK_MONGO_BIN_DIR if they are not on PATH.
ClickHouseThe clickhouse binary (or clickhouse-local) to back up and verify; with the clickhouse-backup engine, clickhouse-backup 2.5 or newer on the database host and the clickhouse binary to verify and restore. Set TILLARK_CLICKHOUSE_BIN_DIR and TILLARK_CHBACKUP_BIN_DIR if they are not on PATH.
MeilisearchThe meilisearch binary, to verify only (backups and restores use the HTTP API). Set TILLARK_MEILI_BIN_DIR if it is not on PATH.
Redis / ValkeyRedis-server (or valkey-server), to verify only (backups and restores use the Redis protocol). Set TILLARK_REDIS_BIN_DIR if it is not on PATH.
Files or bucketNothing beyond restic.
TillForge git nodeThe git node's own tillforge and git, so the agent runs on the node. Set TILLARK_FORGE_BIN_DIR if tillforge is not on PATH.

PostgreSQL’s pg_dump must be at least as new as the server. TillArk → Agents shows what each host can run; a job goes only to an agent that has its programs.

07Customer keys

Keys that stay on your hosts

A source can be put under a customer key: 32 random bytes kept in a file on your agent hosts. The agent derives each repository’s password from it, so TillArk never holds a key that opens those backups. See Security for what that protects.

bash
# As the agent's user, so the agent can read the key
sudo -u ark-agent ark-agent byok-keygen     # prints the key's fingerprint
sudo -u ark-agent ark-agent byok-list       # every key this host holds

# Copy it to each other host that runs the source's jobs
sudo cat /var/lib/ark-agent/byok/<fingerprint>.key              # on this host
sudo -u ark-agent ark-agent byok-import < <fingerprint>.key     # on the other host

# Then put the source under it
tilldev ark sources update <source-id> --byok <fingerprint>
  • Run the commands as the user the agent runs as. A key file is readable by its owner only, so a key written by root is one the agent can’t use. If you set TILLARK_AGENT_DATA_DIR or TILLARK_BYOK_DIR, set it for these commands too.
  • Every host that runs the source’s backups, verifications, restores or WAL archiving needs the key. The agent reports the keys it holds with each heartbeat, so a new key counts from its next heartbeat, with no restart. TillArk sends a source’s jobs only to agents holding its key, and refuses to put a source under a key no approved agent online holds.
  • byok-list names any file it can’t use (another user can read it, it isn’t named after its fingerprint, or its contents don’t match) and exits 1. The agent logs the same and skips it.
  • byok-import reads the key as base64 or hex. The file byok-keygen writes is base64.
Keep an offline copy
TillArk can’t recover a customer key. If every copy is lost, so is every backup taken under it. Keep one somewhere safe and offline, as long as you keep those backups.

On a host that must never run a job under a key TillArk holds, set TILLARK_BYOK_ONLY=1. TillArk then sends it only customer-key work, and TillArk → Agents marks it as customer-key only.

08Private storage

Storage on your network

A target can be a MinIO, another S3-compatible store or an SFTP server on your own network: a private address, or a name outside public DNS (a single label, or one ending in .internal, .local, .lan, .home.arpa or .corp). List it on each agent host that can reach it:

bash
# Storage at 10.20.0.7, or anywhere in that network
TILLARK_PRIVATE_TARGETS=10.20.0.0/16
# An internal name is listed by name
TILLARK_PRIVATE_TARGETS=minio.internal,10.20.0.0/16
  • Each agent reports its list, and TillArk sends a private target’s backups, verifications, restores and deletions only to agents whose list covers it. Under TillArk → Targets, each private target names the agents its jobs go to, or says that none can take them.
  • List a name ending in .internal or .local by name: the agent refuses it before resolving it, so an address range doesn’t cover it. Other names are covered by their name, or by the network they resolve into.
  • For a name that looks public but resolves to a private address, mark the target private (TillArk → Targets, or --private on tilldev ark targets create) and list the name or its network.
  • The host that archives a source’s WAL, the one listing it in TILLARK_LOCAL_SOURCES, must reach every target the source uses.
  • A bucket source on such a host follows the same rule: its backups, and real restores into it, go only to agents that list its endpoint. Mark it private under TillArk → Sources, or with --private on tilldev ark sources create.

Loopback, link-local and cloud metadata addresses can’t be targets. The agent never connects to a link-local or metadata address, even one inside a network you list.

09Clones

Hosting masked clones

An agent can build masked clones of PostgreSQL backups and serve them to your team. Rows are masked on this host as they are copied into the clone; they never pass through TillArk.

bash
# On a host with the PostgreSQL server installed
TILLARK_CLONE_LISTEN=0.0.0.0          # the address clones listen on
TILLARK_CLONE_HOST=clones.internal    # the name your team connects to
TILLARK_CLONE_PORTS=55400-55499       # the default
TILLARK_CLONE_MAX=2                   # clones served at once; the default
  • The host needs what verifying PostgreSQL needs: pg_restore and the server (initdb, postgres), 12 or newer and at least as new as the sources you clone, and restic in TILLARK_ENGINES.
  • Each clone’s certificate names TILLARK_CLONE_HOST, and the connection TillArk hands out checks it. Leave it unset only when TILLARK_CLONE_LISTEN is one address people reach directly.
  • A clone takes one port from the range. Open the range to the machines that use clones and no further. Each clone accepts only TLS, only its own user and database, with a password.
  • Clones live under <data directory>/clones and keep running across restarts of the agent. One that expires, or that someone expires early, is deleted at the agent’s next heartbeat.
  • Restart the agent after setting these. TillArk → Agents then shows the host as hosting clones.
10Security

Security properties

PropertyHow
Pinned client certificateThe agent presents its own certificate on every connection. TillArk pins the first one it sees and refuses the agent’s token with any other certificate, or none, so a stolen token alone can’t act as the agent. After rebuilding a host, reset the pin under TillArk → Agents or with tilldev ark agents reset-cert <id>.
Signed workThe agent acts only on work the control-plane key it trusts signed, with Ed25519 and ML-DSA-65 both verifying, bound to the reply and to a nonce it sent.
Credentials for one jobDatabase and storage credentials arrive sealed to this agent’s key (X-Wing: ML-KEM-768 + X25519), bound to one job, and are opened only in memory.
Encrypted before uploadrestic, WAL-G and pgBackRest encrypt every byte in the agent (clickhouse-backup’s archives are stored with restic); your storage provider holds only ciphertext. pgBackRest’s AES-256-CBC isn’t authenticated, so a restore checks every file against checksums the signed manifest covers, and each WAL segment against the SHA-1 it was archived under.
Customer keys stay localA customer key never leaves the host. Only its fingerprint is sent to TillArk; repository passwords are derived from it in the agent, per job.
Signed manifestsEach backup’s manifest is signed with Ed25519 and ML-DSA-65; TillArk rejects it unless both verify.
No shellEngines run from an argument list with a minimal environment, capped output and a timeout. Secrets go through the environment or owner-only files, never the command line, and are redacted from errors.
Guarded egressStorage traffic goes through a proxy inside the agent that resolves each address and connects only to the one it checked. Private addresses need TILLARK_PRIVATE_TARGETS; link-local and cloud metadata addresses never pass, even when listed.
No partial backupsA dump that fails midway, or a files backup that could not read every file, leaves no snapshot and is reported failed.
Scoped deletionRetention deletes only the snapshot tagged with the expired backup, and nothing if the snapshot list could not be read in full.
HTTPS to TillArkTLS 1.2 or newer, with a post-quantum key exchange offered on TLS 1.3. A redirect to http or another host is refused, so the token can’t leak.

See the quickstart to protect a first source, Security for the key model, or the CLI reference. Back to the TillArk overview.