Scout — install procedure¶
Scout ships as a self-extracting per-host installer you download from the Hub, copy to the target server, and run once as root. It enrolls, installs a systemd service, and starts scanning. Three steps: download → copy → run.
Requirements¶
Supported platforms & dependencies¶
| Requirement | Specification |
|---|---|
| OS | Linux — the only target the Hub generates installers for. |
| Architecture | x86-64 (amd64) — the one prebuilt binary the Hub bundles (scout-linux-amd64). |
| Kernel | ≥ 3.2 (the Go 1.25 minimum for Linux). Any systemd-capable distro clears this. |
| libc | None. The Scout binary is statically linked (pure Go, CGO off) — it runs identically on glibc distros (Debian, Ubuntu, RHEL) and musl distros (Alpine). |
| Shared libraries | None. TLS and crypto are Go's standard library — no OpenSSL needed on the target. (This is also what lets Scout still negotiate TLS 1.0/1.1 for scanning, which a modern system OpenSSL cannot — see Scanning.) |
| Init system | systemd — the installer writes /etc/systemd/system/cyphers-scout.service and drives systemctl. The unit's hardening stanza uses directives up to systemd 245 (ProtectClock); an older systemd logs an "unknown key" warning for the newer directives and skips them — the service still runs. |
| Tools for the installer script | bash, awk, and coreutils (base64, tail, id, chmod, …) — present on any standard systemd distro. The installer fetches nothing from the network; everything it installs is embedded in the script. |
| Disk | ~29 MB for the installer file in transit; ~22 MB installed binary. The data directory grows by kilobytes (keys, certs, state). |
| Privileges | root — the installer refuses to run otherwise. It writes to the install root and /etc/systemd/system, and the service runs as root (see The service). |
One platform
Scout is built and supported for Linux x86-64 only.
Network — target host → Hub¶
| Port (default) | Direction | Purpose |
|---|---|---|
| 7443 (TLS) | target → Hub | one-time enrollment (POST /enroll) |
| 4222 NATS (mTLS) | target ↔ Hub | all commands, reports, acks, CSRs after enrollment |
| 8443 (HTTP) | target → Hub | public PKI reads: CA chain/root, CRL, OCSP |
These are defaults, not fixed ports — the actual ports are whatever the Hub
was configured with, and they reach the target baked into the installer via the
--hub / --hub-nats URLs (step 1).
The installer artifact¶
The downloaded install-<name>.sh (step 1 below) is the only file you need on
the target — it carries a live one-time enrollment token (see the warning in
step 1).
1. Download the installer (Hub UI)¶
From the Hub UI: Scouts → Add Scout opens the Add a Scout dialog.
Enter the name under Installer Scout name and click Download
installer. The browser downloads a single self-contained
install-<name>.sh with everything baked in: the Scout binary, the Hub
endpoints (enrollment + NATS), the CA pin, the acceptance-policy signer pin,
and a one-time enrollment token.
The downloaded file embeds a live enrollment token
The token is single-use and expires in 1 hour. Treat
install-<name>.sh as a secret in transit, and delete it after a
successful install.
The same dialog's other form — Scout name → Create enrollment — shows a raw install command instead (one-time credentials, expires in one hour), for hosts where the binary is already in place.
Generating from the CLI instead¶
The button runs installer/build.sh server-side; you can run the same
generator yourself — it ships in the installer/ directory of every Hub
deployment (release package, image, repo checkout) — with a token from the
Hub UI:
./build.sh --hub https://hub.corp:7443 --hub-nats nats://hub.corp:4222 \
--name web-01 --token <token-from-the-hub-ui> \
--policy-signer-fp sha256:<hex-from-the-hub-ui>
| Flag | Meaning | Example / format |
|---|---|---|
--hub |
The Hub's enrollment URL (HTTPS, on the Hub's dedicated TLS port — default 7443). A custom port goes right in the URL. | https://hub.corp:7443 |
--hub-nats |
The Hub's NATS URL (mTLS) — Scout refuses to start without it. Default NATS port 4222. | nats://hub.corp:4222 |
--name |
Display name for this Scout in the Hub UI. | web-01 |
--token |
The one-time enrollment token (issued by the Hub). | opaque string |
--ca-fingerprint |
The Hub CA pin (see Security). Optional for build.sh: when omitted it derives the pin from the Hub's CA chain, so the emitted installer is always pinned — enrollment refuses to run without one. |
sha256:ab12… |
--policy-signer-fp |
Required. The Hub's acceptance-policy signer fingerprint (see Security) — shown next to the CA fingerprint in the Hub UI's generated command. build.sh refuses to build without it: there is no auto-derive fallback, unlike --ca-fingerprint. Scout pins this fingerprint at enrollment, fetches the signer public key in the enroll reply, verifies it against the pin, and persists it as its local verifier key. |
sha256:ef01… |
--cert-dir |
A directory of certs to discover (repeatable; default /etc/letsencrypt/live) |
/etc/nginx/ssl |
--target |
An explicit host[:port] to scan (repeatable) |
example.com:443 |
--trust-store-path |
Accepted for back-compat; no longer collected — trust-store proof attestations were pruned from the wire vocabulary (repeatable; absolute path — a relative path refuses to start) | /usr/local/share/my-ca.crt |
--segment, --location |
Deployment metadata | production / us-east-1 |
The scout binary is built fresh, never committed
In a repo checkout build.sh builds the Scout binary from source on each
run (GOOS=linux GOARCH=amd64, stripped), so it can never embed a stale
leftover — the prebuilt installer/scout-linux-amd64 is git-ignored, not
committed. To embed a published binary instead (e.g. from
downloads.cyphrs.ai), set CYPHERS_SCOUT_BINARY_URL and the mandatory
CYPHERS_SCOUT_BINARY_SHA256 — build.sh fetches it, verifies the checksum,
and refuses an unverified download. A release package with no source tree
falls back to its bundled binary.
Acceptance policy is mandatory
A Scout will not connect to NATS without a verified, signed acceptance
policy — there is no optional or observe-only mode. Enrollment always
delivers a signed policy plus the Hub's signer public key in the same
reply; Scout verifies the key's fingerprint against --policy-signer-fp
before trusting anything it signs. If a Scout's local policy cache is
ever wiped, recovery is automatic re-enrollment: with a still-valid
re-enrollment token (persisted in state.json) it re-enrolls and gets a
fresh policy on its own; otherwise re-run with a fresh --token (and
--policy-signer-fp) from the Hub UI.
2. Copy it to the target¶
install-<name>.sh is a single, self-contained script — the Scout binary is
embedded inside it, so there's no separate tarball and nothing to unpack by
hand. Move that one file to the target host however you normally would:
3. Run it (as root)¶
Run it from anywhere on the target (e.g. /tmp) — the paths it writes are
absolute, independent of where you launch it.
Install locations are defaults, not rules. The install root defaults to
/opt/cyphers and the data directory to <install-dir>/data; both are
overridable per run with environment variables:
sudo /tmp/install-web-01.sh # → /opt/cyphers
sudo CYPHERS_INSTALL_DIR=/srv/cyphers /tmp/install-web-01.sh # custom install root
sudo CYPHERS_DATA_DIR=/srv/scout-data /tmp/install-web-01.sh # custom data dir only
An overridden data dir propagates everywhere it matters — the installer passes
--data-dir explicitly to both the enrollment run and the systemd unit, so the
service uses the same location.
The script then:
- Extracts the embedded binary to
<install-dir>/scout(mode0755). - Creates the data dir
<install-dir>/datawith mode0700. - Writes
<install-dir>/scout.env(mode0600) holding the Hub endpoints (HUB_REST,HUB_NATS) — the single place they live (see Re-pointing at a different Hub). - Enrolls: runs the agent in the foreground until
agent.crtappears (≤ 30s), then stops it. Enrollment is aPOST /enrollover TLS to the Hub's dedicated enrollment listener (default port 7443): Scout generates its key locally, pin-verifies and captures the Hub CA from the TLS handshake, then sends a CSR + the token and receives its signed client certificate. (The CA it writes toca.crtcomes from that verified handshake, not the reply.) - Writes
/etc/systemd/system/cyphers-scout.service(reads endpoints fromscout.envviaEnvironmentFile=). The unit provisions/var/log/cyphers-scout/(LogsDirectory=cyphers-scout) for the agent's log file — see Logging. systemctl daemon-reload && systemctl enable --now cyphers-scout.
What it installs (filesystem layout)¶
Paths below use <install-dir> — default /opt/cyphers (see
Run it for the override).
| Path | Mode | Contents |
|---|---|---|
<install-dir>/scout |
0755 |
agent binary (statically linked, single file) |
<install-dir>/scout.env |
0600 |
Hub endpoints (HUB_REST, HUB_NATS) — edit + restart to re-point |
<install-dir>/cert-dirs.conf |
0644 |
extra certificate directories (one absolute path per line) — edit + systemctl reload cyphers-scout to apply; kept across installer re-runs |
<install-dir>/targets.conf |
0644 |
the endpoints this box serves (one host or host:port per line; SERVE-DECL-1) — anchors the Manage Scout column, authorizes scans, and joins the scheduled cycle; re-read every cycle, so edits apply with no reload; kept across installer re-runs |
<install-dir>/data/ |
0700 |
data directory (holds creds, state.json, and Scout-issued/delivered certs under certs/) |
…/data/agent.crt |
0644 |
client certificate (public) |
…/data/agent.key |
0600 |
private key |
…/data/ca.crt |
0644 |
Hub CA trust root (captured from the pin-verified enroll handshake) |
…/data/state.json |
0600 |
agent id, re-enrollment token, SPIFFE URI, pinned CA fingerprint, segment/location/labels |
/var/log/cyphers-scout/ |
0700 |
JSON log file scout.log + rotated backups (0600) — see Logging |
Two layers of data-dir default
The Scout binary's built-in --data-dir default is /opt/cyphers/data
— running it by hand with no flags uses that. The installer always
passes --data-dir explicitly (derived from the install root or
CYPHERS_DATA_DIR), so what the service actually uses is whatever the
install chose.
The service¶
cyphers-scout.service is Type=simple, Restart=on-failure, RestartSec=30,
logging to the journal. It runs as root (no User=/Group=) with a
conservative sandbox stanza (PrivateTmp, ProtectKernelTunables/Modules/
ControlGroups/Clock, RestrictRealtime, LockPersonality).
First start right after enrollment
The Hub provisions this agent's NATS ACL moments after enrollment. A Scout
dialing inside that gap is rejected with an authorization violation; it
retries the initial connection for up to 90 seconds (logging
the Hub may still be provisioning this agent's ACL) before giving up to
systemd's restart loop. A rejection that persists past that window means
the agent genuinely isn't authorized — check its status on the Hub.
Operate it the normal systemd way:
systemctl reload cyphers-scout is also available: it re-reads
cert-dirs.conf and starts a scan cycle immediately (see Changing scan
configuration after install) —
nothing else about the running Scout changes.
Re-pointing at a different Hub¶
The Hub endpoints are not baked into the unit file — the unit reads
HUB_REST / HUB_NATS from <install-dir>/scout.env at start. To move a
Scout to a different Hub address, edit that file and restart; no reinstall:
Changing scan configuration after install¶
No reinstall — and no new enrollment token — is needed to change what an installed Scout scans or watches.
Certificate directories are the common change, and they have a dedicated
file: add one absolute path per line to <install-dir>/cert-dirs.conf and run
systemctl reload cyphers-scout — the Scout re-reads the file and starts a
scan cycle immediately (no restart, no NATS reconnect; a reload landing while
a cycle is already running joins that cycle, and the new directories show up
in the next one). A file that fails to
parse is rejected: the running Scout keeps its last-good list and logs the
error; at startup a bad file refuses to start.
Directories listed in cert-dirs.conf carry the same authority as
--cert-dir flags everywhere, not just for discovery: they are part of the
Scout's path policy for Hub-supplied paths, so renewal delivery may install
and re-point certificate files under them exactly as it may under a
flag-configured directory (see
Commands → Safety mechanisms).
The endpoints this box serves have a dedicated file: <install-dir>/
targets.conf, one host or host:port per line (# comments and blank
lines ignored; a bare host is :443). An entry there does three things at
once (SERVE-DECL-1): it declares that this box serves the endpoint —
the Hub mirrors the file on every report and anchors the Manage Scout
column on it, which is how one certificate installed on two boxes still
shows which box serves which hostname — it authorizes the host for
Hub-initiated scans (the scan_target command — see
Security), and it joins the
scheduled scan cycle. + Add endpoint on the Hub appends the line for
you (via the Scout) and Delete removes it; hand edits are always
honored and survive both. The --target unit flag is gone — a unit still
carrying it refuses to start, naming this file. The file is re-read
every cycle, so appending a line takes effect on the next cycle — no reload,
no restart:
Like cert-dirs.conf, a configured file that cannot be read at startup
refuses to start (fail-closed); a read failure while the Scout is running
keeps the last-good list rather than silently dropping coverage.
Everything else (--name, --cert-dir, …) is still an ExecStart= edit — except the
scan interval, which is not a flag at all: it is set on the Hub (Scouts → the Scout →
Configuration) and delivered to the Scout, which runs 6h until told otherwise. A unit
that still carries --interval refuses to start and says so.
| What | Where | Apply with |
|---|---|---|
Hub endpoints (HUB_REST, HUB_NATS) |
<install-dir>/scout.env |
systemctl restart cyphers-scout |
| Certificate directories | <install-dir>/cert-dirs.conf |
systemctl reload cyphers-scout |
| Served endpoints (declaration + scan targets) | <install-dir>/targets.conf |
nothing — re-read every scan cycle; the Hub follows on the next report |
| Scan interval | the Hub: Scouts → this Scout → Configuration | nothing — the Hub sends it; the Scout applies it and re-arms its timer |
Everything else (--name, --cert-dir, …) |
the ExecStart= line in /etc/systemd/system/cyphers-scout.service |
systemctl daemon-reload && systemctl restart cyphers-scout |
The enrollment credentials in the data directory are untouched by any of this — the Scout reconnects with its existing identity.
Upgrade¶
Upgrading a Scout is a binary swap: replace /opt/cyphers/scout and restart
the service. Enrollment credentials, delivered certificates and hook scripts all
live in the data directory and are never touched, so the Scout reconnects with
its existing identity — no re-enrollment, no token.
Upgrade the Hub first
The Hub↔Scout wire contract is strict on both sides
(deny_unknown_fields in cyphers-wire, DisallowUnknownFields() in
scout/pkg/wire). A newer Scout may report fields an older Hub does not
know, and the older Hub will then reject that report outright rather
than ignore the extra fields.
So the order is always: Hub, then Scouts. The reverse direction is safe — an older Scout simply omits newer fields, which the Hub treats as "not reported" rather than as an error. A fleet part-way through a rollout is therefore fine, as long as every Scout is no newer than the Hub.
Per Scout¶
# 1. Record what is running now — this is your rollback reference.
sha256sum /opt/cyphers/scout
/opt/cyphers/scout --version
# 2. Keep the current binary. `cp -a` preserves mode/ownership.
sudo cp -a /opt/cyphers/scout /opt/cyphers/scout.bak-$(date -u +%Y%m%dT%H%M%SZ)
# 3. Install the new binary from the release tarball's bin/scout.
sudo install -m 0755 ./bin/scout /opt/cyphers/scout
# 4. Restart and verify.
sudo systemctl restart cyphers-scout
systemctl is-active cyphers-scout
/opt/cyphers/scout --version
Then confirm the Scout came back on the Hub side — a service that is
active locally but never reconnects is the failure this check catches:
sudo journalctl -u cyphers-scout --since "2 minutes ago" \
| grep -E "NATS connected|policy|refus|error"
Expect a NATS connected to Hub line and an applied acceptance policy. In the
dashboard the Scout's Last seen should advance within a minute.
Never delete the data directory to 'clean up' before an upgrade
An upgrade replaces one file. {data-dir}/certs/ holds delivered
certificates and private keys that web servers are commonly pointed at
(often via symlinks), and {data-dir}/ also holds the enrollment identity.
Removing it turns a binary swap into a re-enrollment and can break a
running web server at its next reload. See the uninstall warning below.
Rolling a fleet¶
Do them one at a time, verifying each before starting the next. A Scout is the only thing observing its endpoints, so a broken upgrade rolled out in parallel blinds the whole estate at once instead of one host.
If a Scout fails to reconnect, roll it back immediately — the binary you saved in step 2 is the whole rollback:
sudo install -m 0755 /opt/cyphers/scout.bak-<timestamp> /opt/cyphers/scout
sudo systemctl restart cyphers-scout
Uninstall¶
To remove a Scout, stop + disable the service, delete
/etc/systemd/system/cyphers-scout.service and the install dir (default
/opt/cyphers/), and revoke/retire the agent from the Hub.
Check the data directory before deleting it
{data-dir}/certs/ is the Scout's certificate delivery directory —
when a renewal delivers a certificate, this is where it lands, and web
servers are commonly pointed (often via symlinks) straight at it. Deleting
the install dir can therefore remove the certificate and private key a
running web server is serving: it keeps working from memory until its next
reload, restart, or reboot — then fails. Before removing anything:
ls -laR /opt/cyphers/data/certs/ 2>/dev/null # anything delivered?
grep -rl /opt/cyphers /etc/nginx /etc/apache2 /etc/haproxy 2>/dev/null # anything pointing in?
If there are consumers, repoint them (or complete a renewal to a fresh install first) before deleting the directory. A deleted private key is not recoverable.