Agent¶
tis-agent (tis/tis-agent) runs on the machines that use the TIS lists – firewalls, DNS resolvers – as one long-running service with one configuration file. It replaces the client scripts tis-pull.sh, tis-push.sh, tis-hits.sh and tis-cron.sh:
- Pull jobs download an export on a schedule and deploy it to a local target – BIND, Unbound and dnsmasq configuration, a plain file, an ipfw or pf table, an nftables or ipset set – checking it first, reloading the service and rolling back when the reload fails, like
tis-pull.sh. - Push jobs send local content – a file, a URL or a command's output – to an import-only source, like
tis-push.sh. - Hit counting reports how often the lists blocked something, for the hit statistics in the web GUI: packets ipfw diverts to the agent, the blocks the BIND RPZ log and the ipfw log record, like
tis-hits.sh, and on Linux the element counters of nftables sets and the packets nftables rules log to an NFLOG group. - Check-in reports the agent's state to the platform every minute, so the machine shows up on the Devices page with its jobs, and the platform notices when it goes offline or a job keeps failing (v0.5.0+).
- fail2ban: the IPs fail2ban bans go to an import-only source as indicators and count as hits when the lists have them (v0.8.0+).
- Healthchecks ping a Healthchecks.io-style monitor around every run, like
tis-cron.sh --healthcheck-url.
The configuration reference describes every setting; Migration from the scripts turns tis-cron.d steps into jobs.
Security model¶
- The agent only connects out – to the platform over HTTPS, and to the URLs of push jobs and healthchecks (without the token). It listens on no port and runs nothing the platform sends.
- Only the machine's configuration file defines the targets: which file or table is written and which commands check, reload and revert it. Commands run directly, never through a shell.
- The token lives in the configuration file (owned by root, mode
600; the agent warns when others can read it) or inTIS_TOKEN, never on the command line. - Upgrades come only as packages; the agent never updates itself.
Requirements¶
- FreeBSD 13 or later (amd64), or Linux – Debian/Ubuntu packages for amd64 and arm64 with systemd.
- Root: the agent writes DNS server configuration and firewall tables and runs their reload commands.
-
A TIS API token (see Tokens & ACL) allowed to export the lists it pulls (
export/group/<list>,export/private/<source>), to import into the sources it pushes to (import/<source>) and to report hits (hits), e.g. -
Hit counting from a divert socket: ipfw and the
ipdivertkernel module (FreeBSD); from logs: read access to them.
Installation¶
The packages are published to the package registry of the agent's project as tis-agent, each with its .sha256.
FreeBSD – tis-agent-<version>.pkg:
It installs /usr/local/bin/tis-agent, the rc.d script /usr/local/etc/rc.d/tis_agent and /usr/local/etc/tis-agent.json.sample.
Debian/Ubuntu – tis-agent_<version>_amd64.deb or _arm64.deb:
It installs /usr/bin/tis-agent, the systemd unit tis-agent.service, /usr/share/doc/tis-agent/examples/config.json and the directory /etc/tis-agent (mode 750). The service is not started on the first install – it needs its configuration first; an upgrade restarts a running one. apt purge also removes the configuration and the state.
Quick start¶
-
Copy the sample and keep it private – it holds the token:
-
Fill in
platform.tokenand adjust the targets and jobs, e.g. a BIND RPZ zone and an ipfw table from the listcustomer-x:{ "platform": { "token": "<token>" }, "targets": { "rpz": { "type": "bind", "path": "/usr/local/etc/namedb/tis.rpz" }, "blocklist": { "type": "ipfw-table", "table": "tis-blocklist", "rule": 1000 } }, "jobs": [ { "name": "rpz", "every": "5m", "pull": { "list": "customer-x", "format": "rpz", "type": "fqdn", "target": "rpz" } }, { "name": "blocklist", "every": "10m", "pull": { "list": "customer-x", "type": "ipv4", "target": "blocklist" } } ] } -
Check it and see what the first run would change:
-
Start the service – FreeBSD:
Debian/Ubuntu:
-
Watch the jobs:
tis-agent status.
Commands¶
tis-agent [-config <file>] [-log-timestamps=false] [command]
daemon run the jobs on their schedules and count hits (default)
run [job ...] run jobs once – all without names – and exit
-dry-run show what would change, change nothing
-force download or push even when unchanged
hits count the hits in the logs once and report them
-dry-run print the counts, send and save nothing
check check the configuration
status show the last result of every job
version print the version
The configuration is /usr/local/etc/tis-agent.json on FreeBSD and /etc/tis-agent/config.json elsewhere, unless -config names another.
run exits with the codes of tis-pull.sh – the highest of the jobs run:
| Code | Meaning |
|---|---|
0 |
Success, or nothing to change |
1 |
Download, upload or other error |
2 |
Deployment failed (reload or table load); previous list kept |
3 |
The check failed; nothing deployed |
A job runs at most once at a time: run fails while the daemon runs the same job.
A deployment logs how many entries it added and removed against the list deployed before, e.g. [blocklist] deployed 20125 entries to nftables set inet filter tis_v4 (+14 -14) (since tis-agent 0.4.0); the generation time and the SOA serial don't count.
status lists every job with its type, schedule, last run and result (deployed, pushed, unchanged, not modified, empty or error with the message), the number of entries and the last change:
JOB TYPE EVERY LAST RUN RESULT ENTRIES LAST CHANGE ERROR
rpz pull 5m0s 2026-10-22 14:05:12 not modified 48213 2026-10-22 09:35:07 -
blocklist pull 10m0s 2026-10-22 14:01:40 deployed 1310 2026-10-22 14:01:40 -
local push 15m0s 2026-10-22 14:00:03 unchanged 12 2026-10-22 06:00:02 -
Running¶
The daemon starts each scheduled job after a random delay of up to a tenth of its interval (at most a minute), so many machines don't hit the platform at once, and then runs it every interval; a run never overlaps the previous one. Jobs without every run only by tis-agent run.
After failed runs in a row the wait grows, so a platform that is down isn't asked again and again: the first failure keeps the interval, then 2×, 4×, … up to an hour (or the interval when it is longer); a success brings the interval back. The log says when the next run comes, e.g. [rpz] 3 failures in a row; the next run in 20m0s. A run that finds the job busy (a tis-agent run of it) doesn't count as a failure. The passes over the hit logs back off the same way.
- SIGHUP re-reads the configuration; an invalid one is logged and the running configuration kept.
- SIGTERM/SIGINT stop it: a deployment that has started finishes (its commands have a 5-minute limit), downloads are dropped, the remaining hits are sent.
FreeBSD – the rc.d script:
service tis_agent start
service tis_agent reload # after editing the configuration
service tis_agent stop
start and reload check the configuration first. The script passes tis_agent_config (default /usr/local/etc/tis-agent.json) and tis_agent_flags, loads ipdivert (a failure only stops the divert hit counting) and logs to syslog with the tag tis_agent – by default to the security facility (/var/log/security) with priority notice, without the agent's own timestamps; tis_agent_syslog_facility and tis_agent_syslog_priority change it.
Debian/Ubuntu – the systemd unit:
The unit checks the configuration before starting and reloading, restarts the agent when it fails, keeps the state in /var/lib/tis-agent and logs to the journal.
Building, testing and releasing are described in the agent's repository.