release

package
v0.2.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 13, 2026 License: Apache-2.0 Imports: 19 Imported by: 0

Documentation

Overview

Package release 负责「把某个版本的 handoff 二进制正确落到某个路径」。

职责:

  • 查 GitHub 的 latest release,解出 tag 与本平台资产的下载 URL
  • 下载、校 sha256、解包、自检、原子替换,并把旧二进制留成 .prev

边界:

  • **不决定何时替换**:那是 internal/selfupdate 的事。本包是一个执行器, 调用方说装就装
  • 不知道 agentd、不知道任务、不读 handoff 的配置
  • 不做自动回滚(D10):留下 .prev 供人工 handoff upgrade --rollback

install.go —— 下载、校验、自检、原子替换。

边界:

  • 临时文件**必须**落在目标二进制的同目录:os.Rename 的原子性只在同一 文件系统内成立,从 /tmp rename 到 /usr/local/bin 会因跨设备直接失败
  • 任何一步失败都清干净临时文件:留一份坏二进制在二进制目录里, 下一轮可能被误当成已就绪的 pending
  • 不做自动回滚(D10):只把旧二进制留成 .prev,回退是人工命令
  • 下载与安装是两件事,前者可跨平台、后者必须在目标平台执行

Index

Constants

View Source
const ChecksumsName = "checksums.txt"

ChecksumsName 是校验和文件名,与 .github/workflows/release.yml 产出一致。

View Source
const DefaultAPIBase = "https://api.github.com"

DefaultAPIBase 是 GitHub REST API 的根。

D11:自动更新链路一律打 GitHub 原生 URL,不走自有域名——域名过期、DNS 故障、 重定向规则改错,任何一样都会让所有机器的自动更新一起哑掉。

View Source
const DefaultRepo = "Xsxdot/handoff"

DefaultRepo 是 Release 所在的 GitHub 仓库。

注意 go.mod 的 module path(github.com/Xsxdot/handoff)已与 GitHub owner 一致:`go install github.com/Xsxdot/handoff@latest` 与下载链指向同一个仓库。

Variables

This section is empty.

Functions

func Activate

func Activate(newPath, target string) (string, error)

Activate 把新二进制换到目标路径,旧的留成 <target>.prev。

参数:

  • newPath: Fetch 返回的临时文件路径(必须与 target 同目录)
  • target: 目标二进制路径(应已 EvalSymlinks 解析过)

返回:

  • 留存的旧二进制路径
  • 错误:目录不可写、rename 失败

注意:

  • 两次 rename 都是同目录内操作,因而是原子的。中途失败最坏的结果是 「旧的已挪到 .prev、新的还没就位」——此时目标路径暂时缺失, 所以第二次 rename 失败时会把 .prev 挪回来
  • **两次 rename 的顺序在 Windows 上是承重的**:Windows 允许 rename 一个 正在运行的 exe,但不允许覆盖或删除它。所以「先把旧的挪走、再把新的挪进来」 恰好就是 Windows 自更新的标准手法。**不要**把它「优化」成先删后写—— 那在 unix 上照样绿,在 Windows 上当场炸

func AssetName

func AssetName(tag, goos, goarch string) string

AssetName 拼装某平台的资产名。

参数:

  • tag: 版本号,形如 v0.1.0
  • goos / goarch: 目标平台

返回:

  • 资产文件名

注意:

  • 格式必须与 .github/workflows/release.yml 里的产出**逐字一致**。 不一致的症状是查得到版本但下不到东西,且每轮重试
  • 扩展名按平台分(见 archiveExt),install.sh / install.ps1 两边也依赖这条

func CurrentPlatform

func CurrentPlatform() (string, string)

CurrentPlatform 返回当前进程的 goos/goarch,便于调用方少写两个 runtime 引用。

func PrevPath

func PrevPath(target string) string

PrevPath 返回某目标路径对应的旧二进制留存路径。

func Rollback

func Rollback(target string) error

Rollback 把 <target>.prev 换回 target。

注意:

  • 这是 D10 的第二道防线,**只由人工命令触发**,自动更新循环永远不调它

func TempName

func TempName(tag string) string

TempName 返回某版本的临时文件名。

前导点让它在目录列表里不显眼;带上 tag 使多次尝试不同版本时互不覆盖。 Windows 上追加 .exe——selfCheck 要 exec 这个临时文件跑 version, 没有该后缀的文件在 Windows 上起不来。

