logo
0
0
WeChat Login

selfupdate

Go Reference

selfupdate 是一个为 Go 命令行工具提供自动更新能力的库。它通过 GitHub Releases、Gitea Releases 或 CNB OpenAPI 检测并下载最新版本,实现二进制的原地替换,并支持更新失败时自动回滚。

功能特性

  • 多平台支持 — 根据运行时 GOOS/GOARCH 智能匹配对应的 release 产物
  • 多 Provider — 支持 GitHub、GitHub Enterprise、Gitea、CNB 四种发布源
  • 多压缩格式 — 支持 .zip.tar.gz.tgz.tar.xz.gz.xz.gzip 及裸二进制
  • 原子替换与回滚 — 更新过程采用写入临时文件 → 重命名的原子操作,失败时自动回滚至旧版本
  • 增量更新 — 内置 BSDiff patch 算法,支持仅下载差异部分进行增量更新
  • 完整性校验 — 支持 SHA256 摘要校验、ECDSA/RSA 签名验证
  • 私有仓库 — 支持 API Token 认证访问私有仓库的 release
  • Symlink 解析 — 自动追踪符号链接,更新实际二进制文件
  • 可定制日志 — 内置日志接口,支持 slog.Logger 适配、文本日志及静默模式

安装

go get cnb.cool/svn/selfupdate

要求 Go 1.25 及以上版本。

快速开始

最简自更新 — 一行代码即可让 CLI 工具自我更新:

package main import ( "log" "cnb.cool/svn/selfupdate" ) const version = "1.2.3" func main() { result, err := selfupdate.UpdateSelf(version, "owner/repo") if err != nil { log.Fatal(err) } switch result.Status { case selfupdate.UpdateStatusUpdated: println("已更新至", result.CurrentVersion) if result.Release != nil { println("更新日志:", result.Release.ReleaseNotes) } case selfupdate.UpdateStatusAlreadyInstalled, selfupdate.UpdateStatusNoRelease: println("当前无需更新,版本:", result.CurrentVersion) case selfupdate.UpdateStatusNewerThanTarget: println("当前版本高于远端 release,保持本地版本:", result.CurrentVersion) default: println("未知更新状态:", result.Status) } }

更新相关 API 返回 UpdateResultStatus 用来表达“未找到 release / 已安装目标版本 / 本地更新 / 本地版本更高”等显式状态;CurrentVersion 表示调用结束后本地应处于的版本;Release 则在命中远端 release 时提供发布元数据。

配置

完整仓库 URL 会自动推断 githubgiteacnb provider;私有实例或多 provider 混用场景下,建议显式构造 Updater

GitHub 私有仓库

通过 Config.APIToken 或环境变量 GITHUB_TOKEN 提供认证令牌,令牌也可从 git config github.token 自动读取。

up, _ := selfupdate.NewUpdater(selfupdate.Config{ Provider: selfupdate.ProviderGitHub, APIToken: "github_pat_xxx", }) result, _ := up.UpdateSelf(version, "owner/repo")

GitHub Enterprise

up, _ := selfupdate.NewUpdater(selfupdate.Config{ Provider: selfupdate.ProviderGitHubEnterprise, APIToken: "token", EnterpriseBaseURL: "https://github.company.com/api/v3", EnterpriseUploadURL: "https://github.company.com/", })

Gitea

支持自建 Gitea 实例,通过 GiteaBaseURL 覆盖默认地址。Token 可通过 Config.APIToken 或环境变量 GITEA_TOKEN 提供。

up, _ := selfupdate.NewUpdater(selfupdate.Config{ Provider: selfupdate.ProviderGitea, GiteaBaseURL: "https://gitea.example.com", }) result, _ := up.UpdateSelf(version, "owner/repo")

GiteaBaseURL 接受 https://gitea.example.comhttps://gitea.example.com/api/v1/ 两种格式,内部会自动规范化。

CNB

默认使用 https://api.cnb.cool/,私有实例可通过 CNBBaseURL 覆盖。Token 可通过 Config.APIToken 或环境变量 CNB_TOKEN 提供。

up, _ := selfupdate.NewUpdater(selfupdate.Config{ Provider: selfupdate.ProviderCNB, }) result, _ := up.UpdateSelf(version, "owner/repo")

使用示例

用户确认式更新

先检测是否有新版本,经用户确认后再按选中的 release 执行更新:

