One Go Agent on Linux and OpenWrt: Fleetd Deployment Choices
How Fleetd v0.3.0 shares an agent across Linux and OpenWrt: amd64/arm64, systemd/procd, persistent state, firmware recovery and monitoring dependencies.
Installing an agent on a cloud server and on a home router initially looks like the same job: copy an executable, write a configuration file and start it. Long-term operation raises different questions. Who starts it after a reboot? What if the network is not ready? Where are the logs? Will device identity and command records survive a restart?
Fleetd manages Linux servers alongside OpenWrt and iStoreOS routers. After covering enrollment and browser-signed commands, this article focuses on making the agent a persistent, diagnosable system service. The implementation described is v0.3.0. Its installer supports amd64 and arm64, with systemd or OpenWrt’s procd; that does not mean every Linux-based device is supported.
One codebase, architecture-specific binaries
OpenWrt is Linux too. Connections, authentication, command authorization and basic kernel metrics can share code. Fleetd therefore does not maintain separate server-machine and router agents. It builds the same source for Linux, producing amd64 and arm64 executables.
“The same agent” means shared source and protocol, not an interchangeable binary. An amd64 executable cannot simply run on arm64.
| Reported architecture | Installer selection |
|---|---|
x86_64, amd64 |
amd64 |
aarch64, arm64 |
arm64 |
| Anything else | The current installer stops |
OpenWrt supports many more devices, including MIPS and 32-bit ARM, which this installation path does not cover. Go’s cross-compilation support is not a project compatibility guarantee. Adding an architecture also requires checking dependencies, artifacts, installer detection, updates and behavior on real hardware.
uname -m is a useful first check. The kernel, userspace and available system facilities still matter.
A single executable reduces runtime preparation
The agent is built with CGO_ENABLED=0, reducing reliance on the target’s dynamically linked C environment. Target machines need neither a Go compiler nor a Node.js or Python runtime to start the agent. That helps on routers with limited storage and a small software installation.
It does not remove every prerequisite. The network must be reachable, HTTPS needs a working certificate trust environment, configuration storage must be writable, and the kernel must provide the required facilities. Some collectors also call external programs: container information uses docker, while WireGuard information uses wg.
The useful distinction is between a lighter base deployment and optional capabilities that still have dependencies.
Detect the environment before installing a service
The installer follows this sequence:
- Require root and identify the CPU architecture.
- Detect systemd or an OpenWrt procd environment.
- Download the matching agent from your Fleetd server.
- Run its
versioncommand to check that the executable can at least start. - Create or preserve configuration, install the binary, and register and start its service.
- Print the device fingerprint for verification and approval in the dashboard.
Downloading successfully only proves that a file arrived. Running version also checks basic executability. Neither proves that long-lived connections, collection, permissions, boot startup or firmware-upgrade recovery work correctly.
The installer prefers curl and falls back to wget. On a minimal system, check that the installed tool supports the required HTTPS requests. A universal package-manager command would be misleading because OpenWrt versions and firmware bundles differ.
Initial installation has a specific trust boundary. The server extracts the executable from an uploaded release bundle whose signature it verifies. The installation script trusts that server and HTTPS; it does not independently verify the downloaded binary’s signature on the target. Later agent-verified update bundles use a different flow, covered in the update article.
systemd and procd supervise the same process
After installation, the existing service manager owns the process lifecycle.
| Item | systemd machine | OpenWrt / procd machine |
|---|---|---|
| Executable | /usr/local/bin/fleetd-agent |
/usr/bin/fleetd-agent |
| Configuration | /etc/fleetd/agent.yaml |
/etc/fleetd/agent.yaml |
| Service definition | /etc/systemd/system/fleetd-agent.service |
/etc/init.d/fleetd-agent |
| State on a fresh install | /var/lib/fleetd |
/etc/fleetd |
| Logs | systemd journal | OpenWrt system log |
The systemd unit points ExecStart at the agent, uses Restart=always with a five-second restart delay, and orders startup against network-online.target.
The OpenWrt branch creates a USE_PROCD=1 init file with a boot order, process command and respawn settings. Standard output and standard error go to the system log. Both branches run the same agent entry point and read the same kind of YAML configuration. Distribution-specific details remain in installation and service integration rather than spreading through the connection protocol.
There are two recovery mechanisms: the service manager handles process exit; the agent handles connection loss. An unreachable server need not kill the process: the agent can retry with backoff. Conversely, reconnection code cannot run after its process exits.
Startup order is not proof of remote availability either. A network target or late boot position cannot ensure DNS, the upstream connection and the Fleetd server are all ready. Handling connection failure remains necessary.
On OpenWrt, storage location matters
This difference directly affects the replay protection discussed previously. /var/lib/fleetd is normally suitable for persistent state on a server. In common OpenWrt layouts, /var relies on temporary RAM storage. Restarting a process and rebooting the device have different consequences for files there.
The OpenWrt installer therefore sets state_dir to /etc/fleetd. Three files have distinct roles:
| File | Purpose |
|---|---|
agent.yaml |
Connection, trusted keys, permissions and state-directory settings |
agent.key |
The agent’s own identity key |
seen.json |
Accepted request IDs for replay protection |
The first two default to /etc/fleetd. seen.json follows state_dir: /var/lib/fleetd on a fresh systemd installation and /etc/fleetd on a fresh OpenWrt installation.
Regenerating a lost identity key produces a different device identity. Losing replay records also removes the basis for promising that previously accepted requests still within their validity window will be recognized as duplicates.
Ask whether the configured path actually maps to persistent storage, not just whether writing a file succeeds. Custom firmware, read-only layouts and unusual mounts need their own checks.
Flash persistence also has a write cost. The current agent updates replay records when it accepts new authorized requests; it does not write each ten-second metrics report into this file. If it evolves into a high-frequency task executor, persistence frequency and flash characteristics will need reassessment. There is no measured endurance or throughput guarantee here.
Preserving configuration does not restore the service automatically
The OpenWrt installer adds /etc/fleetd/ to /etc/sysupgrade.conf. That helps retain configuration and identity during upgrade procedures that support and enable configuration preservation. It does not also guarantee retention of /usr/bin/fleetd-agent or /etc/init.d/fleetd-agent.
After upgrading firmware, configuration may survive while the executable or init file needs reinstalling. Factory resets and upgrades explicitly discarding configuration are outside that preservation mechanism.
Before treating recovery as complete, check:
- Whether configuration and identity survived.
- Whether the executable and service definition exist and still work.
- Whether the restored agent has the same fingerprint.
- Whether missing reports reflect a stopped service, unavailable network or pending approval.
These are recommended checks, not a claim that a firmware-flashing experiment was performed for this article.
Rerunning the current installer preserves an existing agent.yaml, replaces the executable and rewrites the service definition. That is useful for recovery, but newly supplied --permission, --server and --signer options will not automatically override the preserved configuration. An old, incorrect OpenWrt state path will not fix itself either. Inspect the configuration and restart the agent after changing it.
A shared agent does not mean identical monitoring coverage
Basic metrics primarily come from Linux /proc: CPU, memory, load, uptime and cumulative interface traffic. These interfaces provide a common foundation. CPU utilization and network rates need two samples. The first sample after startup is not a complete measurement interval. If an interface is recreated and counters decrease, the agent skips that rate calculation instead of reporting an invalid difference.
Extended collection depends on the environment:
| Collector | Current dependency |
|---|---|
| Failed services | systemctl list-units --state=failed |
| Containers | docker ps -a |
| WireGuard peers | wg show all dump |
Running under procd does not mean procd service-health collection is implemented. Failed-service collection still uses systemd. If a tool is absent or fails, that collector returns no data while basic monitoring can continue. This avoids making Docker a requirement for every router, but missing fault data does not prove that every service is healthy.
The current implementation does not fully explain every missing-capability condition. Troubleshooting must distinguish an inapplicable feature, missing tool, failed invocation and genuinely empty result. They are not all “zero faults.”
Basic reports are scheduled every ten seconds. Disks, services, containers and WireGuard are collected every six reporting rounds. That is a scheduling policy, not a strict real-time guarantee: external commands, network problems and system load can affect timing.
Validate the operating chain
For a resident agent, the important chain is startup, supervision, reconnection and preservation of identity and configuration after reboot. On systemd, start with status and recent logs:
systemctl status fleetd-agent
journalctl -u fleetd-agent -n 50 --no-pager
On OpenWrt, inspect the system log:
logread -e fleetd
These are diagnostic commands, not device reboots. To apply changed configuration, restart the agent with systemctl restart fleetd-agent on systemd or /etc/init.d/fleetd-agent restart on OpenWrt.
Then verify approval and advancing report timestamps in the dashboard, allowing subsequent CPU and network samples to arrive. A monitoring-only device can remain on monitor; validation does not require enabling root commands.
Schedule whole-device reboots and firmware-upgrade checks in a maintenance window, especially on a router providing your internet or remote access. Returning automatically with the same fingerprint after a controlled reboot is stronger operational evidence than repeatedly running version.
The project’s CI includes tests and amd64/arm64 builds. Successful cross-compilation does not validate every OpenWrt image on real hardware. This article provides no measured RAM, CPU-overhead or minimum-flash figures; check free space and observe your own workload.
Fleetd shares protocol and agent logic while keeping service-manager and filesystem differences explicit. That reuses code without treating a router as a small server with an identical filesystem. Next, we will look at making alerts useful once metrics are being collected.
Discussion
Share a question or your experience. Comments appear after moderation. Your email is never displayed publicly. Comment privacy
Loading comments…