# Self update

> HTML: https://remote.ivjn.us/docs/en/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:

```sh
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](/docs/en/platform-installation.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](/#downloads)) 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 `exec`s 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` (also `false` / `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_status` with `phase`:
  `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):

```sh
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 declares `REQUEST_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 `available` with an installation-pending message, not `applied`.
  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 `AgentService` is running.
- Remote `update_req` is 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_REPLACED` still 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, send `check_only: false` and 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.
