2个版本

0.1.1 2024年4月2日
0.1.0 2024年3月16日

#302 in 文件系统

Download history 4/week @ 2024-05-21 7/week @ 2024-07-02

67 每月下载量

MIT 许可证

160KB
2.5K SLoC

Patchify

Patchify包是一个自动更新库,为Rust应用程序提供自动更新自身的功能。

它包括将功能嵌入应用程序本身和Web-based API服务器的功能,使应用程序能够检查更新,而服务器可以完全处理这些更新。库模块的使用非常简单,只需要简单的配置和最少的代码即可启动。

可以配备自动更新功能的应用程序可以是任何持久运行的东西:Web服务器、桌面应用程序、命令行工具等。

项目有一个路线图,概述了计划发布的版本及其相关功能,并根据预期目标指示了当前状态。

具有完整的测试覆盖率,包括单元测试、集成测试和端到端测试,利用各种类型的模拟。还有有用的示例。

本README的主要部分包括

功能

需要注意的主要高阶要点是

  • 客户端应用程序
    • 完全自主的更新检查和升级过程
    • 可配置的检查间隔
    • 自动应用程序重启
    • 注册和管理关键操作以编排升级的能力
    • 更新状态广播器,用于应用级别的状态更新
    • 使用SHA256散列验证发布文件
    • 使用公钥验证HTTP响应签名
  • API服务器
    • 与Web服务器无关,但与Axum完全集成
    • 使用Tokio Tracing记录HTTP请求和事件
    • 流式传输大型发布文件以提高内存效率
    • 使用私钥对HTTP响应进行签名
  • 无需配置即可工作的完整且最少的示例
    • 使用Figment从配置文件和环境变量中进行配置
    • 使用Tokio Hyper的高性能异步HTTP服务器
    • 基于强大且易于使用的Web框架Axum
  • 完整的代码库文档
  • 单元测试和集成测试的完整测试覆盖率

签名和验证

HTTP响应将通过服务器的私钥进行签名,使连接的客户端能够验证它们未被篡改。使用的密钥格式是Ed25519,比RSA更快更安全。

哈希和验证

服务器启动时会检查发布文件的SHA256哈希值,客户端也会验证下载文件的哈希值。这确保了文件是原始文件的准确副本,且未被篡改。

流式传输

服务器将流式传输大发布文件到客户端,这比在发送之前将整个文件读入内存更高效。这是完全可配置的。

模块

目前,提供了以下模块

这些模块被设计成可以独立使用。

客户端

client模块提供功能,可以嵌入到应用程序中,使其具有自动更新功能。

示例

客户端模块的使用基本上如下

  1. 引入patchify::client模块。
  2. 创建一个新的Updater实例,通过一个Config实例传入适当的配置。

这就是全部!Updater实例将在后台以指定的间隔检查更新,并记录其活动。您可以使用提供的Updater.subscribe()方法监听这些活动。

此功能的示例提供为examples/cli-app-*.rs。有两个版本,这样就可以看到升级功能在实际中的运用。

要运行示例,请使用以下命令

cargo run --example cli-app-v1
cargo run --example cli-app-v2

以下要点值得关注

  • 使用tokio::signal::ctrl_c()函数等待Ctrl-C信号,以保持应用程序运行,以便可以观察更新过程。
  • 示例允许配置应用程序名称、API服务器位置、API服务器的公钥和更新间隔。

这更详细地在本指南的端到端示例中介绍。

服务器

server模块提供功能,可以嵌入到基于Web的API服务器中,使其能够为应用程序提供更新和相关信息的功能。除了关键功能外,它还提供了适合与Axum一起使用的端点实现。

示例

提供了一个简单但全面的如何使用服务器模块的示例,作为examples/axum-server.rs。这个示例基本上是标准API服务器集成测试的扩展版本,依赖于所有集成测试使用的相同通用代码,但增加了接受配置的能力。它是实际服务器实现的一个很好的起点。

