From 204562e6a4015be7b5aaf3bebfe9f590e227d94c Mon Sep 17 00:00:00 2001 From: GuoJiKun Date: Thu, 23 Jan 2025 14:27:00 +0800 Subject: [PATCH 1/2] translate `plugin/updater.mdx` --- src/content/docs/zh-cn/plugin/updater.mdx | 768 ++++++++++++++++++++++ 1 file changed, 768 insertions(+) create mode 100644 src/content/docs/zh-cn/plugin/updater.mdx diff --git a/src/content/docs/zh-cn/plugin/updater.mdx b/src/content/docs/zh-cn/plugin/updater.mdx new file mode 100644 index 0000000000..713ca099a7 --- /dev/null +++ b/src/content/docs/zh-cn/plugin/updater.mdx @@ -0,0 +1,768 @@ +--- +title: 更新 +description: Tauri 应用程序的应用内更新。 +plugin: updater +--- + +import PluginLinks from '@components/PluginLinks.astro'; +import Compatibility from '@components/plugins/Compatibility.astro'; + +import PluginPermissions from '@components/PluginPermissions.astro'; +import CommandTabs from '@components/CommandTabs.astro'; +import { TabItem, Steps, Tabs } from '@astrojs/starlight/components'; + + + +自动使用更新服务器或静态 JSON 更新你的 Tauri 应用程序。 + +## Supported Platforms + + + +## Setup + +从安装 Tauri 更新插件开始。 + + + + + 使用项目的包管理器添加依赖项。 + + + + + + + + 1. 在 `src-tauri` 文件夹中运行以下命令,将插件添加到 `Cargo.toml` 中的项目依赖项中。 + + + ```sh frame=none + cargo add tauri-plugin-updater --target 'cfg(any(target_os = "macos", windows, target_os = "linux"))' + ``` + + 2. 修改 `lib.rs` 来初始化插件。 + + ```rust title="lib.rs" ins={5-6} + #[cfg_attr(mobile, tauri::mobile_entry_point)] + pub fn run() { + tauri::Builder::default() + .setup(|app| { + #[cfg(desktop)] + app.handle().plugin(tauri_plugin_updater::Builder::new().build()); + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); + } + ``` + + 3. 使用你喜欢的 JavaScript 包管理器安装 JavaScript Guest 绑定。 + + + + + + + + +## 签名更新包 + +Tauri 的更新程序需要签名来验证更新来自可信来源。这不能被禁用。 + +要对更新进行签名,你需要两个密钥: + +1. 公钥将在 `tauri.conf.json` 中设置,以便在安装前验证升级包。只要您的私钥是安全的,此公钥就可以安全地上传和共享。 +2. 私钥,用于为安装程序文件签名。你不应该与任何人共享这把钥匙。此外,如果你丢失了这个密钥,你将无法向已经安装应用程序的用户发布新的更新。把这个密钥放在安全的地方很重要! + +为了生成密钥,Tauri CLI 提供了 `signer generate` 命令。你可以运行以下命令在主文件夹中创建密钥。 + + + + + +### 构建 + +在构建您的更新包时,您需要在环境变量中配置上述生成的私钥。`.env` 文件*不起*作用! + + + + ```sh frame=none + export TAURI_SIGNING_PRIVATE_KEY="Path or content of your private key" + # optionally also add a password + export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="" + ``` + + + 在 `PowerShell` 中运行。 + ```ps frame=none + $env:TAURI_SIGNING_PRIVATE_KEY="Path or content of your private key" + <# optionally also add a password #> + $env:TAURI_SIGNING_PRIVATE_KEY_PASSWORD="" + ``` + + + +之后,您可以像往常一样运行 Tauri build,Tauri 将生成更新包及其签名。 +生成的文件依赖于下面配置的 [`createUpdaterArtifacts`] 配置值。 + + + + +```json +{ + "bundle": { + "createUpdaterArtifacts": true + } +} +``` + +在 Linux 上,Tauri 将在 `target/release/bundle/appimage/` 文件夹中创建 AppImage。 + +- `myapp.AppImage` - 标准的应用程序包。它将被更新者重新使用。 +- `myapp.AppImage.sig` - 更新包的签名。 + +在 macOS 系统上,Tauri 会从 `target/release/bundle/macos/` 文件夹内的应用程序包创建一个 .tar.gz 归档文件。 + +- `myapp.app` - 标准的应用程序包。 +- `myapp.app.tar.gz` - 更新包。 +- `myapp.app.tar.gz.sig` - 更新包的签名。 + +在 Windows 系统上,Tauri 会在 `target/release/bundle/msi/` and `target/release/bundle/nsis` 文件夹内创建常规的 MSI 和 NSIS 安装程序。 + +- `myapp-setup.exe` - 标准的应用程序包。它将被更新者重新使用。 +- `myapp-setup.exe.sig` - 更新包的签名。 +- `myapp.msi` - 标准的应用程序包。它将被更新者重新使用。 +- `myapp.msi.sig` - 更新包的签名。 + +{''} + + + + +```json +{ + "bundle": { + "createUpdaterArtifacts": "v1Compatible" + } +} +``` + +在 Linux 上,Tauri 将在 `target/release/bundle/appimage/` 文件夹中创建 AppImage。 + +- `myapp.AppImage` - 标准的应用包。 +- `myapp.AppImage.tar.gz` - 更新包。 +- `myapp.AppImage.tar.gz.sig` - 更新包的签名。 + +在 macOS 系统上,Tauri 会从 `target/release/bundle/macos/` 文件夹内的应用程序包创建一个 .tar.gz 归档文件。 + +- `myapp.app` - 标准的应用包。 +- `myapp.app.tar.gz` - 更新包。 +- `myapp.app.tar.gz.sig` - 更新包的签名。 + +在 Windows 系统上,Tauri 会从 `target/release/bundle/msi/` 和 `target/release/bundle/nsis` 文件夹内的 MSI 和 NSIS 安装程序创建 .zip 归档文件。 + +- `myapp-setup.exe` - 标准的应用包。 +- `myapp-setup.nsis.zip` - 更新包。 +- `myapp-setup.nsis.zip.sig` - 更新包的签名。 +- `myapp.msi` - 标准的应用包。 +- `myapp.msi.zip` - 更新包。 +- `myapp.msi.zip.sig` - 更新包的签名。 + +{''} + + + + +## Tauri 配置 + +以这种格式设置 `tauri.conf.json`,以使更新程序开始工作。 + +| Keys | Description | +| ------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `createUpdaterArtifacts` | 将其设置为 `true` 告诉 Tauri 的应用打包器创建更新包。如果你要从较旧的 Tauri 版本迁移应用程序,请将其设置为 `"v1Compatible"`。**此设置将在 v3 中删除**,所以一旦所有用户迁移到 v2,请确保将其更改为 `true`。 | +| `pubkey` | 这必须是上面步骤中从 Tauri CLI 生成的公钥。它**不能**是文件路径! | +| `endpoints` | 这必须是一个字符串形式的 url 数组。TLS 在生产模式下强制执行。只有返回非 2xx 状态码时,Tauri 才会继续访问下一个 url! | +| `dangerousInsecureTransportProtocol` | 将其设置为 `true` 允许更新器接受非 https 端点。请谨慎使用此配置! | + +每个更新的 URL 可以包含以下动态变量,允许您在服务器端确定更新是否可用。 + +- `{{current_version}}`:请求更新的应用程序版本。 +- `{{target}}`:操作系统名称 (`linux`、`windows` 或 `darwin` 之一)。 +- `{{arch}}`:机器的架构 (`x86_64`、`i686`、`aarch64` 或 `armv7` 之一)。 + +```json title=tauri.conf.json +{ + "bundle": { + "createUpdaterArtifacts": true + }, + "plugins": { + "updater": { + "pubkey": "CONTENT FROM PUBLICKEY.PEM", + "endpoints": [ + "https://releases.myapp.com/{{target}}/{{arch}}/{{current_version}}", + // or a static github json file + "https://github.com/user/repo/releases/latest/download/latest.json" + ] + } + } +} +``` + +:::tip +不支持自定义变量,但可以定义[自定义 `{{target}}`](#自定义目标)。 +::: + +### `installMode` on Windows + +在 Windows 平台上,有一个额外的可选的 `"installMode"` 配置来更改更新包的安装方式。 + +```json title=tauri.conf.json +{ + "plugins": { + "updater": { + "windows": { + "installMode": "passive" + } + } + } +} +``` + +- `"passive"`:会有一个带有进度条的小窗口。该更新将在不需要任何用户交互的情况下安装。一般推荐使用默认模式。 +- `"basicUi"`:将显示一个基本的用户界面,它需要用户交互来完成安装。 +- `"quiet"`:没有进度反馈给用户。在这种模式下,安装程序不能自行请求管理员权限,所以它只适用于用户范围内的安装,或者当您的应用程序本身已经以管理员权限运行时。一般不推荐。 + +## 服务器的支持 + +更新插件可以以两种方式使用。要么使用动态更新服务器,要么使用静态 JSON 文件(用于 S3 或 GitHub gist 等服务)。 + +### 静态 JSON 文件 + +使用静态文件时,只需要返回一个包含所需信息的 JSON。 + +| Keys | Description | +| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `version` | 必须是一个有效的 [SemVer](https://semver.org/),带或不带 `v`,这意味着 `1.0.0` 和 `v1.0.0` 都是有效的。 | +| `notes` | 更新说明。 | +| `pub_date` | 如果存在日期,则必须按照 [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339) 规范格式化。 | +| `platforms` | 每个平台的字段名都是 `OS-ARCH` 格式,其中 `OS` 是 `linux`、`darwin` 或 `windows` 中的一个,而 `ARCH` 是 `x86_64`、`aarch64`、`i686` 或 `armv7` 中的一个。 | +| `signature` | 生成的 `.sig` 文件的内容,可能会随着每次构建而改变。路径或 URL 将不起作用! | + +:::info +当使用[自定义目标](#自定义目标)时,所提供的目标字符串会与 `platforms` 键进行匹配,而非默认的 `OS-ARCH` 值。 +::: + +`"version"`、`"platforms.[target].url"` 和 `"platforms.[target].signature"` 是必需的;其它字段是可选的。 + +```json +{ + "version": "", + "notes": "", + "pub_date": "", + "platforms": { + "linux-x86_64": { + "signature": "", + "url": "" + }, + "windows-x86_64": { + "signature": "", + "url": "" + }, + "darwin-x86_64": { + "signature": "", + "url": "" + } + } +} +``` + +注意,Tauri 将在检查 version 字段之前验证整个文件,因此请确保所有现有平台配置都是有效和完整的。 + +:::tip +[Tauri Action](https://github.com/tauri-apps/tauri-action) 会生成一个静态的 JSON 文件供你在 cdn 上使用,比如 GitHub 发行版。 +::: + +### 动态更新服务器 + +在使用动态更新服务器时,Tauri 会遵循服务器的指令。若要禁用内部版本检查,您可以覆盖 [Tauri 的版本比较](https://docs.rs/tauri/latest/tauri/updater/struct.UpdateBuilder.html#method.should_install),这样就会安装服务器发送的版本(如果您需要回滚应用程序,这很有用)。 + +您的服务器可以使用上述 `endpoint` URL 中定义的变量来确定是否需要更新。如果您需要更多数据,可以根据自己的喜好在 Rust 中添加额外的[请求头](https://docs.rs/tauri/latest/tauri/updater/struct.UpdateBuilder.html#method.header)。 + +如果没有可用的更新,您的服务器应响应状态码为 [`204 无内容`](https://datatracker.ietf.org/doc/html/rfc2616#section-10.2.5)。 + +如果需要更新,您的服务器应使用状态码 [`200 OK`](http://tools.ietf.org/html/rfc2616#section-10.2.1) 进行响应,并以这种格式提供 JSON 响应。 + +| Keys | Description | +| ----------- | ------------------------------------------------------------------------------------------------------- | +| `version` | 必须是一个有效的 [SemVer](https://semver.org/),带或不带 `v`,这意味着 `1.0.0` 和 `v1.0.0` 都是有效的。 | +| `notes` | 更新说明。 | +| `pub_date` | 如果存在日期,则必须按照 [RFC 3339](https://datatracker.ietf.org/doc/html/rfc3339) 规范格式化。 | +| `url` | 这必须是的有效更新包 URL。 | +| `signature` | 生成的 `.sig` 文件的内容,可能会随着每次构建而改变。路径或 URL 将不起作用! | + +`"url"`、`"version"` and `"signature"` 是必须的;其它字段是可选的。 + +```json +{ + "version": "", + "pub_date": "", + "url": "", + "signature": "", + "notes": "" +} +``` + +:::tip +金牛座的官方合作伙伴 CrabNebula 提供了一个动态更新服务器。有关更多信息,请参阅 [Distributing with CrabNebula Cloud] 文档。 +::: + +## 检查更新 + +用于检查和安装更新的默认 API 利用配置的端点,可以由 JavaScript 和 Rust 代码访问。 + + + + +```js +import { check } from '@tauri-apps/plugin-updater'; +import { relaunch } from '@tauri-apps/plugin-process'; + +const update = await check(); +if (update) { + console.log( + `found update ${update.version} from ${update.date} with notes ${update.body}` + ); + let downloaded = 0; + let contentLength = 0; + // alternatively we could also call update.download() and update.install() separately + await update.downloadAndInstall((event) => { + switch (event.event) { + case 'Started': + contentLength = event.data.contentLength; + console.log(`started downloading ${event.data.contentLength} bytes`); + break; + case 'Progress': + downloaded += event.data.chunkLength; + console.log(`downloaded ${downloaded} from ${contentLength}`); + break; + case 'Finished': + console.log('download finished'); + break; + } + }); + + console.log('update installed'); + await relaunch(); +} +``` + +更多有关信息,请参阅 [JavaScript API 文档]。 + + + + + +```rust title="src-tauri/src/lib.rs" +use tauri_plugin_updater::UpdaterExt; + +pub fn run() { + tauri::Builder::default() + .setup(|app| { + let handle = app.handle().clone(); + tauri::async_runtime::spawn(async move { + update(handle).await.unwrap(); + }); + Ok(()) + }) + .run(tauri::generate_context!()) + .unwrap(); +} + +async fn update(app: tauri::AppHandle) -> tauri_plugin_updater::Result<()> { + if let Some(update) = app.updater()?.check().await? { + let mut downloaded = 0; + + // alternatively we could also call update.download() and update.install() separately + update + .download_and_install( + |chunk_length, content_length| { + downloaded += chunk_length; + println!("downloaded {downloaded} from {content_length:?}"); + }, + || { + println!("download finished"); + }, + ) + .await?; + + println!("update installed"); + app.restart(); + } + + Ok(()) +} +``` + +:::tip +要通知前端下载进度,请考虑使用带 [channel] 的命令。 + +
+ Updater command + +```rust +#[cfg(desktop)] +mod app_updates { + use std::sync::Mutex; + use serde::Serialize; + use tauri::{ipc::Channel, AppHandle, State}; + use tauri_plugin_updater::{Update, UpdaterExt}; + + #[derive(Debug, thiserror::Error)] + pub enum Error { + #[error(transparent)] + Updater(#[from] tauri_plugin_updater::Error), + #[error("there is no pending update")] + NoPendingUpdate, + } + + impl Serialize for Error { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.to_string().as_str()) + } + } + + type Result = std::result::Result; + + #[derive(Clone, Serialize)] + #[serde(tag = "event", content = "data")] + pub enum DownloadEvent { + #[serde(rename_all = "camelCase")] + Started { + content_length: Option, + }, + #[serde(rename_all = "camelCase")] + Progress { + chunk_length: usize, + }, + Finished, + } + + #[derive(Serialize)] + #[serde(rename_all = "camelCase")] + pub struct UpdateMetadata { + version: String, + current_version: String, + } + + #[tauri::command] + pub async fn fetch_update( + app: AppHandle, + pending_update: State<'_, PendingUpdate>, + ) -> Result> { + let channel = "stable"; + let url = url::Url::parse(&format!( + "https://cdn.myupdater.com/{{{{target}}}}-{{{{arch}}}}/{{{{current_version}}}}?channel={channel}", + )).expect("invalid URL"); + + let update = app + .updater_builder() + .endpoints(vec![url])? + .build()? + .check() + .await?; + + let update_metadata = update.as_ref().map(|update| UpdateMetadata { + version: update.version.clone(), + current_version: update.current_version.clone(), + }); + + *pending_update.0.lock().unwrap() = update; + + Ok(update_metadata) + } + + #[tauri::command] + pub async fn install_update(pending_update: State<'_, PendingUpdate>, on_event: Channel) -> Result<()> { + let Some(update) = pending_update.0.lock().unwrap().take() else { + return Err(Error::NoPendingUpdate); + }; + + let started = false; + + update + .download_and_install( + |chunk_length, content_length| { + if !started { + let _ = on_event.send(DownloadEvent::Started { content_length }); + started = true; + } + + let _ = on_event.send(DownloadEvent::Progress { chunk_length }); + }, + || { + let _ = on_event.send(DownloadEvent::Finished); + }, + ) + .await?; + + Ok(()) + } + + struct PendingUpdate(Mutex>); +} + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_process::init()) + .setup(|app| { + #[cfg(desktop)] + { + app.handle().plugin(tauri_plugin_updater::Builder::new().build()); + app.manage(app_updates::PendingUpdate(Mutex::new(None))); + } + Ok(()) + }) + .invoke_handler(tauri::generate_handler![ + #[cfg(desktop)] + app_updates::fetch_update, + #[cfg(desktop)] + app_updates::install_update + ]) +} +``` + +
+::: + +更多有关信息,请参阅 [Rust API 文档]。 + +
+
+ +请注意,安装更新后不需要立即重启应用程序,你可以选择如何处理更新,要么等待用户手动重启应用程序,要么提示他选择何时这样做。 + +:::note +在 Windows 上,由于 Windows 安装程序的限制,应用程序在执行安装步骤时将自动退出。 +::: + +当检查和下载更新时,可以定义自定义的请求超时、代理和请求头。 + + + + +```js +import { check } from '@tauri-apps/plugin-updater'; + +const update = await check({ + proxy: '', + timeout: 30000 /* milliseconds */, + headers: { + Authorization: 'Bearer ', + }, +}); +``` + + + + + +```rust +use tauri_plugin_updater::UpdaterExt; +let update = app + .updater_builder() + .timeout(std::time::Duration::from_secs(30)) + .proxy("".parse().expect("invalid URL")) + .header("Authorization", "Bearer ") + .build()? + .check() + .await?; +``` + + + + +### 运行时配置 + +更新 api 还允许在运行时配置更新程序,以获得更大的灵活性。 +出于安全原因,一些 api 仅对 Rust 可用。 + +#### Endpoints + +设置运行时检查更新的 url 可以允许更多的动态更新,例如单独的发布通道: + +```rust +use tauri_plugin_updater::UpdaterExt; +let channel = if beta { "beta" } else { "stable" }; +let update_url = format!("https://{channel}.myserver.com/{{{{target}}}}-{{{{arch}}}}/{{{{current_version}}}}"); + +let update = app + .updater_builder() + .endpoints(vec![update_url])? + .build()? + .check() + .await?; +``` + +:::tip +注意,使用 format!() 来插入更新 URL 时,需要对变量进行双重转义, +例如 `{{{{target}}}}`。 +::: + +#### 公钥 + +在运行时设置公钥对于实现密钥轮换逻辑可能很有用。 +它可以由插件构建器或更新器构建器设置: + +```rust +tauri_plugin_updater::Builder::new().pubkey("").build() +``` + +```rust +use tauri_plugin_updater::UpdaterExt; + +let update = app + .updater_builder() + .pubkey("") + .build()? + .check() + .await?; +``` + +#### 自定义目标 + +默认情况下,更新程序允许您使用 `{{target}}` and `{{arch}}` 变量来确定必须交付哪个更新资产。 +如果您需要有关更新的更多信息(例如,在分发通用 macOS 二进制选项或有更多构建风格时),您可以设置自定义目标。 + + + + +```js +import { check } from '@tauri-apps/plugin-updater'; + +const update = await check({ + target: 'macos-universal', +}); +``` + + + + + +自定义目标可以由插件构建者或更新器构建者来设置。 + +```rust +tauri_plugin_updater::Builder::new().target("macos-universal").build() +``` + +```rust +use tauri_plugin_updater::UpdaterExt; +let update = app + .updater_builder() + .target("macos-universal") + .build()? + .check() + .await?; +``` + +:::tip +默认的 `$target-$arch` 键可以使用 `tauri_plugin_updater::target()` 来获取,该函数返回一个 `Option`,当当前平台不支持更新器时,其值为 `None`。 +::: + + + + +:::note + +- 当使用自定义目标时,可能更容易使用它来确定更新平台,所以你可以删除 `{{arch}}` 变量。 +- 所提供的目标值是在使用[静态 JSON 文件](#静态-json-文件)时与平台键相匹配的键。 + +::: + +#### 允许降级 + +默认情况下,Tauri 会检查更新版本是否大于当前应用程序版本,以验证是否应该更新。 +为了允许降级,你必须使用更新构建器的 `version_comparator` API。 + +```rust +use tauri_plugin_updater::UpdaterExt; + +let update = app + .updater_builder() + .version_comparator(|current, update| { + // default comparison: `update.version > current` + update.version != current + }) + .build()? + .check() + .await?; +``` + +#### 窗口退出前的钩子函数 + +由于 Windows 安装程序的限制,Tauri 会在 Windows 上安装更新之前自动退出您的应用程序。 +要在此之前执行操作,请使用 `on_before_exit` 函数。 + +```rust +use tauri_plugin_updater::UpdaterExt; + +let update = app + .updater_builder() + .on_before_exit(|| { + println!("app is about to exit on Windows!"); + }) + .build()? + .check() + .await?; +``` + +:::note +如果没有设置任何构建器的值,则使用[配置](#tauri-configuration)中的值作为备用。 +::: + +[`createUpdaterArtifacts`]: /reference/config/#createupdaterartifacts +[Distributing with CrabNebula Cloud]: /distribute/crabnebula-cloud/ +[channel]: /develop/calling-frontend/#channels +[JavaScript API 文档]: /reference/javascript/updater/ +[Rust API 文档]: https://docs.rs/tauri-plugin-updater + +## 权限 + +默认情况下,所有潜在危险的插件命令和范围都被阻止且无法访问。您必须在您的 `capabilities` 配置中修改权限以启用这些。 + +有关更多信息,请参阅[功能概述](/security/capabilities/),以及使用插件权限的[分步指南](/learn/security/using-plugin-permissions/)。 + +```json title="src-tauri/capabilities/default.json" ins={4} +{ + "permissions": [ + ..., + "updater:default", + ] +} +``` + + From 3580b0fd8005aef1485fb91b072bd30676c3929e Mon Sep 17 00:00:00 2001 From: GuoJiKun Date: Thu, 23 Jan 2025 14:49:18 +0800 Subject: [PATCH 2/2] fix link error --- src/content/docs/zh-cn/plugin/updater.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/docs/zh-cn/plugin/updater.mdx b/src/content/docs/zh-cn/plugin/updater.mdx index 713ca099a7..ac219df519 100644 --- a/src/content/docs/zh-cn/plugin/updater.mdx +++ b/src/content/docs/zh-cn/plugin/updater.mdx @@ -741,7 +741,7 @@ let update = app ``` :::note -如果没有设置任何构建器的值,则使用[配置](#tauri-configuration)中的值作为备用。 +如果没有设置任何构建器的值,则使用[配置](#tauri-配置)中的值作为备用。 ::: [`createUpdaterArtifacts`]: /reference/config/#createupdaterartifacts