Self update
The CLI and standalone Agent can update their own executable from the published website on Windows x64/arm64, Linux x64/arm64 (glibc and static musl builds), macOS Apple Silicon, and FreeBSD/NetBSD x64/arm64:
remote-cli update self --check
remote-cli update self
remote-agent update self --check
remote-agent update self
| Platform | Release archive | Restart after a one-shot remote-agent update self |
|---|---|---|
| Windows x64 / arm64 | remote-windows-x64.zip / remote-windows-arm64.zip |
Scheduled task RemoteAgent, then process restart |
| Linux glibc x64 / arm64 | remote-linux-x64.tar.gz / remote-linux-arm64.tar.gz |
systemd system or user unit, OpenRC, then process restart |
| Linux musl x64 / arm64 | remote-linux-musl-x64.tar.gz / remote-linux-musl-arm64.tar.gz |
Same as Linux glibc |
| macOS Apple Silicon | remote-macos-arm64.tar.gz |
LaunchAgent us.ivjn.remote-agent |
| FreeBSD / NetBSD x64 / arm64 | remote-freebsd-*.tar.gz / remote-netbsd-*.tar.gz |
rc.d remote_agent from remote-agent install --system; otherwise restart manually |
The updater keeps the C runtime of the running executable: a musl build installs only the musl archive and a glibc build only the glibc archive. OpenWrt stays on opkg/apk even on x64/arm64: its packages are built without the archive updater, and a musl tarball running on a host with /etc/openwrt_release refuses to self-update. A platform updates only when its archive is listed in the manifest; installations built before support for that platform must be replaced manually once.
No account login or device configuration is required.
For a first install of the Agent itself (binary + private config + OS autostart), prefer remote-agent install / remote-agent uninstall on Linux, macOS, and Windows — see platforms.md. That command does not need network access; pairing remains separate. After the binary that includes install is on the machine once, later upgrades can use update self or auto-update where supported.
--check compares executable SHA-256 metadata from the manifest without downloading the archive. If executable metadata is absent, it downloads and verifies the archive. Hash comparison distinguishes development builds with the same version number; checking never changes the installation. Installation rejects downgrades and verifies the executable version, size and hash.
The default source is https://r2.ivjn.us (the R2 public domain that hosts downloads/). --source https://your-mirror.example/releases selects a trusted mirror with the same downloads/manifest.json and archive layout. GitHub Releases (mbcc2006/remote-control releases) publish the same class of desktop archives as an additional download channel; they do not replace the website layout, and self-update still uses r2.ivjn.us unless you point --source at a mirror that serves downloads/manifest.json. Only HTTPS is accepted, redirects are refused, and no account token is sent. The archive name is selected from the current OS/architecture, its size and SHA-256 are checked against the HTTPS manifest, and only the exact executable entry is extracted. Before trusting the manifest, desktop clients verify downloads/manifest.json.sig with compiled-in Ed25519 public keys. Missing signatures, changed bytes or unknown keys reject the update. Mirrors must serve the exact signed manifest and sidecar; publish the sidecar before rolling out clients that require it.
Before installation the new executable must pass a bounded --version check. Updates are serialized by a sibling .update.lock file, and the current executable is rechecked to detect concurrent replacement. The update touches the executable and its update/backup files only; device IDs, credentials and configuration files are preserved.
On macOS, the downloaded executable must also pass codesign --verify --strict before the version check. Its published signature and bytes are preserved during replacement so subsequent hash comparisons remain stable.
The previous executable is retained as <executable>.previous. If necessary, stop the Agent and restore that file to the original executable path. On Windows a loaded executable is renamed to the backup before installing its replacement; an installation failure restores the original name.
After a successful remote-agent update self (not --check), the updater tries to restart the running Agent so the new binary is loaded: on Linux (glibc and musl) it runs systemctl restart remote-agent, then systemctl --user restart remote-agent, then rc-service remote-agent restart when /etc/init.d/remote-agent exists (OpenRC), and if those are unavailable it SIGTERM-restarts other remote-agent daemon processes with their prior arguments; on Windows (x64 and arm64) it restarts the RemoteAgent scheduled task, with a process-level fallback; on FreeBSD/NetBSD it runs the rc.d script remote_agent restart installed by remote-agent install --system, and otherwise reports that a manual restart is needed. If the updating process itself is the daemon (arguments are not update self), it execs the new binary on Unix (preserving the PID for supervisors) or spawns a detached replacement on Windows. remote-cli update self never restarts an Agent. Status lines report whether restart used systemd, OpenRC, rc.d, a scheduled task, a newly spawned process, or could not be performed (the update itself still succeeded). Run with the permissions of the installation owner; the updater does not elevate privileges. Command lines logged during restart redact --token values. If a previous Windows Agent is still running from the backup, restart it before updating again.
Auto-update (desktop Agent daemon)
When the Agent daemon is running (not a one-shot update self), it periodically checks the same website manifest and applies an update when the published remote-agent binary differs (hash comparison). Defaults:
- Enabled by default on supported desktop builds (Windows x64/arm64 / Linux x64/arm64 glibc and musl / macOS arm64 / FreeBSD and NetBSD x64/arm64).
- Disable with environment
REMOTE_AUTO_UPDATE=0(alsofalse/no/off), JSON config"auto_update": false, or CLI--auto-update false. - First check ~90 seconds after start; then about every 12 hours (not on every heartbeat).
- Logs progress clearly; never sends the device token to the update CDN.
- OpenWrt (including a musl tarball on an OpenWrt host) skips desktop auto-update; Android uses its own OTA path. Unmapped architectures such as Intel Mac or 32-bit ARM return a missing-release-archive error and cannot auto-update.
Remote push update (operator → device)
Operators with an ordinary device session (same ACL as OpenExec) can ask an online device to check or apply an update:
Protocol (WebSocket JSON):
OperatorUp::update_device{ session_id, device_id, check_only, source? }- Exchange →
DeviceDown::update_req - Device → progressive
DeviceUp::update_status/OperatorDown::update_statuswithphase:checking|downloading|applying|up_to_date|available|applied|error
Console: on device detail, Check for updates / Push update.
CLI (same pattern as other device session ops):
remote-cli device update <device_id> --check
remote-cli device update <device_id>
remote-cli device update <device_id> --source https://r2.ivjn.us
LAN control maps update_device the same way as other OperatorUp open-* messages when LAN is enabled.
OpenWrt / other unsupported Agents return a clear error phase instead of installing the wrong artifact. Windows arm64, Linux musl, macOS Apple Silicon and FreeBSD/NetBSD use the same desktop archive path as Windows x64 and Linux glibc.
Android Agent OTA
The Android Agent APK (remote-agent-android.apk on the website) can update itself from the same HTTPS manifest:
Open Settings → Check for updates in the Agent to check manually. The check downloads and verifies the APK; an available update requires a separate confirmation before opening the Android installer.
- Download APK, verify bytes + sha256, compare
versionCode/ package name against the installed app (same-version rebuilds with a different hash may still install). - Install via
PackageInstaller. The app declaresREQUEST_INSTALL_PACKAGES; on first OTA the user may need to allow “install unknown apps” for Remote Agent in system settings, then confirm the system installer UI when shown. - Starting an install returns
availablewith an installation-pending message, notapplied. The private installer callback opens the system confirmation screen. Repeated requests for the same pending APK reuse the session; failed and stale self-update sessions are abandoned. Confirm the installed version/hash after the Agent reconnects. - Auto-check runs on a similar interval while
AgentServiceis running. - Remote
update_reqis handled by the native Agent, which calls the APK control bridge (action: ota_update). - App-private storage (device id / token) is preserved across package replacement;
MY_PACKAGE_REPLACEDstill restarts the connection when enabled.
All components now share release version 0.1.27: Rust Agent/CLI/Exchange, browser extension, Android apps and web console. Increment versions together; Android versionCode is 30. Run python scripts/check-release-versions.py before packaging.
Android Console updates
The native Console has its own Settings → Check for updates button, available before or after login. It reads the public website manifest without account credentials and compares the installed build with the published versionCode. If the manifest has no build metadata and the marketing versions match, it offers a download without claiming the installed app is current.
Download installer opens the website APK in the browser; finish installation through Android's confirmation screen. This updates the Console on the current phone. The update controls on a device detail page still target that remote device.
Console "Check for updates" (combined workflow)
Device details expose a single "Check for updates" action, which first sends update_device { check_only: true }:
up_to_date: show "Already on the latest version x.y.z".available: show "Current → latest (size)" and offer "Push update / Later". After confirmation, sendcheck_only: falseand show progress and the result.- If Android returns "Installation pending …" after a push, prompt the user to confirm the system installer on the device.
- Offline devices and browser devices show the reason directly; unsupported platforms show "This device does not support remote updates".
Result messages include current <版本> and size <字节> bytes.
See also repository docs/watchdog.md for the optional liveness watchdog (--watchdog, REMOTE_WATCHDOG=1).
Android Diagnostics shows the most recent automatic-update result and timestamp even without READ_LOGS permission. Fixed failure reasons distinguish HTTP status, DNS, timeout, TLS, manifest, APK size/hash and install permission failures. The same safe message is written to logcat as RemoteAgentOTA; full-device logs still require READ_LOGS. Exception URLs, paths and credentials are never copied into this status.