Help us shape the future of Go! Take the 2026 Go Developer Survey

selfupdate

package
v0.15.1 Latest Latest
Warning

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

Go to latest
Published: Oct 27, 2026 License: AGPL-3.0 Imports: 19 Imported by: 0

Documentation

Overview

Package selfupdate implements ARTEX 的页面一键更新:从 GitHub Release 拉取新版 二进制、校验、暂存,并在下次启动时原子换装。

整体分工(见 start.sh / start.bat):

启动脚本  = 傻瓜守护循环,只负责"进程退出后按退出码决定是否再拉起"
本包      = 全部易错逻辑(下载 / SHA256 校验 / 冒烟 / 换装 / 失败回滚)

之所以把换装放在 Go 而不是脚本里,是因为 sha256 校验和冒烟测试在 sh 和 bat 上 要写两套(sha256sum / shasum / certutil),而这恰恰是最不能出错的一环——换上一个 跑不起来的二进制,守护进程会忠实地反复拉起它,用户只能上机器手工救。

一次完整升级经过三次进程启动:

① 旧版 server 收到 /api/update/apply → 下载校验 → 暂存 artex.new → exit 75
② 脚本重新拉起旧版 → Bootstrap 发现 artex.new → 校验+冒烟 → 换装 → exit 75
③ 脚本重新拉起,此时已是新版 → Bootstrap 记一次尝试 → 启动成功后清除标记

任何一步失败都退回旧版:② 校验不过就删掉暂存件继续跑旧版;③ 连续 3 次没活到 清除标记(起不来就崩)则自动把 artex.old 换回去。

Index

Constants

View Source
const ExitRestart = 75

ExitRestart 是"请守护进程重新拉起我"的退出码(EX_TEMPFAIL)。启动脚本看到它 就立刻重跑,不计入崩溃退避。0 表示用户正常停止(脚本退出循环),其余均视为崩溃。

View Source
const Repo = "RC-CHN/ARTEX"

Repo 是发布源。写死而不是做成配置项:更新源可配等于给任何能改配置的人一条 远程代码执行通道,对一个渗透测试平台来说这个口子开不得。

View Source
const SettleDelay = 30 * time.Second

SettleDelay 是判定"新版本活下来了"所需的运行时长。

Variables

This section is empty.

Functions

func AssetName

func AssetName(tag, goos, goarch string) string

AssetName 返回当前平台对应的发布包名,与 build.sh 的 package_binary 保持一致: artex-<版本>-<os>-<arch>.zip(版本号不带 v 前缀)。

func Bootstrap

func Bootstrap() (Action, State)

Bootstrap 在 main 的最开头运行,必须在任何监听端口、打开数据库之前调用。

三种局面:

① 存在暂存件 artex.new  → 校验 + 冒烟,通过则换装并要求重启;不通过则丢弃继续跑旧版
② 只剩标记文件          → 说明刚换装完,累计一次尝试;连续失败够多次则回滚
③ 什么都没有            → 正常启动

func CompareVersions

func CompareVersions(a, b string) (int, bool)

CompareVersions 比较两个版本号,返回 -1/0/1(a<b / a==b / a>b)。 ok=false 表示至少一边不是可比较的版本号(例如本地开发构建的 "dev" 或 git describe 产出的 "0.3.7-2-gabc1234-dirty"),此时调用方应禁用一键更新, 否则会把开发中的构建"升级"成正式版、覆盖掉未提交的改动。

func HasBackup

func HasBackup() bool

HasBackup 报告是否存在可回滚的上一版本,供前端决定要不要显示回滚按钮。

func InDocker

func InDocker() bool

InDocker 报告进程是否跑在容器里。Docker 下换装写的是容器可写层, `docker compose up -d` 重建容器会退回镜像自带的版本——这是预期行为 (那时用户本来就在拉新镜像),但前端要能据此把话说清楚。

func NewClient

func NewClient(proxy string) *http.Client

NewClient 构造一个只认 GitHub 域名的 HTTP 客户端。proxy 为空则直连。

刻意不复用默认 Transport:升级链路必须强制走 TLS 且校验证书,不能被别处 设置的 InsecureSkipVerify 之类影响到。

func Rollback

func Rollback() error