latest, found, err := selfupdate.DetectLatest("owner/repo") if err != nil { log.Fatal(err) } if !found { println("未找到 release") return } isLatest, err := selfupdate.IsLatest(version, latest.Version) if err != nil { log.Fatal(err) } if isLatest { println("当前已是最新版本") return } print("是否更新至 ", latest.Version, "? (y/n): ") input, _ := bufio.NewReader(os.Stdin).ReadString('\n') if input != "y\n" { return } result, err := selfupdate.UpdateSelfToRelease(version, latest) if err != nil { log.Fatal(err) } switch result.Status { case selfupdate.UpdateStatusUpdated: println("已更新至", result.CurrentVersion) case selfupdate.UpdateStatusAlreadyInstalled: println("当前已安装目标版本", result.CurrentVersion) case selfupdate.UpdateStatusNewerThanTarget: println("当前版本高于目标版本", result.CurrentVersion) }

更新指定命令

更新非当前可执行文件的其他命令:

result, err := selfupdate.UpdateCommand("/usr/local/bin/mytool", version, "owner/repo") if err != nil { log.Fatal(err) } switch result.Status { case selfupdate.UpdateStatusUpdated: println("命令已更新至", result.CurrentVersion) case selfupdate.UpdateStatusAlreadyInstalled, selfupdate.UpdateStatusNoRelease: println("命令无需更新,当前版本:", result.CurrentVersion) case selfupdate.UpdateStatusNewerThanTarget: println("本地命令版本更高,保持现状:", result.CurrentVersion) }

检测后更新到指定版本

DetectLatest / DetectVersion 只负责选择远端 release,因此仍返回 *Release。当你想在检测完成后继续执行带状态的更新,可把返回值传给 UpdateSelfToRelease*UpdateCommandToRelease*

selected, found, err := selfupdate.DetectVersion("owner/repo", "v1.2.4") if err != nil { log.Fatal(err) } if !found { println("目标版本不存在") return } result, err := selfupdate.UpdateSelfToRelease(version, selected) if err != nil { log.Fatal(err) } switch result.Status { case selfupdate.UpdateStatusUpdated: println("已切换到", result.CurrentVersion) case selfupdate.UpdateStatusAlreadyInstalled: println("目标版本已经安装") case selfupdate.UpdateStatusNewerThanTarget: println("当前版本高于目标版本,未回退") }

带校验的更新

使用 ApplyOptions 配置校验和及备份路径:

import "crypto/sha256" opts := selfupdate.ApplyOptions{ Checksum: sha256.Sum256(expectedBinary)[:], OldSavePath: "./mycmd.backup", } result, err := selfupdate.UpdateSelfWithOptions(version, "owner/repo", opts)

ECDSA 签名验证

opts := selfupdate.ApplyOptions{} if err := opts.SetPublicKeyPEM(pemBytes); err != nil { log.Fatal(err) } result, err := selfupdate.UpdateSelfWithOptions(version, "owner/repo", opts)

增量更新(BSDiff)

当 release 产物为 BSDiff patch 文件时,使用 BinaryPatcher 应用增量更新:

opts := selfupdate.ApplyOptions{ Patcher: selfupdate.NewBSDiffPatcher(), } result, err := selfupdate.UpdateSelfWithOptions(version, "owner/repo", opts)

Validator 校验器

使用 SHA2ValidatorECDSAValidator 对 release 产物进行附加校验。校验器会自动下载与产物同名的 .sha256.sig 文件:

up, _ := selfupdate.NewUpdater(selfupdate.Config{ Provider: selfupdate.ProviderGitHub, Validator: &selfupdate.SHA2Validator{}, }) result, _ := up.UpdateSelf(version, "owner/repo")

检测指定版本

rel, found, err := selfupdate.DetectVersion("owner/repo", "1.2.3")

日志配置

默认静默。启用 slog 日志:

selfupdate.SetSlogLogger(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{ Level: slog.LevelDebug, })))

或使用内置文本日志:

selfupdate.SetLogger(selfupdate.NewTextLogger(os.Stderr, selfupdate.LevelDebug))

API 文档

核心类型

Updater

管理 self-update 生命周期的核心结构体。通过 NewUpdater(Config) 构造,或使用 DefaultUpdater() 获取默认 GitHub 实例。

方法:

