4 Commits

Author SHA1 Message Date
kone 381ad28fe0 chore: prepare v0.1.147 release
Release Image / image (push) Failing after 2m32s
2026-06-09 01:14:02 +08:00
kone d702f74582 fix: add binary release assets to CI and update download allowlist
- Build linux_amd64 binary in CI and upload to Gitea release assets
- Add checksums.txt for integrity verification
- Update allowed download hosts to Gitea domain/IP
2026-06-09 01:13:48 +08:00
kone 0984773711 fix(gemini): skip token cache when expires_at is within refresh window
When a Gemini OAuth account receives a 401, ratelimit_service sets
expires_at=now() to force a refresh. Previously GetAccessToken would
return the stale cached token before checking expires_at, causing
repeated 401s until the cache TTL expired.

Fix: check needsRefresh before attempting cache lookup.
2026-06-09 01:00:11 +08:00
kone 4eb9877082 fix: use Gitea API for version check instead of GitHub 2026-06-06 04:29:48 +08:00
5 changed files with 42 additions and 13 deletions
+30 -2
View File
@@ -81,6 +81,18 @@ jobs:
docker push "$IMAGE_NAME:$VERSION" docker push "$IMAGE_NAME:$VERSION"
docker push "$IMAGE_NAME:latest" docker push "$IMAGE_NAME:latest"
- name: Build binary
run: |
set -eu
cd backend
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-ldflags "-s -w -X main.Version=${VERSION} -X main.Commit=${COMMIT} -X main.BuildDate=${BUILD_DATE}" \
-o /tmp/sub2api \
./cmd/server
cd /tmp
tar -czf "sub2api_linux_amd64.tar.gz" sub2api
sha256sum "sub2api_linux_amd64.tar.gz" > checksums.txt
- name: Create Gitea release - name: Create Gitea release
env: env:
RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }} RELEASE_TOKEN: ${{ secrets.RELEASE_TOKEN }}
@@ -88,9 +100,25 @@ jobs:
set -eu set -eu
BODY="Docker image: ${IMAGE_NAME}:${VERSION}" BODY="Docker image: ${IMAGE_NAME}:${VERSION}"
PAYLOAD=$(printf '{"tag_name":"%s","target_commitish":"%s","name":"Sub2API %s","body":"%s","draft":false,"prerelease":false}' "$TAG" "$(git rev-parse HEAD)" "$VERSION" "$BODY") PAYLOAD=$(printf '{"tag_name":"%s","target_commitish":"%s","name":"Sub2API %s","body":"%s","draft":false,"prerelease":false}' "$TAG" "$(git rev-parse HEAD)" "$VERSION" "$BODY")
curl -fsS \ RELEASE_ID=$(curl -fsS \
-X POST \ -X POST \
-H "Authorization: token ${RELEASE_TOKEN}" \ -H "Authorization: token ${RELEASE_TOKEN}" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "$PAYLOAD" \ -d "$PAYLOAD" \
"$GITEA_API_URL/repos/$GITEA_OWNER/$GITEA_REPO/releases" || true "$GITEA_API_URL/repos/$GITEA_OWNER/$GITEA_REPO/releases" | grep -o '"id":[0-9]*' | head -1 | grep -o '[0-9]*')
# Upload binary archive
curl -fsS \
-X POST \
-H "Authorization: token ${RELEASE_TOKEN}" \
-H "Content-Type: application/octet-stream" \
--data-binary @/tmp/sub2api_linux_amd64.tar.gz \
"$GITEA_API_URL/repos/$GITEA_OWNER/$GITEA_REPO/releases/${RELEASE_ID}/assets?name=sub2api_linux_amd64.tar.gz"
# Upload checksums
curl -fsS \
-X POST \
-H "Authorization: token ${RELEASE_TOKEN}" \
-H "Content-Type: text/plain" \
--data-binary @/tmp/checksums.txt \
"$GITEA_API_URL/repos/$GITEA_OWNER/$GITEA_REPO/releases/${RELEASE_ID}/assets?name=checksums.txt"
+1 -1
View File
@@ -1 +1 @@
0.1.144 0.1.147
@@ -76,13 +76,14 @@ func (c *githubReleaseClientError) FetchChecksumFile(ctx context.Context, url st
} }
func (c *githubReleaseClient) FetchLatestRelease(ctx context.Context, repo string) (*service.GitHubRelease, error) { func (c *githubReleaseClient) FetchLatestRelease(ctx context.Context, repo string) (*service.GitHubRelease, error) {
url := fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", repo) // 使用 Gitea API(兼容 GitHub Release API 格式)
url := fmt.Sprintf("http://git.jianshixingqiu.com/api/v1/repos/%s/releases/latest", repo)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil { if err != nil {
return nil, err 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") req.Header.Set("User-Agent", "Sub2API-Updater")
resp, err := c.httpClient.Do(req) resp, err := c.httpClient.Do(req)
@@ -92,7 +93,7 @@ func (c *githubReleaseClient) FetchLatestRelease(ctx context.Context, repo strin
defer func() { _ = resp.Body.Close() }() defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK { 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 var release service.GitHubRelease
@@ -62,16 +62,16 @@ func (p *GeminiTokenProvider) GetAccessToken(ctx context.Context, account *Accou
cacheKey := GeminiTokenCacheKey(account) cacheKey := GeminiTokenCacheKey(account)
// 1) Try cache first. // 1) Try cache first — skip if token is already expired or within refresh skew.
if p.tokenCache != nil { expiresAt := account.GetCredentialAsTime("expires_at")
needsRefresh := expiresAt == nil || time.Until(*expiresAt) <= geminiTokenRefreshSkew
if !needsRefresh && p.tokenCache != nil {
if token, err := p.tokenCache.GetAccessToken(ctx, cacheKey); err == nil && strings.TrimSpace(token) != "" { if token, err := p.tokenCache.GetAccessToken(ctx, cacheKey); err == nil && strings.TrimSpace(token) != "" {
return token, nil return token, nil
} }
} }
// 2) Refresh if needed (pre-expiry skew). // 2) Refresh if needed (pre-expiry skew).
expiresAt := account.GetCredentialAsTime("expires_at")
needsRefresh := expiresAt == nil || time.Until(*expiresAt) <= geminiTokenRefreshSkew
if needsRefresh && p.refreshAPI != nil && p.executor != nil { if needsRefresh && p.refreshAPI != nil && p.executor != nil {
result, err := p.refreshAPI.RefreshIfNeeded(ctx, account, p.executor, geminiTokenRefreshSkew) result, err := p.refreshAPI.RefreshIfNeeded(ctx, account, p.executor, geminiTokenRefreshSkew)
+3 -3
View File
@@ -22,11 +22,11 @@ import (
const ( const (
updateCacheKey = "update_check_cache" updateCacheKey = "update_check_cache"
updateCacheTTL = 1200 // 20 minutes updateCacheTTL = 1200 // 20 minutes
defaultGitHubRepo = "man209111-cpu/sub2api" defaultGitHubRepo = "kgod/sub2api"
// Security: allowed download domains for updates // Security: allowed download domains for updates
allowedDownloadHost = "github.com" allowedDownloadHost = "git.jianshixingqiu.com"
allowedAssetHost = "objects.githubusercontent.com" allowedAssetHost = "8.138.12.104"
// Security: max download size (500MB) // Security: max download size (500MB)
maxDownloadSize = 500 * 1024 * 1024 maxDownloadSize = 500 * 1024 * 1024