Author: Thorsten Schröpel · 🇩🇪 Deutsche Version
UpdateWatch2 Agent is the managed-endpoint half of UpdateWatch2: a .NET Worker Service, targeting both Windows and Linux from one codebase, that checks for OS updates, reports them (and whether a reboot is required) to the server, and installs them only on remote trigger — never rebooting on its own.
✅ Stable. The certificate-based onboarding, heartbeat, and self-update mechanics are implemented and tested end to end against a real running server. The real Windows Update API (WUApiLib) integration, the Linux
dnf/yumupdate path, and the Windows installer's install/uninstall behavior have not been verified against a real target host yet. See Project status below.
Companion repository: updatewatch2-server — the management server this agent reports to.
- Identified by hostname. On first contact the agent registers, pins the server's CA certificate (trust-on-first-use), and polls until an admin approves it and issues a client certificate.
- The certificate is stored securely (Windows: the machine certificate store, non-exportable; Linux:
/etc/updatewatch2/agent.pfx, owner-only) and presented on every request afterward. - Renews itself proactively before expiry, recovers automatically if an admin re-issues a fresh registration token — no service restart needed either way — and picks up a rotated CA root the moment the server reports it, rather than waiting out its normal schedule.
- A periodic alive message keeps the server's fleet overview current (last-seen, DNS name, OS, IP, agent version — all re-sent on every heartbeat, not just once at registration).
- Update checking runs on its own configurable interval with random jitter, so many agents don't hit the server at the same moment.
- Detects a protocol-version mismatch against the server and logs a warning, without hard-failing.
- Windows: the real Windows Update API (WUApiLib) via late-bound COM — search, download, and install, deliberately excluding driver updates by default, the same conservative default Windows Update's own UI uses. Can also proactively download pending updates ahead of an actual install trigger, gated by an admin-configurable, fleet-wide toggle (Settings → General on the server) — so an install applies from local cache instead of downloading at trigger time.
- Linux:
apt/dpkgon Debian-derived distros,dnf/yumon RPM-based ones, auto-detected at startup; falls back to a no-op checker if neither is present. - Installation never triggers a reboot itself — "reboot required" is always a separate, independently reported signal. A full machine reboot (not just this agent's own service) can still be triggered remotely by an admin, delivered and acknowledged the same way as a triggered install.
- Reacts to the server offering a newer agent release over the existing heartbeat channel — no separate poll loop.
- Downloads the update from the server itself, never GitHub directly, so an agent never needs its own internet access.
- Verifies the download's SHA-256 before ever applying it — a mismatch aborts and deletes the download without touching anything platform-specific.
- Windows: re-runs the NSIS installer silently. Linux:
dpkg -i/rpm -Uthe package, then restarts its own systemd service.
UpdateWatch2 was built with vibe coding: implemented and iterated on with Claude Code (Anthropic) in conversation, rather than hand-written line by line, driven by a human-authored architecture brief. The certificate lifecycle, registration/heartbeat/self-update protocol, and the Linux apt update-detection path have been run live against a real server and a real package cache, and are covered by an automated (xUnit) test suite. Some pieces are explicitly not yet live-verified against a real target host, called out as such in code comments: the Windows Update API (WUApiLib COM) integration, the Linux dnf/yum path (this project's own dev environment is Debian-based), the NSIS Windows installer's install/uninstall actually run through sc.exe/a package manager, and the arm64 Windows installer/agent (no Windows-on-ARM host has ever been available to this project — the win-arm64 publish itself has been confirmed to produce a genuine native ARM64 executable, just never run on a real device). The arm64 .deb/.rpm packages are a partial exception: each release's CI pipeline actually starts the published linux-arm64 binary on a native (not emulated) arm64 runner and confirms it runs its real startup/registration code before packaging it, so that much is live-verified on real arm64 hardware — install/upgrade through dpkg/rpm itself on an arm64 host is not, same as the existing x86_64 .rpm gap. Treat the pieces called out above as well-researched but not yet confirmed on a real target host — everything else has been live-verified end to end.
Every tagged release (release.yml, triggered on a vX.Y.Z push) builds and publishes installable packages as GitHub Release assets — no manual build needed.
Every release publishes two installers — UpdateWatch2Agent-Setup-<version>-x64.exe for regular (x64) Windows and UpdateWatch2Agent-Setup-<version>-arm64.exe for Windows-on-ARM devices (Snapdragon-based laptops, Surface Pro X, etc.), each bundling a native, self-contained publish for that architecture. Download the one matching your device and run it — everything below applies identically to both, just with the matching filename:
# Interactive install — prompts for the server address/port
UpdateWatch2Agent-Setup-0.12.0-x64.exe
# Unattended install (e.g. via a deployment tool)
UpdateWatch2Agent-Setup-0.12.0-x64.exe /S /SERVERADDRESS=updatewatch2.example.com /SERVERPORT=8796This installs and starts the UpdateWatch2 Agent Windows service, and writes the server address/port to HKLM\SOFTWARE\UpdateWatch2\Agent (ACL-restricted to Administrators/SYSTEM). Re-running the installer on top of an existing install performs an upgrade in place. The uninstaller removes the service, install directory, registry key, and (best-effort) this agent's own client certificate from the machine store.
By default, a freshly installed agent trusts whatever CA certificate the server hands it on its very first contact ("trust-on-first-use", TOFU) — a network attacker present at exactly that moment could intercept it and hand the agent a malicious CA instead. To close that window, download the server's current CA root certificate ahead of time — from the admin UI's Certificates tab ("Download CA root certificate"), or GET /api/admin/certificate-authority/download directly, both session-authenticated — and pass it to the installer via /CACERT=:
UpdateWatch2Agent-Setup-0.15.0-x64.exe /S /SERVERADDRESS=updatewatch2.example.com /SERVERPORT=8796 /CACERT=C:\temp\updatewatch2-ca.crtThis is optional and fully backward compatible — omit /CACERT= and the original TOFU behavior is unchanged.
Every release publishes both architectures — amd64/x86_64 packages for regular Linux hosts and arm64/aarch64 ones for arm64 hosts (AWS Graviton, Ampere Altra, Raspberry Pi, etc.), each bundling a native, self-contained publish for that architecture. Download the file matching your host's architecture (uname -m: x86_64 → the amd64/x86_64 package, aarch64 → the arm64/aarch64 one):
# Debian/Ubuntu, x86_64
sudo dpkg -i updatewatch2-agent_<version>_amd64.deb
# Debian/Ubuntu, arm64
sudo dpkg -i updatewatch2-agent_<version>_arm64.deb
# RHEL/Fedora/openSUSE, x86_64
sudo rpm -U updatewatch2-agent-<version>-1.x86_64.rpm
# RHEL/Fedora/openSUSE, arm64
sudo rpm -U updatewatch2-agent-<version>-1.aarch64.rpmThis installs to /opt/updatewatch2-agent/, seeds a starter /etc/updatewatch2/agent.conf if one doesn't already exist, and ships a systemd unit (updatewatch2-agent.service) — enabled but not started until you set a server address:
sudo nano /etc/updatewatch2/agent.conf # set "ServerAddress" (and "ServerPort" if not 8796)
sudo systemctl start updatewatch2-agentAn upgrade over an already-configured, already-running agent restarts the service automatically to pick up the new binary — no manual step needed.
.deb/.rpm packages have no install-time parameter mechanism, so there's no Linux equivalent of /CACERT= above — instead, place the same downloaded CA certificate at /etc/updatewatch2/ca.pem yourself before the first systemctl start updatewatch2-agent, to close the trust-on-first-use window the same way. postinst.sh normalizes its ownership/permissions (root:root, world-readable) if it finds one already there when the package installs.
Every key below is used verbatim in both places: as the registry value name under HKLM\SOFTWARE\UpdateWatch2\Agent on Windows, and as the JSON field name in /etc/updatewatch2/agent.conf on Linux.
| Setting | Key | Default | What it does |
|---|---|---|---|
| Server address | ServerAddress |
— (required) | Hostname/IP of the UpdateWatch2 server — must match its own UPDATEWATCH2_SERVER_HOSTNAME exactly; this agent validates the server's certificate SAN against it. |
| Server port | ServerPort |
8796 |
The server's agent-facing mutual-TLS port. |
| Update-check interval | UpdateCheckIntervalMinutes |
240 |
Base interval between OS-update checks, in minutes. |
| Update-check jitter | UpdateCheckJitterSeconds |
300 |
Random jitter (0..N seconds) added on top, so many agents don't hit the server at once. |
| Heartbeat interval | AliveIntervalMinutes |
5 |
How often this agent sends an alive message. |
| Log level | LogLevel |
INFO |
DEBUG/INFO/WARNING/ERROR. |
| Certificate renewal lead time | CertificateRenewalLeadTimeDays |
60 |
Days before its certificate's expiry that this agent proactively requests a fresh one. |
| Allow unauthenticated packages | AllowUnauthenticatedPackages |
false |
Linux only. Passes apt-get's --allow-unauthenticated / dnf's and yum's --nogpgcheck on install and pre-download, so a repository with an invalid or missing signature doesn't fail the whole transaction. Security-relevant — only enable this if you've deliberately decided to trust an unsigned/local repository; the usual fix for an "unauthenticated packages" error is importing that repository's GPG key, not this. Local-only, never pushed by the server. |
RegistrationToken and ClientCertificateThumbprint are also stored here but are managed automatically by the agent itself — never set these by hand except when placing a fresh token an admin gave you for re-issuance (see the server's admin UI). No service restart is required after changing any of these; the agent picks config changes up on its own maintenance/heartbeat cadence.
Placing a re-issuance token: just write the fresh
RegistrationToken— that's the only field you need to touch. As of agent v0.14.3,RegistrationWorkerdetects on its own that the config-store token differs from the one it already consumed, and automatically drops the still-locally-present old certificate (and clearsClientCertificateThumbprintfor you) before registering with the new one — this covers the "re-issue for an agent that's still running fine" case (suspected compromise, not loss), not just a genuinely lost/wiped certificate. On an older agent build, you must clearClientCertificateThumbprint(Windows registry) yourself, or delete/etc/updatewatch2/agent.pfx(Linux) — leaving the old value in place there means the fresh token silently sits unused:RegistrationWorkerkeeps finding the old certificate still present and never re-registers, and recovery only happens once the server rejects that old certificate enough times forHeartbeatWorker's self-heal to notice and drop it.
- .NET 10 Generic Host Worker Service, targeting Windows and Linux from one codebase — platform-specific pieces (registry vs. config file, WUApiLib vs.
apt/dnf, the two client-certificate stores) are selected at startup, not via separate build configurations. - Packaging: NSIS (Windows installer),
fpm(.deb/.rpm) from a systemd unit and pre/postinst scripts.
src/UpdateWatch2.Agent/ Certificates/, Communication/, Configuration/, SelfUpdate/, UpdateCheck/ (Windows/, Linux/), RegistrationWorker.cs, HeartbeatWorker.cs, UpdateCheckWorker.cs
tests/ xUnit — hand-written fakes, no mocking library
installer/nsis/ setup.nsi — the Windows installer
installer/linux/ systemd unit + postinst/prerm/postrm scripts, packaged via fpm
Requires the .NET 10 SDK.
dotnet build
dotnet test
dotnet run --project src/UpdateWatch2.Agent # runs in the foreground, not installed as a serviceSee CHANGELOG.md for a version-by-version history of notable changes.
Copyright (C) 2026 Thorsten Schröpel.
UpdateWatch2 Agent is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. See LICENSE, or https://www.gnu.org/licenses/agpl-3.0.html for the full text.
In plain terms: you're free to run, modify, and self-host UpdateWatch2. The one obligation AGPL adds on top of a regular GPL license is that if you run a modified version and let other users interact with it over a network, you must also offer those users access to your modified source code — not just people you hand a copy of the software to directly. Running an unmodified copy for yourself carries no extra obligation beyond the standard copyleft terms.
