---
title: Before you run our server agent as root: rebuilt, signed, checked
description: Our open-source server monitoring agent installs with one sudo command. Here is how each release is rebuilt byte for byte, signed, and checked on your server.
canonical: https://watchfor.io/blog/server-agent-signed-reproducible-releases
---

# Before you run our server agent as root: rebuilt, signed, checked

2026-10-20 · WatchFor Team · Engineering · engineering, security, servers, devops

Our open-source server monitoring agent installs with one sudo command. Here is how each release is rebuilt byte for byte, signed, and checked on your server.

Somebody on your team wants to watch the production database server from
the inside: CPU, memory, disk, the processes eating them. They paste a
line from a vendor's docs into a ticket:

```sh
curl --proto '=https' --tlsv1.2 -fsSL https://watchfor.io/agent/install.sh | sudo sh -s -- --token <host-token>
```

And you, the person who gets paged when that box falls over, read it the
way you should read any `curl | sudo sh`: *who wrote this, what will it
run as, what does it talk to, and how would I know if the file I just
downloaded is not the file they published?*

This post is our answer for
[watchfor-agent](https://github.com/watchfor-io/agent), the open-source
server monitoring agent behind [WatchFor server monitoring](/server-monitoring).
Everything below is in the public repository, so you don't have to take our
word for any of it. We'll show you where to check.

## What it does once it's running

The short version, from the repository's `SECURITY.md`:

- **Outbound HTTPS to one URL, with a token. Nothing listens.** There is no
  port to open in a firewall and no agent API to protect.
- **It reads `/proc`, `/sys` and mount usage.** It runs nothing from its
  configuration and never accepts commands from the server. The server's
  whole reply to a batch of metrics is how many samples it kept, the
  shortest interval your plan allows, and the newest release version.
- **The token lives in a file that must be mode 0600** (or in an
  environment variable). It never goes to logs or to the on-disk spool. A
  world-readable token file is a start-up error, not a warning.
- **The service runs as an unprivileged user** with `ProtectSystem=strict`,
  an empty capability set, `NoNewPrivileges`, and caps on memory and tasks.
- **The host id is a hash of `/etc/machine-id`,** not the id itself.

Root is needed to install it (create the user, write the unit), not to run
it.

If you'd rather see the data before trusting anything, download the binary
and ask it what it would send. `check` needs no token and no config file,
and sends nothing:

```sh
watchfor-agent check    # collect once, print the batch, send nothing
```

## How a release is made

The part people rarely ask about, and should: how does the binary on the
release page come to exist, and who could change it on the way?

A release starts when a version tag is pushed. Every change to `main` goes
through a pull request first, and a published release is never rewritten:
a fix ships as a new version. From the tag, the release workflow runs four
jobs, and the split between them is the point:

![How a watchfor-agent release is made and checked: build makes an unsigned draft and never sees the key; reproduce rebuilds both architectures on a fresh runner and compares the draft byte for byte; a person approves; sign is the only job that gets the key; publish checks the draft is unchanged, verifies the signature with the tagged agent's own key, attests provenance and only then publishes. On your server the installer, the agent's verify command and every upgrade check the signature again](/blog/server-agent-signed-reproducible-releases-pipeline.svg)

1. **build** compiles static binaries for amd64 and arm64 and uploads them
   as an *unsigned draft* release. It never sees the signing key.
2. **reproduce** starts on a fresh runner, rebuilds both binaries from the
   tag, and compares them with the draft byte for byte. It also checks that
   `checksums.txt` lists exactly the expected files and that the installer
   in the draft is the one in the tag. One byte off and the release stops
   there.
3. **sign** waits for a person to approve it. It is the only job that gets
   the key, and it checks out no release code, runs no third-party actions
   and has no write token. It signs the `checksums.txt` that the previous
   job verified, and nothing else.
4. **publish** checks that the draft still holds exactly what was signed,
   verifies the signature with the key built into the tagged agent itself,
   attaches a GitHub build-provenance attestation to every asset, and only
   then turns the draft into a release.

Two smaller decisions make that chain hold. Every GitHub Action in the
workflow is pinned to a full commit SHA, not a tag that could be moved. And
the two tools the pipeline installs, GoReleaser and minisign, are pinned to
exact versions and fetched with `go install`, so their contents are checked
against the Go checksum database: a re-tagged or tampered release of either
can't slip in.

## Reproducible means you can rebuild it

"Reproducible build" is easy to claim, so here's what it means for this
agent: the binaries are static, built with `CGO_ENABLED=0` and `-trimpath`,
and the version is the only thing stamped in at build time. Build the same
tag the same way and you get the same bytes.

We tried it on a laptop for this post, against 0.9.0 as published:

![A terminal: cloning watchfor-io/agent at tag v0.9.0, building it with CGO_ENABLED=0, -trimpath and the release's linker flags, and sha256sum printing the same hash, be8b486d…91d7, for the published binary and the rebuilt one; cmp prints IDENTICAL](/blog/server-agent-signed-reproducible-releases-reproduce.webp)

That is the same comparison the `reproduce` job makes on every release,
for both architectures, before anything is signed. You can make it
yourself for any version: the exact build command is in the release
workflow.

## How you check a download

Every release ships `checksums.txt` and its signature,
`checksums.txt.minisig`. The agent checks both with the public key compiled
into it, so a host needs no extra tools:

```sh
tar -xzf watchfor-agent_0.9.0_linux_amd64.tar.gz watchfor-agent
./watchfor-agent verify -checksums checksums.txt watchfor-agent_0.9.0_linux_amd64.tar.gz
```

Here it is on the real 0.9.0 files, and again after changing a single byte
of the tarball:

![A terminal: watchfor-agent verify prints signature ok for checksums.txt (watchfor-agent 0.9.0 checksums) and sha256 ok for the tarball; after one byte of the tarball is changed the same command prints sha256 mismatch and exits with status 1](/blog/server-agent-signed-reproducible-releases-verify.webp)

The signature is in minisign format, so you can also check it with the
standalone `minisign` tool and the key printed in the README, without
trusting our binary to check itself:

```sh
minisign -Vm checksums.txt -P RWTUApo01PH7RyjD76wN2Vu7l5sO7Ys5psNQE9I7QYdWVfWSf0CetQje
```

And the build-provenance attestation ties each file to the workflow run
that produced it:

```sh
gh attestation verify watchfor-agent_0.9.0_linux_amd64.tar.gz --repo watchfor-io/agent
```

## What the one-line installer actually trusts

Back to the line in the ticket. The installer needs no verification tools
on the machine, and it has no switch to skip verification.

The tarball comes from GitHub's release storage. The checksums it is
checked against come from watchfor.io, which serves a release's
`checksums.txt` only after verifying its signature. So a file replaced on
GitHub alone can't pass, and a forged watchfor.io alone has no tarball to
serve. Then the freshly downloaded agent verifies the release signature
again, with the key built into it.

Here's the part we'd want spelled out if we were reading this: **a first
install rests on TLS to watchfor.io**, the same channel that delivered the
install command. There is no way around trusting *something* the first
time. From then on, the installed agent's own key does the checking, and no
server is trusted for what gets installed. If that first step bothers you,
skip the script: download the release by hand, verify it as above, and
install the binary yourself. The README walks through it, including the
systemd unit.

## Upgrades keep the same chain

The server tells a running agent when a newer release exists. That's a
hint, not an instruction: the daemon logs it and writes it to a small file,
and that's all. The running collector never replaces itself.

An upgrade is a separate step run as root, either by you
(`sudo watchfor-agent upgrade`) or by an opt-in daily timer. Either way it
installs a version only when all of this holds:

- `checksums.txt` carries a valid signature by a key compiled into the
  **installed** binary;
- the signature's trusted comment names that exact version;
- the archive's sha256 matches the signed list;
- the new binary, run once, reports the version it should.

Only then does it swap the binary in, with one atomic rename. It refuses to
downgrade unless told to. It talks HTTPS to watchfor.io and GitHub's
release hosts only, and refuses redirects anywhere else. So whoever
controls the server can, at most, delay an update or trigger one to a real,
newer, signed release: never install anything unsigned, and never roll you
back.

Keys can be rotated without breaking that chain: the agent trusts a short
list of release keys, and a new key ships inside a release signed by the
old one.

## What we can't promise

No build pipeline makes code correct. What this setup does is make the
binary on your server provably the code in the public tag, signed only
after a second look. Whether that code is good is something you can judge,
and we'd rather you did: outside the Go standard library the agent itself
uses two dependencies (`gopkg.in/yaml.v3` and `golang.org/x/crypto`), plus
the Charm libraries for its interactive screen, and `make lint` runs vet,
staticcheck, govulncheck and gosec.

If you find something, report it privately through GitHub's vulnerability
reporting on the repository, or by email to hello@watchfor.io with
"security" in the subject. You'll get an answer within three working days.

## Try it

- Read the code and the release workflow:
  [github.com/watchfor-io/agent](https://github.com/watchfor-io/agent)
- What the agent collects and how alerting works:
  [Server monitoring](/server-monitoring) and [the agent](/agent)
- Create a host token in the dashboard, then run the install line at the
  top, or install it by hand after verifying the release.

---

Canonical page: https://watchfor.io/blog/server-agent-signed-reproducible-releases · All articles: https://watchfor.io/blog/all.md · Site guide: https://watchfor.io/llms.txt