方法签名说明
UpdateSelf(current, slug string) (*UpdateResult, error)检测并更新当前可执行文件至最新版本
UpdateSelfWithOptions(current, slug string, opts ApplyOptions) (*UpdateResult, error)同上,支持高级选项
UpdateSelfToRelease(current string, rel *Release) (*UpdateResult, error)更新当前可执行文件到已选中的 release
UpdateSelfToReleaseWithOptions(current string, rel *Release, opts ApplyOptions) (*UpdateResult, error)同上,支持高级选项
UpdateCommand(cmdPath, current, slug string) (*UpdateResult, error)检测并更新指定路径的命令
UpdateCommandWithOptions(cmdPath, current, slug string, opts ApplyOptions) (*UpdateResult, error)同上,支持高级选项
UpdateCommandToRelease(cmdPath, current string, rel *Release) (*UpdateResult, error)更新指定路径的命令到已选中的 release
UpdateCommandToReleaseWithOptions(cmdPath, current string, rel *Release, opts ApplyOptions) (*UpdateResult, error)同上,支持高级选项
UpdateTo(rel *Release, cmdPath string) error从已检测到的 Release 下载并替换指定命令
UpdateToWithOptions(rel *Release, cmdPath string, opts ApplyOptions) error同上,支持高级选项
DetectLatest(slug string) (*Release, bool, error)检测最新 release 版本
DetectVersion(slug, version string) (*Release, bool, error)检测指定版本的 release

Config

Updater 的运行配置:

字段类型说明
ProviderProvider发布源类型,可选 ProviderGitHubProviderGitHubEnterpriseProviderGiteaProviderCNB
APITokenstringAPI 认证令牌
EnterpriseBaseURLstringGitHub Enterprise API 地址(仅 GHE)
EnterpriseUploadURLstringGitHub Enterprise 上传地址(仅 GHE)
GiteaBaseURLstringGitea 实例地址,默认 https://gitea.com
CNBBaseURLstringCNB 实例地址,默认 https://api.cnb.cool/
ValidatorValidatorrelease 产物附加校验器

UpdateResult

一次更新调用的显式结果:

字段类型说明
StatusUpdateStatus本次更新调用的最终状态
PreviousVersionstring调用时传入并规范化后的当前版本
CurrentVersionstring调用结束后本地程序应处于的版本
Release*Release命中的远端 release;未找到 release 时为 nil

方法:

方法签名说明
Updated() bool判断本次调用是否实际执行了更新
HasRelease() bool判断本次调用是否命中了远端 release

UpdateResult 只用于“会改写二进制”的更新接口;DetectLatest / DetectVersion 仍返回 *Release,因为它们只负责选择目标 release,不承担版本比较与状态表达。

UpdateStatus

常量说明
UpdateStatusNoRelease未找到可用 release
UpdateStatusAlreadyInstalled目标 release 已经安装在本地
UpdateStatusNewerThanTarget当前版本高于目标 release
UpdateStatusUpdated已成功完成更新

Release

匹配到的 release 产物信息:

字段类型说明
Versionstringrelease 版本号(不含 v 前缀)
AssetURLstring产物下载地址
AssetNamestring产物文件名
ReleaseNotesstring更新日志(Markdown)
URLstringrelease 页面地址

ApplyOptions

二进制替换阶段的高级选项:

字段类型说明
Checksum[]byte期望的 SHA256 摘要,用于下载后校验
OldSavePathstring旧二进制备份路径,为空则删除旧文件
PatcherBinaryPatcher增量更新 patch 算法
VerifierSignatureVerifier签名校验算法
PublicKeycrypto.PublicKey签名验证公钥

方法:

方法签名说明
CheckPermissions(cmdPath string) error预检目标路径是否可写
SetPublicKeyPEM(pembytes []byte) error从 PEM 编码填充 PublicKey 字段

包级函数

使用 DefaultUpdater() 的快捷封装:

函数签名说明
UpdateSelf(current, slug string) (*UpdateResult, error)自更新至最新版本
UpdateSelfWithOptions(current, slug string, opts ApplyOptions) (*UpdateResult, error)带选项自更新
UpdateSelfToRelease(current string, rel *Release) (*UpdateResult, error)自更新到已选中的 release
UpdateSelfToReleaseWithOptions(current string, rel *Release, opts ApplyOptions) (*UpdateResult, error)带选项自更新到已选中的 release
UpdateCommand(cmdPath, current, slug string) (*UpdateResult, error)更新指定命令
UpdateCommandWithOptions(cmdPath, current, slug string, opts ApplyOptions) (*UpdateResult, error)带选项更新指定命令
UpdateCommandToRelease(cmdPath, current string, rel *Release) (*UpdateResult, error)更新指定命令到已选中的 release
UpdateCommandToReleaseWithOptions(cmdPath, current string, rel *Release, opts ApplyOptions) (*UpdateResult, error)带选项更新指定命令到已选中的 release
UpdateTo(assetURL, cmdPath string) error从 URL 下载并替换
UpdateToWithOptions(assetURL, cmdPath string, opts ApplyOptions) error带选项从 URL 下载替换
DetectLatest(slug string) (*Release, bool, error)检测最新 release
DetectVersion(slug, version string) (*Release, bool, error)检测指定版本 release