Rollback 是 /api/update/rollback 的实现:主动退回上一版本。 只做换装,重启同样交给守护脚本(调用方随后以 ExitRestart 退出)。

func Settle

func Settle()

Settle 确认新版本已稳定运行,清除升级标记。

由 main 在 HTTP 监听起来之后延迟调用:活过这段时间才算数,否则标记留在原地, 下次启动继续累计尝试次数,直到触发回滚。

func Stage

func Stage(ctx context.Context, c *http.Client, rel *Release, currentVersion string, prog Progress) error

Stage 下载指定 Release 的当前平台发布包,校验后把新二进制暂存为 artex.new。

走的是完整 zip 而不是裸二进制,理由有两个:现有 Release 的 SHA256SUMS 本来就 只覆盖 zip,走 zip 不需要改 CI,也能兼容已经发布出去的历史版本;zip 里还带着 skills/,为将来同步内置 skill 留了口子。代价只是多下载 skills 那几百 KB。

函数返回即代表暂存完成,调用方随后优雅关闭并以 ExitRestart 退出。

Types

type Action

type Action int

Action 是 Bootstrap 给 main 的指令。

const (
	// Continue:照常启动 server。
	Continue Action = iota
	// Restart:立刻以 ExitRestart 退出,让守护脚本重新拉起。
	Restart
)

type Asset

type Asset struct {
	Name string `json:"name"`
	URL  string `json:"browser_download_url"`
	Size int64  `json:"size"`
}

Asset 是 Release 上挂的一个文件。

type Paths

type Paths struct {
	Dir     string // 可执行文件所在目录
	Current string // 当前运行的二进制        artex      / artex.exe
	New     string // 暂存的新版本            artex.new  / artex.new.exe
	Sum     string // 新版本的 sha256(hex)  artex.new.sha256 / artex.new.exe.sha256
	Old     string // 换装前备份的旧版本      artex.old  / artex.old.exe
	Marker  string // 升级状态标记            artex.upgrade.json
}

Paths 是一次升级涉及的全部文件,统一挂在**可执行文件所在目录**下。 刻意不用 CWD:服务化运行时工作目录可能是 / 或任意路径,用 CWD 会让暂存件落到 别处,换装逻辑直接失效。

func ResolvePaths

func ResolvePaths() (Paths, error)

ResolvePaths 按当前可执行文件推导全部升级路径。

Windows 上 .new/.old 也必须带 .exe 后缀,否则冒烟测试和换装后的执行都会失败, 所以先把后缀摘掉再拼,两个平台的命名才对称。

type Phase

type Phase string

Phase 是升级过程中的阶段,直接用作 SSE 事件里的 phase 字段。

const (
	PhaseIdle     Phase = "idle"
	PhaseDownload Phase = "downloading"
	PhaseVerify   Phase = "verifying"
	PhaseExtract  Phase = "extracting"
	PhaseStaged   Phase = "staged"
	PhaseFailed   Phase = "failed"
)

type Progress

type Progress func(ph Phase, pct int, msg string)

Progress 由调用方提供,用来把进度推给前端。pct 仅在下载阶段有意义(0-100), 其余阶段传 -1。

type Release

type Release struct {
	TagName     string    `json:"tag_name"`
	Name        string    `json:"name"`
	Body        string    `json:"body"`
	Draft       bool      `json:"draft"`
	Prerelease  bool      `json:"prerelease"`
	PublishedAt time.Time `json:"published_at"`
	HTMLURL     string    `json:"html_url"`
	Assets      []Asset   `json:"assets"`
}

Release 是 GitHub Release 里我们关心的字段。

func FetchLatest

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

FetchLatest 查询最新正式版。

func (*Release) FindAsset

func (r *Release) FindAsset(name string) (Asset, bool)

FindAsset 在 Release 里按名字找资产。

type State

type State struct {
	Pending     bool   // 换装后尚未确认稳定
	RolledBack  bool   // 本次启动刚刚执行过自动回滚
	FailedStage bool   // 暂存件校验/冒烟未通过,已丢弃
	Detail      string // 面向用户的一句话说明
}

State 描述本次启动时的升级状态,供 /api/update/check 如实告诉前端 "上一次升级是成功了还是被回滚了"。

Jump to

Keyboard shortcuts

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