i18n(zh-cn): translate plugin/updater.mdx (#3130)

This commit is contained in:
_zhiqiu
2025-02-07 00:25:24 +08:00
committed by GitHub
parent 065c1cf15b
commit 4b8797d376

View File

@@ -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';
<PluginLinks plugin={frontmatter.plugin} />
自动使用更新服务器或静态 JSON 更新你的 Tauri 应用程序。
## Supported Platforms
<Compatibility plugin={frontmatter.plugin} />
## Setup
从安装 Tauri 更新插件开始。
<Tabs>
<TabItem label="自动">
使用项目的包管理器添加依赖项。
<CommandTabs
npm="npm run tauri add updater"
yarn="yarn run tauri add updater"
pnpm="pnpm tauri add updater"
deno="deno task tauri add updater"
bun="bun tauri add updater"
cargo="cargo tauri add updater"
/>
</TabItem>
<TabItem label="手动">
<Steps>
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 绑定。
<CommandTabs
npm="npm install @tauri-apps/plugin-updater"
yarn="yarn add @tauri-apps/plugin-updater"
pnpm="pnpm add @tauri-apps/plugin-updater"
deno="deno add npm:@tauri-apps/plugin-updater"
bun="bun add @tauri-apps/plugin-updater"
/>
</Steps>
</TabItem>
</Tabs>
## 签名更新包
Tauri 的更新程序需要签名来验证更新来自可信来源。这不能被禁用。
要对更新进行签名,你需要两个密钥:
1. 公钥将在 `tauri.conf.json` 中设置,以便在安装前验证升级包。只要您的私钥是安全的,此公钥就可以安全地上传和共享。
2. 私钥,用于为安装程序文件签名。你不应该与任何人共享这把钥匙。此外,如果你丢失了这个密钥,你将无法向已经安装应用程序的用户发布新的更新。把这个密钥放在安全的地方很重要!
为了生成密钥Tauri CLI 提供了 `signer generate` 命令。你可以运行以下命令在主文件夹中创建密钥。
<Tabs>
<CommandTabs
npm="npm run tauri signer generate -- -w ~/.tauri/myapp.key"
yarn="yarn tauri signer generate -w ~/.tauri/myapp.key"
pnpm="pnpm tauri signer generate -w ~/.tauri/myapp.key"
deno="deno task tauri signer generate -w ~/.tauri/myapp.key"
bun="bunx tauri signer generate -w ~/.tauri/myapp.key"
cargo="cargo tauri signer generate -w ~/.tauri/myapp.key"
/>
</Tabs>
### 构建
在构建您的更新包时,您需要在环境变量中配置上述生成的私钥。`.env` 文件*不起*作用!
<Tabs>
<TabItem label="Mac/Linux">
```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=""
```
</TabItem>
<TabItem label="Windows">
在 `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=""
```
</TabItem>
</Tabs>
之后,您可以像往常一样运行 Tauri buildTauri 将生成更新包及其签名。
生成的文件依赖于下面配置的 [`createUpdaterArtifacts`] 配置值。
<Tabs>
<TabItem label="v2">
```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` - 更新包的签名。
{''}
</TabItem>
<TabItem label="v1 compatible">
```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` - 更新包的签名。
{''}
</TabItem>
</Tabs>
## 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 代码访问。
<Tabs syncKey="lang">
<TabItem label="JavaScript">
```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 文档]。
</TabItem>
<TabItem label="Rust">
```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] 的命令。
<details>
<summary>Updater command</summary>
```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<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.to_string().as_str())
}
}
type Result<T> = std::result::Result<T, Error>;
#[derive(Clone, Serialize)]
#[serde(tag = "event", content = "data")]
pub enum DownloadEvent {
#[serde(rename_all = "camelCase")]
Started {
content_length: Option<u64>,
},
#[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<Option<UpdateMetadata>> {
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<DownloadEvent>) -> 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<Option<Update>>);
}
#[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
])
}
```
</details>
:::
更多有关信息,请参阅 [Rust API 文档]。
</TabItem>
</Tabs>
请注意,安装更新后不需要立即重启应用程序,你可以选择如何处理更新,要么等待用户手动重启应用程序,要么提示他选择何时这样做。
:::note
在 Windows 上,由于 Windows 安装程序的限制,应用程序在执行安装步骤时将自动退出。
:::
当检查和下载更新时,可以定义自定义的请求超时、代理和请求头。
<Tabs syncKey="lang">
<TabItem label="JavaScript">
```js
import { check } from '@tauri-apps/plugin-updater';
const update = await check({
proxy: '<proxy url>',
timeout: 30000 /* milliseconds */,
headers: {
Authorization: 'Bearer <token>',
},
});
```
</TabItem>
<TabItem label="Rust">
```rust
use tauri_plugin_updater::UpdaterExt;
let update = app
.updater_builder()
.timeout(std::time::Duration::from_secs(30))
.proxy("<proxy-url>".parse().expect("invalid URL"))
.header("Authorization", "Bearer <token>")
.build()?
.check()
.await?;
```
</TabItem>
</Tabs>
### 运行时配置
更新 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("<your public key>").build()
```
```rust
use tauri_plugin_updater::UpdaterExt;
let update = app
.updater_builder()
.pubkey("<your public key>")
.build()?
.check()
.await?;
```
#### 自定义目标
默认情况下,更新程序允许您使用 `{{target}}` and `{{arch}}` 变量来确定必须交付哪个更新资产。
如果您需要有关更新的更多信息(例如,在分发通用 macOS 二进制选项或有更多构建风格时),您可以设置自定义目标。
<Tabs syncKey="lang">
<TabItem label="JavaScript">
```js
import { check } from '@tauri-apps/plugin-updater';
const update = await check({
target: 'macos-universal',
});
```
</TabItem>
<TabItem label="Rust">
自定义目标可以由插件构建者或更新器构建者来设置。
```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<String>`,当当前平台不支持更新器时,其值为 `None`。
:::
</TabItem>
</Tabs>
:::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-配置)中的值作为备用。
:::
[`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",
]
}
```
<PluginPermissions plugin={frontmatter.plugin} />