Tauri 踩坑实录之有关自动更新的本地测试

前言

根据官方文档 Tauri Updater: Dynamic Update Server ,我们可以通过搭建本地的 Server 并配置相关响应来实现自动更新的本地测试。

搭建更新环境

由于更新需要使用到 https,首先我们需要搭建一个本地凭证颁发机构。

使用 mkcert,根据指南进行搭建,linux 和 macos 环境下需要 homebrew, windows 环境下需要 chocolatey

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 在 linux 或 macos 平台安装 homebrew
$ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# 添加 brew 至环境
$ eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"

# 检测 homebrew 是否安装成功
$ brew -v
Homebrew 4.0.12


# 在 linux 平台安装 mkcert
$ sudo apt install libnss3-tools
$ brew install mkcert

# 在 macOS 平台安装 mkcert
$ brew install mkcert

# 建立本地 CA
$ mkcert -install
The local CA is now installed in the system trust store! ⚡️
The local CA is now installed in the Firefox and/or Chrome/Chromium trust store (requires browser restart)! 🦊

BASH
# 在 window 平台安装 chocolatry,需以管理员权限运行 powershell 并安装
$ Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))

# 检测 chocolatry 是否安装成功
$ choco --version

# 安装 mkcert.
$ choco install mkcert

# 建立本地 CA
$ mkcert -install
The local CA is now installed in the system trust store! ⚡️
The local CA is now installed in the Firefox and/or Chrome/Chromium trust store (requires browser restart)! 🦊

创建本地 https 证书

1
2
3
4
5
6
7
8
9
$ mkdir ${Your local update server path}
$ cd ${Your local update server path}
$ mkcert ${Your host name, like 'localhost'}
Created a new certificate valid for the following names 📜
- "localhost"

The certificate is at "./localhost.pem" and the key at "./localhost-key.pem"

It will expire on 10 July 2025 🗓

项目添加签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$ cd ${Your project path}

# 在 linux 或 macOS 为项目签名
$ pnpm tauri signer generate -w ~/.tauri/${Custom sign name}.key

pnpm tauri signer generate -w ~/.tauri/tauri-app.key

# 在 windows 为项目签名
$ pnpm tauri signer generate -w $HOME/.tauri/${Custom sign name}.key

# 输入密码,密码不会显示但输入都是有效的,类似 linux
# 此处假设设置的 Custom sign name 为 tauri-app
> tauri-app@0.0.0 tauri /home/user/Desktop/tauri-app
> tauri "signer" "generate" "-w" "/home/user/.tauri/tauri-app.key"

Generating new private key without password.
Please enter a password to protect the secret key.
Password:
Password (one more time):
Deriving a key from the password in order to encrypt the secret key... done

Your keypair was generated successfully
Private: /home/user/.tauri/tauri-app.key (Keep it secret!)
Public: /home/user/.tauri/tauri-app.key.pub
---------------------------

Environment variables used to sign:
`TAURI_PRIVATE_KEY` Path or String of your private key
`TAURI_KEY_PASSWORD` Your private key password (optional)

ATTENTION: If you lose your private key OR password, you'll not be able to sign your update package and updates will not work.
---------------------------

这里我们一共生成了三个内容,一个是我们输入的 password,需要我们记住;一个是项目的 private key tauri-app.key, 不可以泄露;一个是项目的 public key,用于校验

更新 tauri.conf.json

1
2
3
4
5
6
7
8
9
10
11
12
13
{
...
"tauri": {
"updater": {
"active": true,
"endpoints": ["https://localhost:4399"],
"dialog": true,
"pubkey": "${{/home/user/.tauri/tauri-app.key.pub}}"
},
...
}
...
}

编译签名项目

1
2
3
4
5
6
7
8
9
10
# 添加密码和私钥至 linux 或 macOS 环境
$ export TAURI_PRIVATE_KEY="content of the generated key"
$ export TAURI_KEY_PASSWORD="password"