Types

type Asset

type Asset struct {
	Name string
	URL  string
}

Asset 是一个 release 资产。

type Client

type Client struct {
	HTTP    *http.Client
	APIBase string
	Repo    string
}

Client 查 GitHub release。

func NewClient

func NewClient() *Client

NewClient 构造默认 client:30s 超时,打 GitHub 官方端点。

30s 而不是更长:查版本是一个可以失败的后台动作(失败就等下一个 interval), 卡住一个 goroutine 几分钟没有任何好处。

func (*Client) Latest

func (c *Client) Latest(ctx context.Context) (Release, error)

Latest 查最新发布。

参数:

  • ctx: 上下文,用于超时与取消

返回:

  • 解析后的 Release
  • 错误:网络失败、非 200、响应畸形

注意:

  • 匿名调用有 60 次/小时/IP 的限流,被限流时返回带 403 的错误。 **调用方不要重试**——interval 本身就是退避(spec §4.7)

type Installer

type Installer struct {
	HTTP *http.Client
	Log  *slog.Logger
}

Installer 执行下载与安装。

func NewInstaller

func NewInstaller(log *slog.Logger) *Installer

NewInstaller 构造默认 installer(10 分钟超时,覆盖慢网下的 20MB 下载)。

func (*Installer) Fetch

func (i *Installer) Fetch(ctx context.Context, rel Release, destDir string) (string, error)

Fetch 下载本平台资产、校验、解包、自检,返回可供 Activate 的临时文件路径。

参数:

  • ctx: 上下文
  • rel: 目标发布
  • destDir: 临时文件落点,**必须**与目标二进制同目录

返回:

  • 临时二进制的完整路径(已 chmod 0755 并通过自检)
  • 错误:缺资产、下载失败、校验不过、解包失败、自检不过

注意:

  • 任何一步失败都会把临时文件删掉,不留残件
  • 本函数是 FetchArchive + InstallArchive 的本机组合,行为与拆分前一致

func (*Installer) FetchArchive

func (i *Installer) FetchArchive(ctx context.Context, rel Release, goos, goarch string) ([]byte, string, error)

FetchArchive 按指定平台下载资产并校验完整性,返回字节与期望哈希。

参数:

  • ctx: 上下文
  • rel: 目标发布
  • goos / goarch: **目标机器**的平台,不是本机——跨平台推送时远端可能 是 linux/amd64 而本机是 darwin/arm64,必须知道该下哪份资产

返回:

  • tgz: 资产原文(tar.gz 字节,**未经解包**)
  • 期望的 sha256(十六进制小写),**来自 checksums.txt 的声明**—— 这是信任链的第一道校验,消费方把它原样传给 InstallArchive 让两端 比同一个来自 release 的声明,不互相背书
  • 错误:缺资产、下载失败、校验不过

注意:

  • **不解包、不自检**。自检要 exec 执行新二进制,而本机执行别的平台的 二进制必然失败——自检必须在目标平台上做(agentd 收到推送后)
  • 不重试:完整性失败重试只会重下同一份坏数据(spec §4.7)

func (*Installer) InstallArchive

func (i *Installer) InstallArchive(tgz []byte, wantSum, wantTag, destDir string) (string, error)

InstallArchive 校验、解包、自检一份已下载的资产,返回可供 Activate 的临时文件路径。

参数:

  • tgz: FetchArchive 返回的资产原文
  • wantSum: 期望的 sha256(十六进制小写),agentd 侧来自 CLI 推来的 query 参数,是信任链的第二道校验(传输完整性)
  • wantTag: 目标版本,自检时拿新二进制 version 首行与它比对
  • destDir: 临时文件落点,**必须**与目标二进制同目录

返回:

  • 临时二进制的完整路径(已 chmod 0755 并通过自检)
  • 错误:校验不过、解包失败、置位失败、自检不过

注意:

  • 任何一步失败都会把临时文件删掉,不留残件

type Release

type Release struct {
	Tag    string
	Assets []Asset
}

Release 是一次发布。

func (Release) AssetFor

func (r Release) AssetFor(goos, goarch string) (Asset, bool)

AssetFor 取本平台的资产。

返回:

  • 资产与是否找到。找不到说明这次发布漏了某平台

func (Release) Checksums

func (r Release) Checksums() (Asset, bool)

Checksums 取校验和文件资产。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL