# 自更新

> HTML: https://remote.ivjn.us/docs/zh-CN/self-update/

Windows x64/arm64、Linux x64/arm64（glibc 与静态 musl 构建）、macOS Apple Silicon 以及 FreeBSD/NetBSD x64/arm64 上的 CLI 与独立 Agent 可以从发布网站更新自身的可执行文件：

```sh
remote-cli update self --check
remote-cli update self
remote-agent update self --check
remote-agent update self
```

| 平台 | 发布包 | 一次性 `remote-agent update self` 后的重启方式 |
| --- | --- | --- |
| Windows x64 / arm64 | `remote-windows-x64.zip` / `remote-windows-arm64.zip` | 计划任务 `RemoteAgent`，其次重启进程 |
| Linux glibc x64 / arm64 | `remote-linux-x64.tar.gz` / `remote-linux-arm64.tar.gz` | systemd 系统或用户单元、OpenRC，其次重启进程 |
| Linux musl x64 / arm64 | `remote-linux-musl-x64.tar.gz` / `remote-linux-musl-arm64.tar.gz` | 与 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` | `remote-agent install --system` 注册的 rc.d `remote_agent`；否则需手动重启 |

更新器保持当前可执行文件的 C 运行库：musl 构建只安装 musl 包，glibc 构建只安装 glibc 包。OpenWrt 即使是 x64/arm64 也继续使用 opkg/apk：其软件包不含归档更新器，在存在 `/etc/openwrt_release` 的主机上运行的 musl 包也会拒绝自更新。只有 manifest 中列出对应发布包的平台才会更新；在支持该平台之前构建的安装需手动替换一次。

无需登录账号或配置设备。

首次安装 Agent 本体（二进制文件、私有配置和系统自动启动）时，优先使用 `remote-agent install` / `remote-agent uninstall` ，适用于 Linux、macOS 和 Windows，参见 [platforms.md](/docs/zh-CN/platform-installation.md)。 该命令不需要网络访问；配对仍需单独完成。包含 `install` 的二进制文件在机器上安装一次后，后续升级可以使用 `update self` ，或在支持的平台上使用自动更新。

`--check` 优先读取清单中的可执行文件 SHA-256，无需下载完整归档；清单缺少可执行文件元数据时才下载归档验证。哈希比较能区分版本号相同的开发构建，检查不会修改安装。安装前拒绝降级，并核对可执行文件版本、大小和哈希。

默认来源是 `https://r2.ivjn.us` （托管 `downloads/`的 R2 公共域名）。 `--source https://your-mirror.example/releases` 用于选择可信镜像，镜像需提供相同的 `downloads/manifest.json` 和归档布局。GitHub Releases ([mbcc2006/remote-control releases](/#downloads)) 作为**额外的**下载渠道发布同类桌面归档包；它们**不会**替代网站布局，自更新仍使用 `r2.ivjn.us` ，除非将 `--source` 指向提供 `downloads/manifest.json` 的镜像。仅接受 HTTPS，拒绝重定向，不发送账号令牌。系统根据当前操作系统和架构选择归档名称，按照 HTTPS 清单校验其大小和 SHA-256，并仅提取准确匹配的可执行文件条目。桌面客户端先用内置 Ed25519 公钥验证 `downloads/manifest.json.sig`，再信任清单。缺失签名、内容篡改或未知密钥都会拒绝更新。镜像必须提供原样的已签名清单与签名文件；启用强制验签客户端前先发布签名文件。

安装前，新可执行文件必须通过有时间限制的 `--version` 检查。更新通过同目录中的 `.update.lock` 文件串行执行，并重新检查当前可执行文件以检测并发替换。更新只修改可执行文件及其更新/备份文件；设备 ID、凭据和配置文件均会保留。

在 macOS 上，下载的可执行文件还必须先通过 `codesign --verify --strict` ，再检查版本。替换时保留发布时的签名和字节，使后续哈希比较保持稳定。

旧可执行文件保留为 `<executable>.previous`。 必要时，停止 Agent 并将该文件恢复到原可执行文件路径。在 Windows 上，安装替换文件前会将正在加载的可执行文件重命名为备份；安装失败时会恢复原名称。

成功执行 `remote-agent update self`（非 `--check`）后，更新器会尝试重启正在运行的 Agent 以加载新程序：Linux（glibc 与 musl）上依次执行 `systemctl restart remote-agent`、`systemctl --user restart remote-agent`，存在 `/etc/init.d/remote-agent` 时再执行 `rc-service remote-agent restart`（OpenRC），都不可用时以原参数 SIGTERM 重启其他 `remote-agent` 守护进程；Windows（x64 与 arm64）上重启计划任务 `RemoteAgent`，并有进程级回退；FreeBSD/NetBSD 上执行 `remote-agent install --system` 安装的 rc.d 脚本 `remote_agent restart`，没有时提示需手动重启。若执行更新的进程本身就是守护进程（参数不是 `update self`），Unix 上会 `exec` 新程序（保留 PID 供服务管理器使用），Windows 上会启动一个分离的替代进程。`remote-cli update self` 不会重启 Agent。状态行会说明重启使用了 systemd、OpenRC、rc.d、计划任务、新启动的进程，或未能重启（更新本身仍然成功）。请以安装所有者的权限运行；更新器不会提权。重启时记录的命令行会隐藏 `--token` 的值。如果旧的 Windows Agent 仍在从备份文件运行，请先重启它再更新。