要运行示例,请使用以下命令

cargo run --example axum-server

以下要点值得关注

  • 完整的实现逻辑可以在tests/common/server.rs中找到,该文件被集成测试使用。它提供了initialize()create_patchify_api_server()patchify_api_routes()函数。
  • tokio::signal::ctrl_c()函数用于等待Ctrl-C信号,这有助于服务器干净地关闭。这不是强制性的,但是一种常见的模式,也是良好的实践。它在集成测试中使用,以确保在需要时测试服务器继续在后台运行,并在测试完成后可以将其关闭。
  • 示例允许配置应用程序名称、运行在上的主机和端口、包含发布文件的文件夹以及具有相关哈希的版本列表。为了使用随机分配的端口,将值设置为0

默认情况下,示例中不会配置版本。要添加一些,应在配置的发布文件夹中创建发布文件,并适当地命名,然后计算哈希并将其添加到列表中。然后示例将为请求这些文件和哈希的客户提供服务。这在端到端示例中有更详细的说明。

设置

使用Patchify设置项目的过程简单且标准。您需要一个合理近期的Rust环境,在Linux机器上。目前没有超出构建标准Rust项目所需之外的特殊要求。

环境

在选择环境时应注意一些关键点

  • Debian和Ubuntu是首选的Linux发行版,尽管其他发行版也应工作良好,因为没有特殊要求。
  • 目前没有针对Windows原生运行或测试的目标,但计划在将来正确地支持它。在WSL上运行是可行的。
  • 在MacOS上原生运行尚未测试,尽管没有已知的技术原因说明它不会工作。

配置

使用Config结构体配置Patchify,这些结构体被传递到客户端的Updater实例以及服务器端的Core实例。这些内容在此处有文档说明

集成

对于客户端,只需创建一个具有合适ConfigUpdater实例即可。对于服务器,除了创建一个Core实例外,还有选择是否与Axum集成或直接从您自己的端点函数调用Core方法。

值得注意的是,客户端功能提供了两个主要的接触点来帮助协调升级过程:Updater.subscribe()方法和关键操作计数器。

关键操作计数器

关键操作计数器是一个简单的计数器,可以增加和减少,用于确定在升级后何时安全地重新启动应用程序。这是一种确保应用程序在重启时不在关键操作中的简单方法。

基本前提是,如果应用程序即将进行不应被中断的操作,可以增加计数器,在操作完成后减少。然后,Updater实例只有在计数器为零时才会重新启动应用程序。如果更新器即将重新启动应用程序,则它将拒绝启动任何新的关键操作,直到重启完成。

这使得将升级过程集成到应用程序中变得非常容易,并确保确切知道何时将重新启动。

如果需要更细致地控制重启方式,建议在应用程序启动时注册一个关键操作,并且永远不要注销它,而是依赖于状态变化事件来检测何时需要重启,并按自定义方式处理。

状态事件订阅

方法Updater.subscribe()允许应用程序监听Updater实例的活动,并对它广播的状态变化做出反应。这有助于更新应用程序的UI或用于日志记录。如果需要,它还提供了一种手动控制升级过程的方法。

端到端示例

无法直接提供100%正常工作的端到端示例,因为需要生成和将发布文件注册到服务器。然而,所有必要的组件都已提供,通过遵循本节中的几个简短步骤,您可以很快地设置一个完全工作的示例。

先决条件

您需要在Linux上设置Rust环境,并克隆Patchify存储库。本例中的步骤假设命令是在存储库根目录下运行的。这只是为了演示目的,在现实世界的场景中,您将使用自己的应用程序存储库,并将Patchify作为依赖项包含在内,如本README中所述。

1. 准备发布目录

假设是全新的克隆,为服务器创建一个新的目录,以便从中提供发布版本

mkdir -p /tmp/patchify-releases

2. 构建应用程序发布版本

