selfupdate

package
v0.3.15 Latest Latest
Warning

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

Go to latest
Published: Oct 7, 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 = "Autumn-27/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