版本操作

函数签名说明
CompareVersion(left, right string) (int, error)语义化版本比较,返回 -101
IsLatest(current, latest string) (bool, error)判断当前版本是否为最新

版本号支持 v1.2.31.2.3 格式,v 前缀会被自动去除。预发布版本(如 1.2.3-beta)在比较中小于对应的正式版本。

解压

函数签名说明
UncompressCommand(src io.Reader, url, cmd string) (io.Reader, error)从压缩流中提取指定命令名的二进制

根据 URL 后缀自动识别压缩格式,支持 .zip.tar.gz.tgz.tar.xz.gz.xz.gzip

校验器

Validator 接口

type Validator interface { Validate(release, asset []byte) error Suffix() string }
实现类型Suffix() 返回值说明
SHA2Validator.sha256SHA256 摘要校验
ECDSAValidator.sigECDSA 公钥签名校验

校验器会在下载 release 产物的同时,额外下载 {asset}{suffix} 文件(如 mycmd_linux_amd64.tar.gz.sha256),然后调用 Validate 进行校验。

工厂函数

函数签名说明
NewUpdater(config Config) (*Updater, error)根据配置创建 Updater
DefaultUpdater() *Updater返回默认 GitHub Updater
NewBSDiffPatcher() BinaryPatcher创建 BSDiff 增量 patch 算法实例
NewECDSASignatureVerifier() SignatureVerifier创建 ECDSA 签名校验器
NewRSASignatureVerifier() SignatureVerifier创建 RSA 签名校验器
RollbackError(err error) error从更新错误中提取回滚错误(若有)

日志

函数签名说明
SetLogger(l Logger)设置自定义日志实现
SetSlogLogger(logger *slog.Logger)设置 slog.Logger 适配
CurrentLogger() Logger获取当前日志实例
NewNopLogger() Logger创建静默日志
NewSlogLogger(logger *slog.Logger) Logger创建 slog 适配器
NewTextLogger(writer io.Writer, minLevel Level) Logger创建文本日志

Level 枚举值:LevelDebugLevelInfoLevelWarnLevelError

Release 产物规范

二进制命名

release 产物文件名需遵循以下格式,以便库自动匹配当前平台的二进制:

{command}_{os}_{arch}{.ext} {command}-{os}-{arch}{.ext}

示例:

  • mycmd_linux_amd64.tar.gz
  • mycmd-darwin-arm64.zip
  • mycmd_windows_amd64.exe.zip

Windows 平台下会自动匹配带 .exe 后缀的产物。

版本标签

使用语义化版本(semver),如 v1.2.31.2.3。预发布版本(如 v1.2.3-beta)在 DetectLatest 时默认跳过,但 DetectVersion 指定版本号时不受此限制。Release tag 中的常见前缀(如 release-v)在检测时会被自动剥离。

校验文件

若使用 Validator,需在 release 中附加校验文件:

  • SHA256 — 文件名 {asset}.sha256,内容为 SHA256 摘要的十六进制字符串
  • ECDSA 签名 — 文件名 {asset}.sig,内容为二进制签名

附带命令行工具

工具路径说明
go-get-releasecmd/go-get-release类似 go get,安装 release 二进制至 $GOBIN
detect-latest-releasecmd/detect-latest-release命令行查询最新 release 版本及下载地址
selfupdate-examplecmd/selfupdate-example自更新示例程序

更新流程

DetectLatest / DetectVersion │ ▼ 选择目标 Release │ ▼ Update* / Update*ToRelease* │ ▼ 版本比较(CompareVersion) │ ▼ 下载 Asset(HTTP GET) │ ▼ Validator 校验(可选,下载附加 .sha256/.sig) │ ▼ 解压(UncompressCommand) │ ▼ 原子替换(写入 .new → rename 旧 → rename 新) │ ┌────┴────┐ │ 成功 │ 失败 │ ▼ │ 自动回滚(rename .old 回原路径) ▼ 清理旧文件 / 保存备份 │ ▼ 返回 UpdateResult.Status

依赖

依赖说明
github.com/google/go-github/v30GitHub API SDK
github.com/ulikunitz/xzXZ 压缩格式支持
golang.org/x/crypto加密算法(间接依赖)

许可证

MIT License

About

一个为 Go 命令行工具提供自动更新能力的库。

8.93 MiB
0 forks0 stars1 branches8 TagREADMEMIT license
Language
Go99.7%
Shell0.3%