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
- Variables
- func AssetName(tag, goos, goarch string) string
- func Bootstrap() (Action, State)
- func CompareVersions(a, b string) (int, bool)
- func ExtractMember(zipPath, baseName, dst string) error
- func FetchVerifiedZip(ctx context.Context, c *http.Client, rel *Release, dir string, prog Progress) (string, error)
- func HasBackup() bool
- func InDocker() bool
- func NewClient(proxy string) *http.Client
- func ReplaceBinary(newPath, target string) error
- func Rollback() error
- func Settle()
- func SmokeTestBinary(bin string, args ...string) error
- func Stage(ctx context.Context, c *http.Client, rel *Release, currentVersion string, ...) error
- func ValidVersionTag(s string) bool
- type Action
- type Asset
- type Paths
- type Phase
- type Progress
- type Release
- type State
Constants ¶
const ExitRestart = 75
ExitRestart 是"请守护进程重新拉起我"的退出码(EX_TEMPFAIL)。启动脚本看到它 就立刻重跑,不计入崩溃退避。0 表示用户正常停止(脚本退出循环),其余均视为崩溃。
const SettleDelay = 30 * time.Second
SettleDelay 是判定"新版本活下来了"所需的运行时长。
Variables ¶
var Repo = "chu0119/artex"
Repo 是发布源。
刻意做成构建期变量而不是**运行时配置项**:更新源可配,等于给任何能改配置的人 一条远程代码执行通道,对一个渗透测试平台来说这个口子开不得。二开/镜像要在构建时 用 `-ldflags "-X github.com/chu0119/artex/selfupdate.Repo=owner/name"` 覆盖, 这样改的是产物本身,不是运行期可篡改的开关。
Functions ¶
func AssetName ¶
AssetName 返回当前平台对应的发布包名,与 build.sh 的 package_binary 保持一致: artex-<版本>-<os>-<arch>.zip(版本号不带 v 前缀)。
func Bootstrap ¶
Bootstrap 在 main 的最开头运行,必须在任何监听端口、打开数据库之前调用。
三种局面:
① 存在暂存件 artex.new → 校验 + 冒烟,通过则换装并要求重启;不通过则丢弃继续跑旧版 ② 只剩标记文件 → 说明刚换装完,累计一次尝试;连续失败够多次则回滚 ③ 什么都没有 → 正常启动
func CompareVersions ¶
CompareVersions 比较两个版本号,返回 -1/0/1(a<b / a==b / a>b)。 ok=false 表示至少一边不是可比较的版本号(例如本地开发构建的 "dev" 或 git describe 产出的 "0.3.7-2-gabc1234-dirty"),此时调用方应禁用一键更新, 否则会把开发中的构建"升级"成正式版、覆盖掉未提交的改动。
func ExtractMember ¶ added in v0.3.22
ExtractMember 从发布包里取出**基名**为 baseName 的文件写到 dst(0755)。
按基名匹配而不是拼完整路径:包内目录名带版本号,拼错一个字符就整个失败; 按基名找更耐改,也不会被包内多余的层级影响。
func FetchVerifiedZip ¶ added in v0.3.22
func FetchVerifiedZip(ctx context.Context, c *http.Client, rel *Release, dir string, prog Progress) (string, error)
FetchVerifiedZip 下载 rel 对应当前平台/架构的发布包,按 SHA256SUMS 校验后返回 本地路径。调用方负责删除返回的文件。
文件落在 dir 下而不是 /tmp:/tmp 常常是独立挂载点,跨设备 rename 会失败。
func InDocker ¶
func InDocker() bool
InDocker 报告进程是否跑在容器里。Docker 下换装写的是容器可写层, `docker compose up -d` 重建容器会退回镜像自带的版本——这是预期行为 (那时用户本来就在拉新镜像),但前端要能据此把话说清楚。
func NewClient ¶
NewClient 构造一个只认 GitHub 域名的 HTTP 客户端。proxy 为空则直连。
刻意不复用默认 Transport:升级链路必须强制走 TLS 且校验证书,不能被别处 设置的 InsecureSkipVerify 之类影响到。
func ReplaceBinary ¶ added in v0.3.22
ReplaceBinary 原子地把 newPath 装到 target(保留旧版为 target+".prev")。
用于伴随二进制自我换装:同目录 rename 是原子的;旧版留一份,装完起不来还能手工退回。
func Rollback ¶
func Rollback() error
Rollback 是 /api/update/rollback 的实现:主动退回上一版本。 只做换装,重启同样交给守护脚本(调用方随后以 ExitRestart 退出)。
func Settle ¶
func Settle()
Settle 确认新版本已稳定运行,清除升级标记。
由 main 在 HTTP 监听起来之后延迟调用:活过这段时间才算数,否则标记留在原地, 下次启动继续累计尝试次数,直到触发回滚。
func SmokeTestBinary ¶ added in v0.3.22
SmokeTestBinary 用 -h 拉起一个二进制,确认它在当前系统上真的能执行。 挡掉下载截断、架构选错(exec format error)、缺动态库这类问题。
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 退出。
func ValidVersionTag ¶ added in v0.3.24
ValidVersionTag 报告字符串是否是允许的版本号形态。hostd 的 /self-upgrade 用它 做入参校验——那个参数最终会拼进 GitHub API 的 URL,必须收窄形态。 刻意**不做 TrimSpace**:带空白的输入直接拒绝,而不是悄悄纠正后再用。
Types ¶
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 ¶
ResolvePaths 按当前可执行文件推导全部升级路径。
Windows 上 .new/.old 也必须带 .exe 后缀,否则冒烟测试和换装后的执行都会失败, 所以先把后缀摘掉再拼,两个平台的命名才对称。
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 FetchByTag ¶ added in v0.3.22
FetchByTag 查询指定 tag 的 Release(伴随二进制自我换装时按主程序版本对齐用)。
版本号有两种写法:Release tag 带 v 前缀(v0.3.23),而二进制里注入的版本号不带 (build.sh 会 `${ARTEX_BUILD_VERSION#v}` 剥掉)。两种都试,避免因写法差异 报"版本不存在"——实测 hostd 拿 0.3.23 去查就撞上了这个。
func FetchLatest ¶
FetchLatest 查询最新正式版。