Auto-Update
The daemon includes an automatic update service that periodically checks for new releases from the FreeSDN control plane, downloads the binary, and restarts the service. The update pipeline is designed to be fail-closed - an unsigned release, a checksum mismatch, or a key fingerprint mismatch all abort the update.
How the update cycle works
Section titled “How the update cycle works”-
At a configurable interval (default 50 minutes, minimum 5 minutes), the daemon calls:
GET /api/v1/agents/updates/check?current_version=1.0.0&platform=linux&agent_type=daemonThe request includes
X-Agent-IDandX-Agent-Keyheaders. The endpoint returns 401 without them (information-disclosure mitigation). -
If
update_availableisfalse, the daemon logs and sleeps until the next interval. -
If an update is available, the server response includes:
{"update_available": true,"latest_version": "1.1.0","download_url": "/api/v1/agents/releases/3fa1b2c4-8d5e-4f6a-9b0c-1d2e3f4a5b6c/binary","checksum_sha256": "a3f7c891...","signature": "<base64-DER ECDSA-P256 signature>"} -
The daemon validates the download URL:
http://URLs are rejected outright- Relative paths (starting with
/) are resolved against the configured server URL - Absolute
https://URLs must match the server’s hostname - a different hostname is blocked as a potential SSRF
-
The binary is downloaded with a streaming 500 MB size cap.
-
The SHA-256 checksum is verified. A mismatch aborts the update and reports
checksum_mismatchto the control plane. -
The ECDSA-P256 signature is verified (see below). A missing or invalid signature aborts the update by default.
-
The new binary is staged atomically: written to a temp file in the same directory, the current binary backed up as
.bak, thenos.replace()(atomic on POSIX and NTFS). -
A rollback marker is written recording the previous version hash so recovery is possible if the new binary fails.
-
The daemon triggers a platform-specific restart:
- Linux:
systemctl restart freesdn-agent - macOS:
launchctl kickstart -k system/com.freesdn.agent - Windows:
sc stop FreeSDNAgent && sc start FreeSDNAgent(no automatic restart recovery is configured by default; both commands must be issued explicitly)
- Linux:
Signature verification
Section titled “Signature verification”SHA-256 alone is not sufficient - a compromised or MITM’d control plane could replace both the binary and its checksum simultaneously. The ECDSA-P256 signature requires the backend’s private signing key, which is never served publicly.
Verification flow
Section titled “Verification flow”1. Fetch the release public key: GET /api/v1/agents/releases/public-key → PEM-encoded ECDSA-P256 public key
2. Verify the public key fingerprint against the pinned value (see "Key pinning" below)
3. Verify: ECDSA-P256.verify( public_key, signature=base64.decode(response["signature"]), data=sha256_digest_bytes )The signing algorithm is ECDSA over NIST P-256 with SHA-256 (i.e. the signature is over the SHA-256 digest of the binary, not the raw binary).
Fail-closed by default
Section titled “Fail-closed by default”{ "daemon": { "auto_update_require_signature": true }}auto_update_require_signature defaults to true. When true:
- A release with no
signaturefield → refused (unsigned_release) - A release with an invalid signature → refused (
signature_mismatch) - A release where the public key fingerprint does not match the pin → refused (possible key-swap)
Set auto_update_require_signature: false only if your FreeSDN instance predates the signing infrastructure and cannot sign releases. This is explicitly not recommended.
Public-key pinning
Section titled “Public-key pinning”Fetching the public key from the server endpoint on every update check is not enough - a compromised endpoint could serve a different key signed with the attacker’s private key. FreeSDN uses trust-on-first-use (TOFU) pinning with an optional install-time override.
Install-time pin (recommended)
Section titled “Install-time pin (recommended)”Set release_public_key_sha256 in the daemon config to the SHA-256 fingerprint (hex) of the expected public key PEM:
{ "daemon": { "release_public_key_sha256": "a3f7c891deadbeef..." }}Get the fingerprint from your FreeSDN admin (Settings → Agent Releases → Public Key Fingerprint) and set it when deploying new agents. With this set, any key the server returns that does not match is rejected, even on the very first connection.
TOFU pin (default)
Section titled “TOFU pin (default)”If release_public_key_sha256 is not set, the agent uses trust-on-first-use:
- On the first update check that reaches signature verification, the fetched public key’s SHA-256 fingerprint is saved to
release_signing_key.sha256in the config directory. - On every subsequent check, the fetched key is compared against this saved fingerprint. A mismatch is rejected - a key swap is refused even across reboots.
Configuration reference
Section titled “Configuration reference”All update settings live under the daemon key in config.json:
{ "daemon": { "auto_update_enabled": true, "auto_update_interval": 3000, "auto_update_channel": "stable", "auto_update_require_signature": true, "release_public_key_sha256": null }}| Field | Default | Description |
|---|---|---|
auto_update_enabled | true | Enable or disable automatic updates entirely |
auto_update_interval | 3000 | Check interval in seconds (minimum 300, maximum 86400) |
auto_update_channel | "stable" | Release channel: "stable" or "beta" |
auto_update_require_signature | true | Reject updates with no or invalid ECDSA signature |
release_public_key_sha256 | null | Optional hex SHA-256 of the expected signing public key (install-time pin) |
Monitoring update status
Section titled “Monitoring update status”Update results are reported to the control plane as action_result WebSocket messages with "action": "auto_update". The agent detail page in the FreeSDN UI shows the current version and the last update check result.
If an update fails, the daemon logs the reason and continues running the current version. It will retry at the next interval.
Rollback
Section titled “Rollback”Automatic rollback
Section titled “Automatic rollback”The daemon performs automatic rollback at every startup. Before applying an update the updater writes a rollback marker (.freesdn-rollback next to the binary) recording the previous version string and SHA-256 hash. On the next startup, check_rollback_needed() reads this marker. If the daemon crashed within 60 seconds of the update being applied, the .bak binary is automatically restored and the daemon continues on the previous version - no operator action required.
The rollback marker is cleared automatically once the daemon sends its first successful heartbeat, confirming the new binary is healthy. Up to 3 automatic rollback attempts are allowed. After the third failure the daemon logs a CRITICAL message and requires manual intervention.
Manual recovery (if automatic rollback fails)
Section titled “Manual recovery (if automatic rollback fails)”If all three automatic attempts are exhausted (or the .bak file is missing), restore the previous binary manually:
# Linux - restore the backup manuallycp /opt/freesdn-agent/freesdn-agent.bak /opt/freesdn-agent/freesdn-agentsudo systemctl start freesdn-agentThe rollback marker (.freesdn-rollback in the same directory as the agent binary) records the previous version string and SHA-256 hash for verification.
Disabling auto-update
Section titled “Disabling auto-update”To pin an agent to a specific version, set auto_update_enabled: false in config.json:
{ "daemon": { "auto_update_enabled": false }}You can also update manually by downloading a release binary, verifying its checksum, replacing the binary, and restarting the service.