fix: use Hinln/ARTEX for authenticated release updates
This commit is contained in:
1 parent
e6ec569991
commit
f3e3f54b6c
6 files changed
+220
-12
No files matched your search
@@ -4,6 +4,9 @@ POSTGRES_USER=artex
|
|||||||
POSTGRES_PASSWORD=change-me-please
|
POSTGRES_PASSWORD=change-me-please
|
||||||
POSTGRES_DB=artex
|
POSTGRES_DB=artex
|
||||||
|
|
||||||
|
# 私有 GitHub Release 更新(仅后端读取;令牌需 Hinln/ARTEX 的 Contents: read)
|
||||||
|
ARTEX_UPDATE_GITHUB_TOKEN=
|
||||||
|
|
||||||
# LLM(二选一或都填;也可留空到 UI 里配)
|
# LLM(二选一或都填;也可留空到 UI 里配)
|
||||||
ANTHROPIC_API_KEY=
|
ANTHROPIC_API_KEY=
|
||||||
OPENAI_API_KEY=
|
OPENAI_API_KEY=
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ AI 自主渗透测试系统(Go 后端 + Next.js 前端)
|
|||||||
### 方式一:一键安装脚本(推荐)
|
### 方式一:一键安装脚本(推荐)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/Autumn-27/ARTEX.git
|
git clone https://github.com/Hinln/ARTEX.git
|
||||||
cd ARTEX
|
cd ARTEX
|
||||||
./install.sh
|
./install.sh
|
||||||
```
|
```
|
||||||
@@ -84,7 +84,7 @@ cd ARTEX
|
|||||||
### 方式二:Docker Compose(手动)
|
### 方式二:Docker Compose(手动)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/Autumn-27/ARTEX.git
|
git clone https://github.com/Hinln/ARTEX.git
|
||||||
cd ARTEX
|
cd ARTEX
|
||||||
cp .env.example .env # 填 POSTGRES_PASSWORD、可选 ANTHROPIC_API_KEY
|
cp .env.example .env # 填 POSTGRES_PASSWORD、可选 ANTHROPIC_API_KEY
|
||||||
docker compose up -d # 拉取 autumn27/artex 镜像 + postgres
|
docker compose up -d # 拉取 autumn27/artex 镜像 + postgres
|
||||||
@@ -100,7 +100,7 @@ docker compose up -d # 拉取 autumn27/artex 镜像 + postgres
|
|||||||
|
|
||||||
### 方式三:下载预编译二进制(Releases)
|
### 方式三:下载预编译二进制(Releases)
|
||||||
|
|
||||||
到 [Releases](https://github.com/Autumn-27/ARTEX/releases) 下载对应平台的 zip,解压后得到 `artex` + `start.sh`(Windows 为 `start.bat`)+ `skills/` + `config.example.json`:
|
到 [Releases](https://github.com/Hinln/ARTEX/releases) 下载对应平台的 zip,解压后得到 `artex` + `start.sh`(Windows 为 `start.bat`)+ `skills/` + `config.example.json`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp config.example.json config.json # 填好 database 连接
|
cp config.example.json config.json # 填好 database 连接
|
||||||
@@ -144,6 +144,21 @@ ARTEX_TARGETS=linux/amd64,windows/amd64 ./build.sh --release
|
|||||||
|
|
||||||
> 升级只换程序、不动数据:Postgres 数据卷 `pgdata`、`./data`(jwt.key / SQLite 等)、`./skills` 都会保留。**数据库迁移无需手动执行**——`artex` 每次启动会幂等重跑 `schema.sql`(含 `ADD COLUMN` / `CREATE INDEX IF NOT EXISTS`),即“重启即迁移”。升级前仍建议先备份 `./data` 与数据库。
|
> 升级只换程序、不动数据:Postgres 数据卷 `pgdata`、`./data`(jwt.key / SQLite 等)、`./skills` 都会保留。**数据库迁移无需手动执行**——`artex` 每次启动会幂等重跑 `schema.sql`(含 `ADD COLUMN` / `CREATE INDEX IF NOT EXISTS`),即“重启即迁移”。升级前仍建议先备份 `./data` 与数据库。
|
||||||
|
|
||||||
|
### 本仓库的私有 Release 更新源
|
||||||
|
|
||||||
|
页面一键更新固定从 [Hinln/ARTEX Releases](https://github.com/Hinln/ARTEX/releases) 检查和下载版本。克隆或通过 Git 更新源码也使用 `Hinln/ARTEX`;原项目的 Go module 路径保持不变。
|
||||||
|
|
||||||
|
这是私有仓库。请为运行 ARTEX 的后端进程设置 `ARTEX_UPDATE_GITHUB_TOKEN`,令牌只需本仓库的 **Contents: read** 权限。令牌由后端用于查询 Release 和下载附件,不发送给浏览器,也不要提交到 Git。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 先在当前 shell 或服务环境中安全设置 ARTEX_UPDATE_GITHUB_TOKEN
|
||||||
|
./start.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
本地直接启动时,`start.sh` 不会自动读取 `.env`;必须将变量导出到进程环境。Docker Compose 部署则可在未纳入 Git 的 `.env` 中填写该变量,并重建 artex 容器;镜像需要包含本分支的更新代码,旧版镜像不会因此切换升级源。
|
||||||
|
|
||||||
|
需要先在本仓库发布非 draft、非 prerelease 的正式 Release,并附上 `artex-<版本>-<os>-<arch>.zip` 和 `SHA256SUMS`,页面才能提供可安装更新。仓库里只有源码时还不能在线升级。现有 Release 工作流会在推送 `v*` tag 时为当前仓库生成这些附件。
|
||||||
|
|
||||||
### 方式一:页面一键更新(推荐)
|
### 方式一:页面一键更新(推荐)
|
||||||
|
|
||||||
在 **系统配置** 页(侧边栏「系统配置」→ `/system/settings`)的**版本与更新**卡片里,可以直接检查并安装新版本,无需登录服务器。
|
在 **系统配置** 页(侧边栏「系统配置」→ `/system/settings`)的**版本与更新**卡片里,可以直接检查并安装新版本,无需登录服务器。
|
||||||
@@ -182,7 +197,7 @@ docker image prune -f # 清理旧镜像(可选)
|
|||||||
|
|
||||||
### 方式四:预编译二进制(Releases)
|
### 方式四:预编译二进制(Releases)
|
||||||
|
|
||||||
到 [Releases](https://github.com/Autumn-27/ARTEX/releases) 下载新版本 zip,停掉旧进程后覆盖 `artex` 与 `skills/`(保留你的 `config.json` 与 `data/`),重启即可:
|
到 [Releases](https://github.com/Hinln/ARTEX/releases) 下载新版本 zip,停掉旧进程后覆盖 `artex` 与 `skills/`(保留你的 `config.json` 与 `data/`),重启即可:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp -r <解压目录>/skills ./ && cp <解压目录>/artex ./
|
cp -r <解压目录>/skills ./ && cp <解压目录>/artex ./
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
environment:
|
environment:
|
||||||
ARTEX_PG_DSN: postgres://${POSTGRES_USER:-artex}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-artex}?sslmode=disable
|
ARTEX_PG_DSN: postgres://${POSTGRES_USER:-artex}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-artex}?sslmode=disable
|
||||||
|
ARTEX_UPDATE_GITHUB_TOKEN: ${ARTEX_UPDATE_GITHUB_TOKEN:-}
|
||||||
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
|
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
|
||||||
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
||||||
ARTEX_LLM_PROVIDER: ${ARTEX_LLM_PROVIDER:-}
|
ARTEX_LLM_PROVIDER: ${ARTEX_LLM_PROVIDER:-}
|
||||||
|
|||||||
+42
-6
@@ -6,13 +6,17 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Repo 是发布源。写死而不是做成配置项:更新源可配等于给任何能改配置的人一条
|
// Repo 是发布源。写死而不是做成配置项:更新源可配等于给任何能改配置的人一条
|
||||||
// 远程代码执行通道,对一个渗透测试平台来说这个口子开不得。
|
// 远程代码执行通道,对一个渗透测试平台来说这个口子开不得。
|
||||||
const Repo = "Autumn-27/artex"
|
const Repo = "Hinln/ARTEX"
|
||||||
|
|
||||||
|
// GitHubTokenEnv 只由后端进程读取,不通过前端或配置 API 传递令牌。
|
||||||
|
const GitHubTokenEnv = "ARTEX_UPDATE_GITHUB_TOKEN"
|
||||||
|
|
||||||
// latestURL 是 GitHub 的"最新正式版"接口。它会自动跳过 prerelease 和 draft。
|
// latestURL 是 GitHub 的"最新正式版"接口。它会自动跳过 prerelease 和 draft。
|
||||||
const latestURL = "https://api.github.com/repos/" + Repo + "/releases/latest"
|
const latestURL = "https://api.github.com/repos/" + Repo + "/releases/latest"
|
||||||
@@ -44,9 +48,33 @@ type Release struct {
|
|||||||
type Asset struct {
|
type Asset struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
URL string `json:"browser_download_url"`
|
URL string `json:"browser_download_url"`
|
||||||
|
APIURL string `json:"url"`
|
||||||
Size int64 `json:"size"`
|
Size int64 `json:"size"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 私有仓库的附件需通过 Release Asset API 携带认证下载。
|
||||||
|
func (a Asset) downloadURL() string {
|
||||||
|
if a.APIURL != "" {
|
||||||
|
return a.APIURL
|
||||||
|
}
|
||||||
|
return a.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
// setUpdateAuth 仅向固定发布仓库的 HTTPS API 发送令牌。附件重定向到
|
||||||
|
// GitHub 对象存储时使用签名 URL,不得把 Authorization 一并转发。
|
||||||
|
func setUpdateAuth(req *http.Request) {
|
||||||
|
req.Header.Del("Authorization")
|
||||||
|
u := req.URL
|
||||||
|
if u.Scheme != "https" || !strings.EqualFold(u.Hostname(), "api.github.com") ||
|
||||||
|
(u.Port() != "" && u.Port() != "443") || u.User != nil ||
|
||||||
|
!strings.HasPrefix(strings.ToLower(u.EscapedPath()), strings.ToLower("/repos/"+Repo+"/releases/")) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if token := strings.TrimSpace(os.Getenv(GitHubTokenEnv)); token != "" {
|
||||||
|
req.Header.Set("Authorization", "Bearer "+token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewClient 构造一个只认 GitHub 域名的 HTTP 客户端。proxy 为空则直连。
|
// NewClient 构造一个只认 GitHub 域名的 HTTP 客户端。proxy 为空则直连。
|
||||||
//
|
//
|
||||||
// 刻意不复用默认 Transport:升级链路必须强制走 TLS 且校验证书,不能被别处
|
// 刻意不复用默认 Transport:升级链路必须强制走 TLS 且校验证书,不能被别处
|
||||||
@@ -68,7 +96,11 @@ func NewClient(proxy string) *http.Client {
|
|||||||
if len(via) >= 10 {
|
if len(via) >= 10 {
|
||||||
return fmt.Errorf("重定向次数过多")
|
return fmt.Errorf("重定向次数过多")
|
||||||
}
|
}
|
||||||
return checkURL(req.URL)
|
if err := checkURL(req.URL); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
setUpdateAuth(req)
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,6 +127,7 @@ func FetchLatest(ctx context.Context, c *http.Client) (*Release, error) {
|
|||||||
}
|
}
|
||||||
req.Header.Set("Accept", "application/vnd.github+json")
|
req.Header.Set("Accept", "application/vnd.github+json")
|
||||||
req.Header.Set("User-Agent", "artex-selfupdate")
|
req.Header.Set("User-Agent", "artex-selfupdate")
|
||||||
|
setUpdateAuth(req)
|
||||||
|
|
||||||
resp, err := c.Do(req)
|
resp, err := c.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -103,11 +136,14 @@ func FetchLatest(ctx context.Context, c *http.Client) (*Release, error) {
|
|||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case resp.StatusCode == http.StatusForbidden, resp.StatusCode == http.StatusTooManyRequests:
|
case resp.StatusCode == http.StatusUnauthorized:
|
||||||
// 未认证的 GitHub API 是每 IP 每小时 60 次,共用出口 IP 时很容易撞上。
|
return nil, fmt.Errorf("GitHub 认证失败,请检查后端环境变量 %s", GitHubTokenEnv)
|
||||||
return nil, fmt.Errorf("GitHub 接口限流(每小时 60 次),请稍后再试")
|
case resp.StatusCode == http.StatusForbidden:
|
||||||
|
return nil, fmt.Errorf("GitHub 拒绝访问:请检查令牌的仓库 Contents 读取权限或 API 限额")
|
||||||
|
case resp.StatusCode == http.StatusTooManyRequests:
|
||||||
|
return nil, fmt.Errorf("GitHub 接口限流,请稍后再试")
|
||||||
case resp.StatusCode == http.StatusNotFound:
|
case resp.StatusCode == http.StatusNotFound:
|
||||||
return nil, fmt.Errorf("仓库 %s 尚未发布任何正式版本", Repo)
|
return nil, fmt.Errorf("仓库 %s 不可访问或尚未发布正式版本;私有仓库请在后端设置 %s(需 Contents 读取权限)", Repo, GitHubTokenEnv)
|
||||||
case resp.StatusCode != http.StatusOK:
|
case resp.StatusCode != http.StatusOK:
|
||||||
return nil, fmt.Errorf("GitHub 返回 %d", resp.StatusCode)
|
return nil, fmt.Errorf("GitHub 返回 %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
package selfupdate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type updateTransportFunc func(*http.Request) (*http.Response, error)
|
||||||
|
|
||||||
|
func (f updateTransportFunc) RoundTrip(r *http.Request) (*http.Response, error) { return f(r) }
|
||||||
|
|
||||||
|
func updateResponse(req *http.Request, status int, body string) *http.Response {
|
||||||
|
return &http.Response{StatusCode: status, Header: make(http.Header), Body: io.NopCloser(strings.NewReader(body)), Request: req}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrivateReleaseMetadataChecksumsAndDownload(t *testing.T) {
|
||||||
|
t.Setenv(GitHubTokenEnv, "test-private-release-token")
|
||||||
|
payload := "release archive bytes"
|
||||||
|
sum := sha256.Sum256([]byte(payload))
|
||||||
|
wantSum := hex.EncodeToString(sum[:])
|
||||||
|
client := NewClient("")
|
||||||
|
seen := map[string]bool{}
|
||||||
|
client.Transport = updateTransportFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
|
seen[req.URL.String()] = true
|
||||||
|
if req.URL.Host == "api.github.com" {
|
||||||
|
if req.Header.Get("Authorization") != "Bearer test-private-release-token" {
|
||||||
|
t.Fatal("private Release API request missing authentication")
|
||||||
|
}
|
||||||
|
} else if req.Header.Get("Authorization") != "" {
|
||||||
|
t.Fatal("token leaked to release object storage")
|
||||||
|
}
|
||||||
|
switch req.URL.String() {
|
||||||
|
case latestURL:
|
||||||
|
if req.Header.Get("Accept") != "application/vnd.github+json" {
|
||||||
|
t.Fatal("metadata request must ask for JSON")
|
||||||
|
}
|
||||||
|
return updateResponse(req, 200, fmt.Sprintf(`{"tag_name":"v0.3.16","assets":[
|
||||||
|
{"name":"SHA256SUMS","url":"https://api.github.com/repos/%s/releases/assets/1","browser_download_url":"https://github.com/unusable-sums"},
|
||||||
|
{"name":"artex-0.3.16-darwin-arm64.zip","url":"https://api.github.com/repos/%s/releases/assets/2","browser_download_url":"https://github.com/unusable-zip","size":%d}
|
||||||
|
]}`, Repo, Repo, len(payload))), nil
|
||||||
|
case "https://api.github.com/repos/" + Repo + "/releases/assets/1":
|
||||||
|
if req.Header.Get("Accept") != "application/octet-stream" {
|
||||||
|
t.Fatal("checksum request must ask for binary data")
|
||||||
|
}
|
||||||
|
return updateResponse(req, 200, wantSum+" artex-0.3.16-darwin-arm64.zip\n"), nil
|
||||||
|
case "https://api.github.com/repos/" + Repo + "/releases/assets/2":
|
||||||
|
if req.Header.Get("Accept") != "application/octet-stream" {
|
||||||
|
t.Fatal("archive request must ask for binary data")
|
||||||
|
}
|
||||||
|
resp := updateResponse(req, 302, "")
|
||||||
|
resp.Header.Set("Location", "https://release-assets.githubusercontent.com/signed-archive")
|
||||||
|
return resp, nil
|
||||||
|
case "https://release-assets.githubusercontent.com/signed-archive":
|
||||||
|
return updateResponse(req, 200, payload), nil
|
||||||
|
default:
|
||||||
|
t.Fatalf("unexpected update URL: %s", req.URL)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
rel, err := FetchLatest(t.Context(), client)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
sums, err := fetchSums(t.Context(), client, rel)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
asset, ok := rel.FindAsset("artex-0.3.16-darwin-arm64.zip")
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("archive missing")
|
||||||
|
}
|
||||||
|
dst := filepath.Join(t.TempDir(), "release.zip")
|
||||||
|
got, err := download(t.Context(), client, asset, dst, func(Phase, int, string) {})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got != sums[asset.Name] || got != wantSum || readAll(t, dst) != payload {
|
||||||
|
t.Fatal("downloaded archive/checksum mismatch")
|
||||||
|
}
|
||||||
|
if len(seen) != 4 {
|
||||||
|
t.Fatalf("expected metadata, sums, archive API and CDN requests, got %d", len(seen))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateTokenNeverLeavesReleaseAPI(t *testing.T) {
|
||||||
|
t.Setenv(GitHubTokenEnv, "test-token")
|
||||||
|
for _, raw := range []string{
|
||||||
|
"https://github.com/" + Repo + "/releases/download/v1/file.zip",
|
||||||
|
"https://api.github.com/repos/other/repo/releases/assets/1",
|
||||||
|
"https://api.github.com/repos/" + Repo + "-other/releases/assets/1",
|
||||||
|
"https://api.github.com:8443/repos/" + Repo + "/releases/assets/1",
|
||||||
|
"https://api.github.com/repos/" + Repo + "/releases%2Fassets/1",
|
||||||
|
"https://objects.githubusercontent.com/archive",
|
||||||
|
"http://api.github.com/repos/" + Repo + "/releases/assets/1",
|
||||||
|
} {
|
||||||
|
t.Run(raw, func(t *testing.T) {
|
||||||
|
req, err := http.NewRequest(http.MethodGet, raw, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
req.Header.Set("Authorization", "Bearer inherited-token")
|
||||||
|
setUpdateAuth(req)
|
||||||
|
if req.Header.Get("Authorization") != "" {
|
||||||
|
t.Fatal("unsafe destination retained token")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// A same-host redirect must not inherit authorization for a different repo.
|
||||||
|
req, _ := http.NewRequest(http.MethodGet, "https://api.github.com/repos/other/repo/releases/assets/1", nil)
|
||||||
|
req.Header.Set("Authorization", "Bearer test-token")
|
||||||
|
if err := NewClient("").CheckRedirect(req, nil); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if req.Header.Get("Authorization") != "" {
|
||||||
|
t.Fatal("same-host redirect leaked token to another repo")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublicReleaseDownloadWithoutToken(t *testing.T) {
|
||||||
|
t.Setenv(GitHubTokenEnv, "")
|
||||||
|
asset := Asset{Name: "archive", URL: "https://github.com/" + Repo + "/releases/download/v1/archive.zip"}
|
||||||
|
client := NewClient("")
|
||||||
|
client.Transport = updateTransportFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
|
if req.URL.String() != asset.URL || req.Header.Get("Authorization") != "" {
|
||||||
|
t.Fatal("invalid public asset request")
|
||||||
|
}
|
||||||
|
return updateResponse(req, 200, "archive"), nil
|
||||||
|
})
|
||||||
|
if _, err := download(t.Context(), client, asset, filepath.Join(t.TempDir(), "archive"), func(Phase, int, string) {}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReleaseAuthenticationErrors(t *testing.T) {
|
||||||
|
t.Setenv(GitHubTokenEnv, "")
|
||||||
|
for status, message := range map[int]string{401: GitHubTokenEnv, 403: "Contents", 404: Repo, 429: "限流"} {
|
||||||
|
t.Run(fmt.Sprint(status), func(t *testing.T) {
|
||||||
|
client := &http.Client{Transport: updateTransportFunc(func(req *http.Request) (*http.Response, error) { return updateResponse(req, status, "{}"), nil })}
|
||||||
|
_, err := FetchLatest(t.Context(), client)
|
||||||
|
if err == nil || !strings.Contains(err.Error(), message) {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-2
@@ -133,7 +133,7 @@ func fetchSums(ctx context.Context, c *http.Client, rel *Release) (map[string]st
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("该 Release 没有 %s,无法校验完整性,拒绝升级", sumsAsset)
|
return nil, fmt.Errorf("该 Release 没有 %s,无法校验完整性,拒绝升级", sumsAsset)
|
||||||
}
|
}
|
||||||
body, err := get(ctx, c, asset.URL)
|
body, err := get(ctx, c, asset.downloadURL())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("下载 %s: %w", sumsAsset, err)
|
return nil, fmt.Errorf("下载 %s: %w", sumsAsset, err)
|
||||||
}
|
}
|
||||||
@@ -189,7 +189,7 @@ func isHexSHA256(s string) bool {
|
|||||||
|
|
||||||
// download 把资产写入 dst,同时计算 SHA256 并按 Content-Length 汇报进度。
|
// download 把资产写入 dst,同时计算 SHA256 并按 Content-Length 汇报进度。
|
||||||
func download(ctx context.Context, c *http.Client, a Asset, dst string, prog Progress) (string, error) {
|
func download(ctx context.Context, c *http.Client, a Asset, dst string, prog Progress) (string, error) {
|
||||||
body, err := get(ctx, c, a.URL)
|
body, err := get(ctx, c, a.downloadURL())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("下载 %s: %w", a.Name, err)
|
return "", fmt.Errorf("下载 %s: %w", a.Name, err)
|
||||||
}
|
}
|
||||||
@@ -225,6 +225,8 @@ func get(ctx context.Context, c *http.Client, rawURL string) (io.ReadCloser, err
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
req.Header.Set("User-Agent", "artex-selfupdate")
|
req.Header.Set("User-Agent", "artex-selfupdate")
|
||||||
|
req.Header.Set("Accept", "application/octet-stream")
|
||||||
|
setUpdateAuth(req)
|
||||||
resp, err := c.Do(req)
|
resp, err := c.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
Reference in new issue
Block a user