现在我们需要编译客户端示例。虽然可以直接运行它们,但它们在测试运行时也会被编译,所以这是构建它们的简单方法,同时也确保在您的环境中所有测试都通过。

cargo test

3. 复制发布文件

测试完成后,需要将编译的二进制文件复制到发布目录

cp target/debug/examples/cli-app-v1 /tmp/patchify-releases/cli-app-1.0.0
cp target/debug/examples/cli-app-v2 /tmp/patchify-releases/cli-app-2.0.0

注意文件名的变化。这是因为Cargo希望每个示例都有一个不同的crate,而我们在这里实际上想创建两个相同应用程序的不同版本。因此,示例被命名为v1v2,分别对应版本1.0.02.0.0

为了能够在以后运行客户端应用程序,将应用程序二进制文件复制到当前目录

cp target/debug/examples/cli-app-v1 ./cli-app

再次注意文件名的变化。这是因为我们不在乎应用程序的版本,我们只想运行它。我们复制的文件将在更新程序运行时被新版本替换。

注意,如果您的本地Cargo设置用于构建的不同目录,则需要相应地调整路径。

4. 配置API服务器

现在需要运行服务器示例。这将提供我们刚刚创建的发布文件,并为客户端应用程序提供与之交互的API端点。

但是,首先我们需要一些配置。将examples/axum-server.toml文件复制到当前工作目录

cp examples/axum-server.toml .

现在编辑它,将releases值设置为/tmp/patchify-releases,并添加两个版本及其关联的哈希。文件看起来应该像这样

appname  = "cli-app"
host     = "127.0.0.1"
port     = 8000
releases = "/tmp/patchify-releases"

[versions]
"1.0.0" = "beef1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f"
"2.0.0" = "cafe1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f"

要获取哈希值,可以使用sha256sum命令

sha256sum /tmp/patchify-releases/cli-app-1.0.0
sha256sum /tmp/patchify-releases/cli-app-2.0.0

默认情况下,服务器将在端口 8000 上运行,并从本地主机提供服务。请随意更改这些值以适应您的环境。

5. 运行API服务器

现在您可以运行服务器示例了。请注意,您需要在不同终端窗口中保持此窗口打开,因此从现在起您将有两个终端窗口打开。

cargo run --example axum-server

服务器在启动时会检查发布文件的合法性,如果存在错误,则退出。请注意,这可能需要几秒钟的时间,因为需要读取每个文件的全部内容以计算哈希值。

6. 配置客户端应用程序

examples/cli-app.toml 文件复制到您的当前工作目录

cp examples/cli-app.toml .

现在编辑它,并将 updater_api_server 的值设置为服务器正在运行的地址。这应该是您配置的,但服务器在启动时会打印出地址和公钥。您还需要将此密钥添加到客户端配置中,作为 updater_api_key

appname            = "cli-app"
updater_api_server = "http://127.0.0.1:8000/api/"
updater_api_key    = "beef1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f"
update_on_startup  = false
update_interval    = 10

示例服务器每次启动都会生成一个新的密钥,因此每次重启服务器时都需要从服务器的输出中复制它。这是为了使演示过程更加健壮,而不是在服务器配置中接受私钥,以防错误生成,这可能会引起挫败感。

注意,在上面的配置示例中,update_on_startup 的值设置为 false。这是因为我们想看到更新过程,所以不希望应用程序在启动时更新自己。此外,update_interval 设置为 10,这样应用程序将每10秒检查一次更新,这不会等待得太久。您可以随意尝试这些值,并观察行为上的差异。

7. 运行客户端应用程序

我们现在可以运行客户端应用程序了!

./cli-app

应用程序将启动并按您在配置中指定的间隔检查更新。如果有更新可用,它将下载、验证、安装它们并重新启动自己。您应该看到状态变更事件被打印到控制台,并且当应用程序重新启动时,您应该看到打印的版本号发生变化。

依赖项

~15–29MB
~473K SLoC