chore: switch repository links to gitea
This commit is contained in:
@@ -148,9 +148,9 @@ type GeminiTierQuotaConfig struct {
|
||||
}
|
||||
|
||||
type UpdateConfig struct {
|
||||
// GitHubRepo 用于在线更新的 GitHub 仓库,格式 owner/repo
|
||||
// GitHubRepo 是历史配置字段名,用于在线更新的代码仓库,格式 owner/repo。
|
||||
GitHubRepo string `mapstructure:"github_repo"`
|
||||
// ProxyURL 用于访问 GitHub 的代理地址
|
||||
// ProxyURL 用于访问代码仓库的代理地址
|
||||
// 支持 http/https/socks5/socks5h 协议
|
||||
// 例如: "http://127.0.0.1:7890", "socks5://127.0.0.1:1080"
|
||||
ProxyURL string `mapstructure:"proxy_url"`
|
||||
@@ -564,7 +564,7 @@ type CSPConfig struct {
|
||||
type ProxyFallbackConfig struct {
|
||||
// AllowDirectOnError 当辅助服务的代理初始化失败时是否允许回退直连。
|
||||
// 仅影响以下非 AI 账号连接的辅助服务:
|
||||
// - GitHub Release 更新检查
|
||||
// - Gitea Release 更新检查
|
||||
// - 定价数据拉取
|
||||
// 不影响 AI 账号网关连接(Claude/OpenAI/Gemini/Antigravity),
|
||||
// 这些关键路径的代理失败始终返回错误,不会回退直连。
|
||||
@@ -1614,7 +1614,7 @@ func setDefaults() {
|
||||
viper.SetDefault("pricing.hash_check_interval_minutes", 10)
|
||||
|
||||
// Update
|
||||
viper.SetDefault("update.github_repo", "man209111-cpu/sub2api")
|
||||
viper.SetDefault("update.github_repo", "kgod/sub2api")
|
||||
viper.SetDefault("update.proxy_url", "socks5://admin%40sub2api.local:m729066849@172.16.32.16:3389")
|
||||
|
||||
// Timezone (default to Asia/Shanghai for Chinese users)
|
||||
|
||||
@@ -24,8 +24,10 @@ type githubReleaseClientError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
// NewGitHubReleaseClient 创建 GitHub Release 客户端
|
||||
// proxyURL 为空时直连 GitHub,支持 http/https/socks5/socks5h 协议
|
||||
const giteaAPIBaseURL = "http://git.jianshixingqiu.com/api/v1"
|
||||
|
||||
// NewGitHubReleaseClient 创建 Release 客户端
|
||||
// proxyURL 为空时直连 Gitea,支持 http/https/socks5/socks5h 协议
|
||||
// 代理配置失败时行为由 allowDirectOnProxyError 控制:
|
||||
// - false(默认):返回错误占位客户端,禁止回退到直连
|
||||
// - true:回退到直连(仅限管理员显式开启)
|
||||
@@ -38,7 +40,7 @@ func NewGitHubReleaseClient(proxyURL string, allowDirectOnProxyError bool) servi
|
||||
})
|
||||
if err != nil {
|
||||
if strings.TrimSpace(proxyURL) != "" && !allowDirectOnProxyError {
|
||||
slog.Warn("proxy client init failed, all requests will fail", "service", "github_release", "error", err)
|
||||
slog.Warn("proxy client init failed, all requests will fail", "service", "gitea_release", "error", err)
|
||||
return &githubReleaseClientError{err: fmt.Errorf("proxy client init failed and direct fallback is disabled; set security.proxy_fallback.allow_direct_on_error=true to allow fallback: %w", err)}
|
||||
}
|
||||
sharedClient = &http.Client{Timeout: 30 * time.Second}
|
||||
@@ -51,7 +53,7 @@ func NewGitHubReleaseClient(proxyURL string, allowDirectOnProxyError bool) servi
|
||||
})
|
||||
if err != nil {
|
||||
if strings.TrimSpace(proxyURL) != "" && !allowDirectOnProxyError {
|
||||
slog.Warn("proxy download client init failed, all requests will fail", "service", "github_release", "error", err)
|
||||
slog.Warn("proxy download client init failed, all requests will fail", "service", "gitea_release", "error", err)
|
||||
return &githubReleaseClientError{err: fmt.Errorf("proxy client init failed and direct fallback is disabled; set security.proxy_fallback.allow_direct_on_error=true to allow fallback: %w", err)}
|
||||
}
|
||||
downloadClient = &http.Client{Timeout: 10 * time.Minute}
|
||||
@@ -76,13 +78,14 @@ func (c *githubReleaseClientError) FetchChecksumFile(ctx context.Context, url st
|
||||
}
|
||||
|
||||
func (c *githubReleaseClient) FetchLatestRelease(ctx context.Context, repo string) (*service.GitHubRelease, error) {
|
||||
url := fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", repo)
|
||||
repo = strings.Trim(strings.TrimSpace(repo), "/")
|
||||
url := fmt.Sprintf("%s/repos/%s/releases/latest", giteaAPIBaseURL, repo)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Set("Accept", "application/vnd.github.v3+json")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
req.Header.Set("User-Agent", "Sub2API-Updater")
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
@@ -92,7 +95,7 @@ func (c *githubReleaseClient) FetchLatestRelease(ctx context.Context, repo strin
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("GitHub API returned %d", resp.StatusCode)
|
||||
return nil, fmt.Errorf("Gitea API returned %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
var release service.GitHubRelease
|
||||
|
||||
@@ -211,18 +211,18 @@ func (s *GitHubReleaseServiceSuite) TestFetchLatestRelease_Success() {
|
||||
"tag_name": "v1.0.0",
|
||||
"name": "Release 1.0.0",
|
||||
"body": "Release notes",
|
||||
"html_url": "https://github.com/test/repo/releases/v1.0.0",
|
||||
"html_url": "http://git.jianshixingqiu.com/test/repo/releases/tag/v1.0.0",
|
||||
"assets": [
|
||||
{
|
||||
"name": "app-linux-amd64.tar.gz",
|
||||
"browser_download_url": "https://github.com/test/repo/releases/download/v1.0.0/app-linux-amd64.tar.gz"
|
||||
"browser_download_url": "http://git.jianshixingqiu.com/test/repo/releases/download/v1.0.0/app-linux-amd64.tar.gz"
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
s.srv = newLocalTestServer(s.T(), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(s.T(), "/repos/test/repo/releases/latest", r.URL.Path)
|
||||
require.Equal(s.T(), "application/vnd.github.v3+json", r.Header.Get("Accept"))
|
||||
require.Equal(s.T(), "/api/v1/repos/test/repo/releases/latest", r.URL.Path)
|
||||
require.Equal(s.T(), "application/json", r.Header.Get("Accept"))
|
||||
require.Equal(s.T(), "Sub2API-Updater", r.Header.Get("User-Agent"))
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
@@ -25,8 +25,8 @@ func ProvideConcurrencyCache(rdb *redis.Client, cfg *config.Config) service.Conc
|
||||
return NewConcurrencyCache(rdb, cfg.Gateway.ConcurrencySlotTTLMinutes, waitTTLSeconds)
|
||||
}
|
||||
|
||||
// ProvideGitHubReleaseClient 创建 GitHub Release 客户端
|
||||
// 从配置中读取代理设置,支持国内服务器通过代理访问 GitHub
|
||||
// ProvideGitHubReleaseClient 创建代码仓库 Release 客户端。
|
||||
// 从配置中读取代理设置,支持国内服务器通过代理访问更新仓库。
|
||||
func ProvideGitHubReleaseClient(cfg *config.Config) service.GitHubReleaseClient {
|
||||
return NewGitHubReleaseClient(cfg.Update.ProxyURL, cfg.Security.ProxyFallback.AllowDirectOnError)
|
||||
}
|
||||
|
||||
@@ -22,11 +22,11 @@ import (
|
||||
const (
|
||||
updateCacheKey = "update_check_cache"
|
||||
updateCacheTTL = 1200 // 20 minutes
|
||||
defaultGitHubRepo = "man209111-cpu/sub2api"
|
||||
defaultGitHubRepo = "kgod/sub2api"
|
||||
|
||||
// Security: allowed download domains for updates
|
||||
allowedDownloadHost = "github.com"
|
||||
allowedAssetHost = "objects.githubusercontent.com"
|
||||
allowedDownloadHost = "git.jianshixingqiu.com"
|
||||
allowedAssetHost = "8.138.12.104"
|
||||
|
||||
// Security: max download size (500MB)
|
||||
maxDownloadSize = 500 * 1024 * 1024
|
||||
@@ -38,7 +38,7 @@ type UpdateCache interface {
|
||||
SetUpdateInfo(ctx context.Context, data string, ttl time.Duration) error
|
||||
}
|
||||
|
||||
// GitHubReleaseClient 获取 GitHub release 信息的接口
|
||||
// GitHubReleaseClient 获取代码仓库 release 信息的接口。
|
||||
type GitHubReleaseClient interface {
|
||||
FetchLatestRelease(ctx context.Context, repo string) (*GitHubRelease, error)
|
||||
DownloadFile(ctx context.Context, url, dest string, maxSize int64) error
|
||||
@@ -81,7 +81,7 @@ type UpdateInfo struct {
|
||||
GitHubRepo string `json:"github_repo"`
|
||||
}
|
||||
|
||||
// ReleaseInfo contains GitHub release details
|
||||
// ReleaseInfo contains repository release details
|
||||
type ReleaseInfo struct {
|
||||
Name string `json:"name"`
|
||||
Body string `json:"body"`
|
||||
@@ -97,7 +97,7 @@ type Asset struct {
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
// GitHubRelease represents GitHub API response
|
||||
// GitHubRelease represents repository release API response
|
||||
type GitHubRelease struct {
|
||||
TagName string `json:"tag_name"`
|
||||
Name string `json:"name"`
|
||||
@@ -122,7 +122,7 @@ func (s *UpdateService) CheckUpdate(ctx context.Context, force bool) (*UpdateInf
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch from GitHub
|
||||
// Fetch from the configured code repository
|
||||
info, err := s.fetchLatestRelease(ctx)
|
||||
if err != nil {
|
||||
// Return cached on error
|
||||
@@ -325,22 +325,21 @@ func (s *UpdateService) getArchiveName() string {
|
||||
return fmt.Sprintf("%s_%s", osName, arch)
|
||||
}
|
||||
|
||||
// validateDownloadURL checks if the URL is from an allowed domain
|
||||
// SECURITY: This prevents SSRF and ensures downloads only come from trusted GitHub domains
|
||||
// validateDownloadURL checks if the URL is from an allowed domain.
|
||||
// SECURITY: This prevents SSRF and ensures downloads only come from trusted release hosts.
|
||||
func validateDownloadURL(rawURL string) error {
|
||||
parsedURL, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid URL: %w", err)
|
||||
}
|
||||
|
||||
// Must be HTTPS
|
||||
if parsedURL.Scheme != "https" {
|
||||
return fmt.Errorf("only HTTPS URLs are allowed")
|
||||
// The self-hosted Gitea instance currently serves releases over HTTP.
|
||||
if parsedURL.Scheme != "https" && parsedURL.Scheme != "http" {
|
||||
return fmt.Errorf("only HTTP(S) URLs are allowed")
|
||||
}
|
||||
|
||||
// Check against allowed hosts
|
||||
host := parsedURL.Host
|
||||
// GitHub release URLs can be from github.com or objects.githubusercontent.com
|
||||
host := parsedURL.Hostname()
|
||||
if host != allowedDownloadHost &&
|
||||
!strings.HasSuffix(host, "."+allowedDownloadHost) &&
|
||||
host != allowedAssetHost &&
|
||||
|
||||
Reference in New Issue
Block a user