# 添加密码和私钥至 windows 环境,需使用 powershell
$ $env:TAURI_PRIVATE_KEY="content of the generated key"
$ $env:TAURI_KEY_PASSWORD="password"

# 编译项目
$ pnpm tauri build

搭建本地更新服务器

1
2
3
$ mkdir local-updater-server
$ cd local-updater-server
$ touch index.js

编写相关代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// local-updater-server/index.js
const https = require('https');
const url = require('url');
const fileSystem = require('fs');
const path = require('path');

// 项目编译后的更新文件路径,注意这里是 .zip 文件的路径
const new_app_file_path = "path\\to\\your\\app\\release\\file";

// 本地 https key 和 cert
const options = {
key: fileSystem.readFileSync("./localhost-key.pem"),
cert: fileSystem.readFileSync("./localhost.pem")
}

async function fileExists(filename) {
try {
await fileSystem.promises.access(filename);
return true;
} catch (err) {
if (err.code === 'ENOENT') {
return false;
} else {
throw err;
}
}
}

async function requestListener (req, res) {
var uri = url.parse(req.url);

if (uri.path.includes('update')) {
const fileName = path.basename(new_app_file_path);

console.log('try fetch update file');
await fileExists(new_app_file_path)
.then(exists => {
if (exists) {
res.writeHead(200, {
"Content-Type": "application/octet-stream",
"Content-Disposition": "attachment; filename=" + fileName
});

console.log('ready to send file');
fileSystem.createReadStream(new_app_file_path).pipe(res);
return;
}

console.log("failed to prepare file");
res.writeHead(400, { "Content-Type": "text/plain" });
res.end("ERROR File does not exist");
});
} else {
const responseData = {
// 自定义更新版本
version: "0.0.2",
// 自定义更新时间
pub_date: "2020-09-18T12:29:53+01:00",
// 指定本地更新地址,访问将直接下载更新文件
url: "https://localhost:4399/update",
// 项目编译后的签名文件内容,即 .sig 文件中内容
signature: `${{your app pub key}}`,
// 自定义更新信息
notes: "These are some release notes"
}

const jsonContent = JSON.stringify(responseData);
res.end(jsonContent);
}
};

const server = https.createServer(options, requestListener);

server.listen(4399,'localhost', function(){
console.log("Server is Listening at Port 4399!");
});

程序自动获取更新

1
2
3
4
5
6
7
# Start update server
$ cd local-updater-server
$ node index.js

# Run tauri app
$ cd ${Your project path}
$ pnpm tauri dev

自动提示更新信息

自动提示更新信息

程序手动更新

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<script setup lang="ts">
import { ref } from "vue";
import { invoke } from "@tauri-apps/api/tauri";
import {
checkUpdate,
installUpdate,
onUpdaterEvent,
} from '@tauri-apps/api/updater'
import { relaunch } from '@tauri-apps/api/process'

const greetMsg = ref("");
const name = ref("");

async function greet() {
greetMsg.value = await invoke("greet", { name: name.value });
// 手动触发检查更新
await check_for_update();
}

async function check_for_update() {
try {
const { shouldUpdate, manifest } = await checkUpdate()

if (shouldUpdate) {
// 打印更新信息
console.log(
`Installing update ${manifest?.version}, ${manifest?.date}, ${manifest?.body}`
)

// 安装更新. windows 平台下将自动重启应用
await installUpdate()

// macOS 或 linux 平台下需要手动重启应用
await relaunch()
}
} catch (error) {
greetMsg.value = String(error);
}
}
</script>

<template>
<div class="card">
<input id="greet-input" v-model="name" placeholder="Enter a name..." />
<button type="button" @click="greet()">Greet</button>
</div>

<p>{{ greetMsg }}</p>
</template>

手动获取更新信息


Tauri 踩坑实录之有关自动更新的本地测试
http://example.com/2023/04/15/Tauri-踩坑实录之有关自动更新的本地测试/
作者
Steins Gu
发布于
2023年4月15日
许可协议