## 自动更新（桌面 Agent 守护进程）

当 Agent 守护进程运行时（非单次 `update self`），它会**定期检查**相同的网站清单，并在发布的 `remote-agent` 二进制文件不同时应用更新（通过哈希比较）。默认行为：

- 在支持的桌面构建上**默认启用**（Windows x64/arm64 / Linux x64/arm64 glibc 与 musl / macOS arm64 / FreeBSD 与 NetBSD x64/arm64）。
- 可通过环境变量 `REMOTE_AUTO_UPDATE=0` （也接受 `false` / `no` / `off`）、JSON 配置 `"auto_update": false`，或 CLI 参数 `--auto-update false` 禁用。
- 启动约 90 秒后首次检查；之后约每 12 小时检查一次（并非每次心跳都检查）。
- 清楚记录进度；**绝不**向更新 CDN 发送设备令牌。
- OpenWrt（包括在 OpenWrt 主机上运行的 musl 包）跳过桌面自动更新；Android 使用自己的 OTA 路径。Intel Mac、32 位 ARM 等未映射的架构会返回无发布包错误，不能自动升级。

## 远程下发更新（操作员 → 设备）

拥有普通设备会话的操作员（ACL 与 `OpenExec`相同）可以要求在线设备检查或应用更新：

协议（WebSocket JSON）：

- `OperatorUp::update_device` `{ session_id, device_id, check_only, source? }`
- Exchange → `DeviceDown::update_req`
- 设备 → 持续返回 `DeviceUp::update_status` / `OperatorDown::update_status` ，包含 `phase`：
  `checking` | `downloading` | `applying` | `up_to_date` | `available` | `applied` | `error`

控制台：在设备详情中选择 **检查更新** / **下发更新**。

CLI（与其他设备会话操作方式相同）：

```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 时，局域网控制对 `update_device` 的映射方式与其他 `OperatorUp` open-* 消息相同。

OpenWrt 和其他不支持的 Agent 会返回明确的 `error` 阶段，而不会安装错误的产物。Windows arm64、Linux musl、macOS Apple Silicon 与 FreeBSD/NetBSD 使用与 Windows x64、Linux glibc 相同的桌面归档路径。

## Android Agent OTA

Android Agent APK（`remote-agent-android.apk` 位于网站上）可以通过同一 HTTPS 清单更新自身：

在 Agent 中打开 **Settings / 设置 → 检查更新** 手动检查。检查会下载并验证 APK；发现更新后，还需单独确认才会打开 Android 安装器。

- 下载 APK，验证**字节大小与 sha256**，将 `versionCode` 和包名与已安装应用比较（版本相同但哈希不同的重新构建仍可能安装）。
- 通过 `PackageInstaller` 安装。应用声明了 `REQUEST_INSTALL_PACKAGES`；首次 OTA 时，用户可能需要在系统设置中允许 Remote Agent“安装未知应用”，并在系统安装器界面出现时确认。
- 开始安装会返回 `available` 和等待安装的消息，而非 `applied`。
  私有安装器回调会打开系统确认界面。对同一个待安装 APK 的重复请求会复用会话；失败和过期的自更新会话会被放弃。Agent 重新连接后，请确认已安装的版本和哈希。
- 当 `AgentService` 运行时，自动检查会以类似间隔执行。
- 远程 `update_req` 由原生 Agent 处理，并调用 APK 控制桥 （`action: ota_update`）。
- 替换安装包时会保留应用私有存储（设备 ID / 令牌）； `MY_PACKAGE_REPLACED` 在启用时仍会重启连接。

所有组件统一使用发布版本 0.1.27：Rust Agent/CLI/Exchange、浏览器插件、Android 应用和 Web 控制台。后续一起递增版本，Android versionCode 为 30。打包前运行 `python scripts/check-release-versions.py`。

## Android 控制台更新

原生控制台有独立的**设置 → 检查更新**按钮，登录前后均可使用。它不携带账号凭据读取公开网站清单，并将已安装构建与发布的 `versionCode` 比较。如果清单不包含构建元数据且对外展示的版本一致，它会提供下载，而不会声称已安装应用是最新版本。

**下载安装包**会在浏览器中打开网站 APK；请通过 Android 的确认界面完成安装。该操作更新当前手机上的控制台。设备详情页的更新控件仍针对相应的远程设备。

## 控制台“检查更新”（合并流程）

设备详情只保留一个“检查更新”操作：先发 `update_device { check_only: true }`；
- `up_to_date` → 显示“已是最新版本 x.y.z”；
- `available` → 显示“当前 → 最新（大小）”，用户选择“下发更新 / 稍后”，确认后发 `check_only: false` 并显示进度与结果；
- Android 下发后返回 “Installation pending …” 时提示在设备上确认系统安装；
- 设备离线、浏览器设备直接给出原因；平台不支持时显示“此设备不支持远程更新”。
结果消息附带 `current <版本>` 和 `size <字节> bytes`。

另请参阅仓库中的 `docs/watchdog.md` ，了解可选的存活监视程序 （`--watchdog`、`REMOTE_WATCHDOG=1`）。

Android 诊断页独立显示最近一次自动升级结果与时间，不依赖 READ_LOGS 权限。固定原因区分 HTTP 状态、DNS、超时、TLS、清单、APK 大小/哈希及安装权限失败。同一安全消息以 RemoteAgentOTA 标签写入 logcat；完整设备日志仍要求 READ_LOGS。状态不会复制异常中的 URL、路径或凭据。
