Installing the Linux Agent
One command installs the agent, and this page is what happens inside it: the terms of use first, then what is checked before anything is written, every question and what an Enter answers, the environment variables and flags of an unattended rollout, an installation by hand, and a host without systemd.
Install
One command, run as root. It reads the current version from the public metadata, downloads the build
for your architecture with your key, verifies it against SHA256SUMS, installs it to
/usr/local/bin/reportedip-agent and then sets the host up.
curl -fsSL https://reportedip.com/agent/install.sh | REPORTEDIP_KEY=<YOUR-KEY> sh
The key goes into the environment because it is needed twice, once for the licensed download and once
to register the host, and pasting it twice is how a key ends up in a shell history in the wrong form.
Leave the variable out and the script asks for the key, with the terminal echo off so it stays out of
the scrollback. The prompt reads the terminal and not standard input, because standard input is the
script itself in the form above, and being able to open that terminal at all is the test for whether
anybody is there to ask: a run from cron, from a systemd unit or from an automation task has no
terminal, is not asked, and stops with the line that names the variable. The script is POSIX
sh on purpose: it has to run on hosts where bash is not the default shell. It stops with
a named reason and installs nothing when it is not run as root, when the platform is not Linux, when
the architecture is neither amd64 nor arm64, when neither curl nor wget
exists, and when sha256sum is missing.
The two fetchers are deliberately separate. The version metadata is pulled without the key, the licensed files with it, so the key is never sent to a redirect target that only serves public metadata. Of the checksum file only the line for your own architecture is checked, because the file also lists the other one.
What install checks before it writes
Three things are checked before the first byte reaches the disk, in the order in which a mistake is
cheapest to find. The arguments come first, because a typo in a flag is the same mistake on every
host and has nothing to do with what this machine can do: a malformed --notify-email
used to create the directories first and be rejected afterwards. Then the host, printed as a named
block, because whoever runs this should learn that the agent cannot run here before being sent to
look up a key. Then the questions of the next section. The key last, because it is the only step that
costs a round trip and because a key that was just typed in should be held against the server at
once.
requirements:
ok systemd running as pid 1
ok firewall ipset and iptables found, nft as well; auto takes ipset, set backend: nftables in the config if this host's rules live in nftables
Each line carries one of three marks. ok is met, warn is missing and does
not stop the run, and STOP is the only mark that ever means the install ended there.
Only systemd is hard, and it is read off /run/systemd/system, the directory systemd
creates when it is pid 1, rather than off the presence of systemctl: a container image
can carry the binary without ever running the manager, and that is the host that used to get a full
installation which could never start. A missing firewall backend is reported as
warn and stops nothing, because report only
is a supported way to run the agent, and the line names the package to install,
apt install ipset iptables or apt install nftables. The key is put to
verify-key before the config is written, and deliberately without the install ID: a
probe must not leave a registry row behind for a host that may never be installed. Only a 401 or a
403 stops the run, and then nothing is on disk. Everything else, a DNS failure, a proxy, a firewall
that has not been opened yet, prints one line and the installation continues, because an
installation without a network is legitimate and the sync run retries by itself. What
install.sh itself refuses before any of this is listed above.
What it asks, and what an Enter answers
Four questions, and only for what it was not given: the key, the address a broken state is mailed to, whether a match of the community list is dropped or only logged, and whether this host also bans what it catches in its own logs. A run that was handed all four as flags or as environment variables is asked nothing at all, which is the case an unattended rollout runs, and it is neither slower nor more talkative than before. Everything the installer can find out for itself, the ssh ports, the log sources, the firewall backend, it finds out instead of asking.
api key (from your account on https://reportedip.com):
email for the state mails of this host, Enter for none: ops@example.org
blocking: the community list is loaded into the kernel either way. What the rules do
with a match is the question, and this host is set up to deny it: drop is what
an Enter here writes, so the first sync already stops what the list names.
log is the other answer, and the one to give for a host you want to watch
before it denies anything: it counts every match and lets the packet through.
Neither answer is final. mode: log or mode: drop in the config is one word, and
the next sync run rebuilds the rules. Your own addresses, the session you are
sitting in and everything in the whitelist are never blocked in either mode.
drop or log, Enter for drop:
local bans: the second switch, and the half that replaces fail2ban. The list above comes
from the community; this is about the addresses this host catches in its own
logs, and it is on unless you say otherwise. Every entry has a timeout the
kernel enforces, so a stopped agent leaves no permanent ban, whitelist add is
the way out of one, and in mode: log they are recorded and only logged.
ban what this host finds itself [Y/n]:
ok key accepted, role <your role>, reports today <used>/<limit> (account-wide)
wrote /etc/reportedip-agent/config.yaml (lists [ssh mail web ftp edge], ssh ports [22022], 10 log sources, mode drop)
this host denies every match of the community list and bans what it finds in its own logs
state mails go to ops@example.org; change notify.email in the config to stop or redirect them
units installed; sync timer and log watcher enabled; run: reportedip-agent sync && reportedip-agent status
The key is read with the terminal echo off, so it stays out of the scrollback and appears in no line
of the output. An Enter is a valid answer to the other three and it takes the default every time: no
mail address, mode: drop, local bans on. A host set up with three Enters therefore
denies what the community list names and bans what it catches in its own logs, which is what the
agent is for. What makes that safe is not the answer, it is the whitelist: the addresses of this
host, the session the install runs in, RFC1918 and everything in whitelist.conf are
never blocked and never reported, in any mode, and reportedip-agent whitelist add takes
effect without a sync. The last two questions sit in the plain install rather than behind a flag
because they are the two values that decide whether the host does anything at all, and the
difference between them is invisible in systemctl status.
reportedip-agent install --expert, or REPORTEDIP_EXPERT=1, asks for the
rest. Eight groups, each one behind a [y/N] gate that an Enter skips, and inside a group
every value shows its default in brackets: the feed with the firewall backend, the ban ladder, the
detection thresholds, the threshold of a single event source, the operating limits with the logging,
the mail delivery including an smtp server for a host without a local MTA, the ports of the ssh list,
and the detected log sources, where one can be dropped and one the detection missed can be added.
Every answer is checked against the range the configuration parser accepts before it can be written,
so a 0 for min_hits comes back as
0 is outside 1 to 10000 and is asked again, and the finished configuration then goes
through the same validation the agent runs when it reads the file, which makes one class of bug
impossible: an answer that produces a registered host with an agent that refuses to start. The ban
group prints the ladder it just built, so time_minutes 45 with escalate 3
and max_time_minutes 20160 answers with
45 min, 135 min, 405 min, 1215 min, 3645 min, 10935 min, 14 d (ceiling).
mode and ban.enabled are not asked a second time, they were answered above.
An expert run that skips every group writes byte for byte the same file as a plain one. No question
offers the expert mode, it is reachable through the flag and the variable and nothing else, and
without a terminal it is an error rather than a silent fall back to the plain install.
Without a terminal nothing is asked, which is the situation of cron, of a systemd unit, of an
automation task and of CI. One detail is worth knowing there: < /dev/null on its own
does not make a run non-interactive, because the controlling terminal survives a redirected standard
input. setsid removes it, and that is what those callers do.
| Without a terminal | What happens |
|---|---|
| the terms of use not accepted | install: the terms of use were not accepted, nothing was written; read https://reportedip.com/terms/ and pass --accept-terms or set REPORTEDIP_ACCEPT_TERMS=1, exit 2, nothing written. This comes before the key and before the requirements. install.sh stops the same way, before the download. |
| no key anywhere | install: no config yet; pass --key <api_key>, or set REPORTEDIP_KEY, or run this from a terminal and it asks, exit 2, nothing written. |
| every value as a flag, or as a variable | Not one question. The ordinary run, and the configuration is written. |
--expert | install: --expert needs a terminal to ask on; without one, pass the values as flags or edit the config afterwards, exit 2, nothing written. |
REPORTEDIP_BAN is neither true nor false | --ban: REPORTEDIP_BAN="maybe": want true or false, exit 2, nothing written. A typo in an automation variable is an error and not a silent off. |
install.sh with no key | It names the variable, says there is no terminal to ask on, and nothing is downloaded. |
What the installer really does
Everything below is idempotent. Running the same command again on a host that already has the agent upgrades the binary and leaves the configuration exactly as it was.
- Shows the terms of use and takes a
yes, unlessREPORTEDIP_ACCEPT_TERMS=1is set. Without a terminal and without the variable the script stops here, before the key and before any download. - Reads
https://reportedip.com/agent/latestwithout the key and pulls the version out of it. That file is the same JSON the agent itself polls for updates. - Downloads
reportedip-agent_linux_<arch>andSHA256SUMSwith theX-Keyheader into a temporary directory that is removed on every exit path. - Verifies the checksum and refuses to install on a mismatch.
- Installs the binary with mode 0755 to
/usr/local/bin/reportedip-agentand prints the version it just placed. - Restarts
reportedip-agent.serviceif that unit is currently active. This matters on an upgrade: the watch daemon would otherwise keep running the old code until the host reboots. The sync unit is a oneshot and picks the new binary up by itself on its next start. - Runs the host setup, unless
REPORTEDIP_NO_SETUP=1is set or/etc/reportedip-agent/config.yamlalready exists. In the second case it says so and leaves the configuration alone.
The host setup is the step that writes files, and it is the same thing as
sudo REPORTEDIP_KEY=<YOUR-KEY> reportedip-agent install. In order, once the terms of use, the flags, the requirements and the questions are through:
- Detects the SSH port from
sshd -T, fromSSH_CONNECTIONand from the listeners of running sshd processes, and uses the union of the three. When nothing is found it stops and asks for--ssh-portrather than assuming 22. - Decides which of the five feed lists to configure.
sshandedgeare always on.mail,webandftpare switched on when a matching unit is active or a matching port is listening, so a host with no web server gets no web list. A port counts only when something outside could reach it: a socket bound to127.0.0.1or::1alone is not an attack surface, and it is not treated as one. That is the difference between a mail server and a local delivery agent that only takes mail from its own machine, and between a name server and the stub resolver that holds127.0.0.53:53on a normal Debian host. - Detects the log sources and writes them into the config explicitly. Detection happens once, at
install time, and the result stays in the file: a service that is stopped for maintenance must
never switch a source off silently. On a host with a control panel the per site globs of the
panel and the default logs of the web server are both configured, and not just the first of the
two. They are not two views of the same traffic: a request that reaches no virtual host with an
access_logof its own lands in the default log, which is exactly what a scanner walking addresses instead of host names produces, and on a measured production host that was 20,453 lines from 466 distinct addresses in a single day. Every default log that exists is taken, because nginx in front of Apache is the ordinary panel layout and both write their own, and an empty one counts too, since a log is empty until somebody knocks./var/log/apache2/other_vhosts_access.logis deliberately left out: its lines begin with the virtual host, so the address sits in the second field while the detector reads the first, and a source that can report nothing whiledoctorcalls the host healthy is worse than no source. - Calls
verify-keyand prints your role and the reports used today against the daily limit. This is the last of the three checks and it happens before the first directory is created, so a wrong key costs nothing on disk. A 401 or 403 here means the key is wrong and is the one failure worth fixing immediately. - Creates
/etc/reportedip-agentand/var/lib/reportedip-agent, the second one mode 0700, and it corrects the mode of a directory that was already there. - Writes
/etc/reportedip-agent/config.yamlwith mode 0600.modeandban.enabledcarry the answers from the questions above, and with nothing given and nothing typed that ismode: dropandban.enabled: true, the same two values the question above printed as its default. The two lines after the file say in words what the host now does, because the difference is invisible insystemctl status. An existing config is never overwritten: a second run asks nothing and saysthe key was ignored because the config already exists; edit api_key in /etc/reportedip-agent/config.yaml to change it, and reads both switches back out of the file it kept, so it can sayit says mode: drop and ban.enabled: true, so that is what this host does; nothing here changed either value. Do not delete the file to get a fresh install unless you mean to lose those two decisions; move it aside instead. - Writes the auto-whitelist in the state directory: the address your SSH session came from, plus every interface address of the host. The session address of an earlier install run is kept, so a second run from a different session does not strip the address the first one relied on. Interface addresses are re-derived on each run, so an address you removed does not linger.
- Generates the install ID, a random UUID v4 in
/var/lib/reportedip-agent/install-id. That is the identity this server has towards the API from then on, and it stays that identity even though the hostname travels next to it. - Warns about three things it cannot fix for you: an nftables table
inet reportedipleft behind by the documented shell script, whatever the backend; on a fresh host, ipset sets namedrip-*that the agent did not create, usually the same script run from a cron job, whose whitelist has to be taken over and whose job has to be off before the first sync; and an nginx that reads Cloudflare headers withoutset_real_ip_from, which would have the web source count Cloudflare's addresses instead of the visitor's. - Writes the three systemd units to
/etc/systemd/system, runsdaemon-reload, enablesreportedip-agent-sync.service, and enables and starts bothreportedip-agent-sync.timerandreportedip-agent.service. - Once the units are in place, calls
verify-keya second time, now with the install ID, which registers the host, and prints the role and today's reports again. A 401 or 403 at this point ends the run with exit 1: the host is installed, andapi_keyin the config is what needs fixing.
INPUT. The first reportedip-agent sync creates all of that, which is
why the installer ends by telling you to run it. Until then the host is exactly as it was, and an
installation you decide against costs one rm of two directories.
The environment variables of install.sh
| Variable | Default | What it is for |
|---|---|---|
REPORTEDIP_KEY | none | Your API key. Without it the script asks for it on a terminal, and where there is no terminal to ask on it stops before it downloads anything. Required in an unattended run. |
REPORTEDIP_ACCEPT_TERMS | unset | 1 accepts the terms of use without the question. The script shows them before the key and before the download, and a yes given there is passed on to the setup step, so nobody is asked twice. Required in an unattended run: without a terminal and without the variable the script stops before it downloads anything. Same as --accept-terms. |
REPORTEDIP_VERSION | the current release | Pins a version, for a staged rollout or to reinstall a specific build. Old version directories are kept on the distribution point for exactly this. |
REPORTEDIP_PREFIX | /usr/local/bin | Where the binary goes. Change it and the systemd units, which name the absolute path, have to follow. |
REPORTEDIP_BASE | https://reportedip.com/agent | The download base. For an internal mirror that serves latest, SHA256SUMS, SHA256SUMS.sig and the two binaries. The script itself checks only SHA256SUMS; the signature in SHA256SUMS.sig is verified by the self-update. |
REPORTEDIP_NO_SETUP | unset | 1 places the binary and stops. Nothing is detected, no config is written and no unit is installed. This is the variable for a golden image, a container layer or a configuration management run that owns the config file itself. |
REPORTEDIP_NOTIFY_EMAIL | unset | Where this host mails a broken state. The setup reads it from the environment, so one line arms the mail on a whole rollout. Without it the config carries an empty notify.email and the host sends no mail. |
REPORTEDIP_MODE | unset, which means drop | log or drop. Read by the setup step, so it answers the blocking question on a rollout instead of leaving it at the default. Same as --mode. |
REPORTEDIP_LOG_LEVEL | unset, which means warn | debug, info, warn or error, the log_level of the fresh config. A rollout that wants to read along on every host says info here instead of editing the config of every host afterwards. Anything else is an error before anything is written, because a config the parser refuses leaves the host with units that start and a binary that exits 2 on every command. Same as --log-level. |
REPORTEDIP_BAN | unset, which means on | true or false, whether this host bans what it catches in its own logs. Unset is a third state and not a false: only unset lets the question be asked. Anything that is neither is an error, because a typo in an automation variable would otherwise leave a fleet blocking nothing with nobody told. Same as --ban and --no-ban. |
REPORTEDIP_EXPERT | unset | 1 asks the eight granular groups. It needs a terminal; without one it is an error and not a silent plain install. Same as --expert. |
REPORTEDIP_FAIL2BAN_SOURCE | unset | 0 leaves fail2ban out as a source even when the host still runs it. For the migration this site documents, where the agent is installed while fail2ban is still up and fail2ban is removed afterwards: without it the setup writes a source that points at a log nobody writes any more. Harmless, the source has no channel and is skipped, but it shows up as a gap in a fleet check. |
# Binary only, no host setup: for an image or for Ansible. The
# terms of use are asked before the download, so the variable goes
# here as well.
curl -fsSL https://reportedip.com/agent/install.sh \
| REPORTEDIP_ACCEPT_TERMS=1 REPORTEDIP_KEY=<YOUR-KEY> REPORTEDIP_NO_SETUP=1 sh
# Later, on the running host, or from your playbook. The key goes
# in the environment and not in an argument, because
# /proc/<pid>/cmdline can be read by every local user of the host.
# sudo clears the environment, so the variable goes in front of it.
sudo REPORTEDIP_KEY=<YOUR-KEY> reportedip-agent install --accept-terms
Installing by hand
The setup step is a command of its own. Run it when REPORTEDIP_NO_SETUP=1 placed the
binary, when the detection guessed something you want to correct, or when a rollout has to answer
every question in advance. Anything passed as a flag is not asked for:
--key, --notify-email, --mode log|drop, --ban and
--no-ban, --log-level debug|info|warn|error, --admin-ip,
--ssh-port, --accept-terms, and --expert
for the granular questions. --key still works and prints one line saying that its value
is in the process list, where every local user of this host can read it for as long as the run takes;
pass the key as REPORTEDIP_KEY instead. sudo clears the environment by
default, so the variable belongs in front of sudo and not behind it. On a host that has no
configuration yet the run begins with the terms of use and goes on only after a yes; the
acceptance is recorded under /var/lib/reportedip-agent/terms-accepted with the version
of the text and the time, and doctor shows that line. A host that is already set up is
not asked again and is only told where the terms are.
# The key goes in the environment. sudo clears the environment by
# default, so the variable belongs in front of sudo; behind it the
# install finds no key and exits 2.
sudo REPORTEDIP_KEY=<YOUR-KEY> reportedip-agent install
# The detection could not find the SSH port (socket activation,
# a non-standard unit, a port from an include file):
sudo REPORTEDIP_KEY=<YOUR-KEY> reportedip-agent install --ssh-port 2222
# The session address is not where you administer this host from
# (a jump host, a console, a serial connection):
sudo REPORTEDIP_KEY=<YOUR-KEY> reportedip-agent install --admin-ip 203.0.113.0/24
# Arm the state mails at the same time:
sudo REPORTEDIP_KEY=<YOUR-KEY> reportedip-agent install --notify-email ops@example.org
# A rollout that answers everything, so nothing is asked and no
# terminal is needed. Works the same as five environment variables:
# REPORTEDIP_ACCEPT_TERMS, REPORTEDIP_KEY, REPORTEDIP_NOTIFY_EMAIL,
# REPORTEDIP_MODE, REPORTEDIP_BAN
sudo REPORTEDIP_KEY=<YOUR-KEY> reportedip-agent install --accept-terms \
--notify-email ops@example.org --mode drop --ban
# Everything the four questions do not cover, asked one group at a time:
sudo REPORTEDIP_KEY=<YOUR-KEY> reportedip-agent install --expert
--admin-ip takes a single address or a CIDR range and takes the place of the guessed session address; addresses that earlier install runs wrote
to the auto-whitelist stay on it. It is
worth using whenever the SSH session is not representative, because the guessed address is what
stands between you and your own block rule on day one. The whole install run has a five minute
deadline, so a hanging nft or systemctl ends with an error instead of a
process you have to interrupt.
A host without systemd
install does not run here. It writes the systemd units and enables them, so it refuses a
host where systemd is not pid 1 and writes nothing, rather than leaving a configuration behind that
nothing ever starts. The binary is a different matter: nothing in it depends on systemd, and
doctor reports the missing manager instead of failing. What is left is a setup by hand,
and two things have to be arranged on such a host.
# 1. The feed sync, from cron. Every 15 minutes is safe on any
# plan: the agent skips a fetch its own plan interval does
# not allow yet, so nothing is requested too often.
*/15 * * * * root /usr/local/bin/reportedip-agent sync >/dev/null 2>&1
@reboot root sleep 120 && /usr/local/bin/reportedip-agent sync
# 2. The watch daemon, from your init system. It runs in the
# foreground, logs to stderr and stops on SIGTERM.
/usr/local/bin/reportedip-agent watch
The reboot entry is not optional. ipset timeouts do not survive a reboot and neither do the sets
themselves, so without a sync after boot the host comes up with no rules and no restored bans. On a
systemd host that path is covered by reportedip-agent-sync.service being enabled for
multi-user.target, which runs after every firewall service and without the random
delay. Keep log_file set on a host without journald, because stderr may go nowhere
there.
Two consequences of that are easy to miss. sync needs a
/etc/reportedip-agent/config.yaml that nobody wrote for you, because the command which
writes one is the command that will not run here. And without the install ID the same command
generates, this host has no identity towards the API, so it never appears under
Agent Servers and it holds no server licence.
Last updated: · Maintained by the ReportedIP team