5 Commits

Author SHA1 Message Date
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
kone 88ccd0ecbb feat: add registration abuse prevention
- Silently block verification code for IPs with 2+ registered accounts
- Silently block Gmail alias emails (containing + or . in local part)
- Add CountByRegistrationIP to UserRepository interface
- Pass client IP to SendVerifyCodeAsync for abuse detection

Both checks return fake success to prevent enumeration attacks.
2026-06-06 04:07:07 +08:00
kone ba5a09862f fix: remove hardcoded default update proxy URL
The default socks5 proxy (172.16.32.16:3389) was unreachable for most
deployments, causing version check to timeout after 30 seconds.

Setting the default to empty string allows direct connection to the
Gitea API, which is the expected behavior for most users.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
2026-06-06 03:43:13 +08:00
41 changed files with 283 additions and 198 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"
+3 -3
View File
@@ -29,12 +29,12 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
path-to-signatures: "cla.json" path-to-signatures: "cla.json"
path-to-document: "http://git.jianshixingqiu.com/kgod/sub2api/src/branch/main/CLA.md" path-to-document: "https://github.com/Wei-Shaw/sub2api/blob/main/CLA.md"
branch: "cla-signatures" branch: "cla-signatures"
allowlist: "dependabot[bot],renovate[bot],bot*" allowlist: "dependabot[bot],renovate[bot],bot*"
lock-pullrequest-aftermerge: false lock-pullrequest-aftermerge: false
custom-notsigned-prcomment: | custom-notsigned-prcomment: |
Thank you for your contribution! Before we can merge this PR, we need $you to sign our [Contributor License Agreement (CLA)](http://git.jianshixingqiu.com/kgod/sub2api/src/branch/main/CLA.md). Thank you for your contribution! Before we can merge this PR, we need $you to sign our [Contributor License Agreement (CLA)](https://github.com/Wei-Shaw/sub2api/blob/main/CLA.md).
**To sign**, please reply with the following comment: **To sign**, please reply with the following comment:
@@ -54,6 +54,6 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
path-to-signatures: "cla.json" path-to-signatures: "cla.json"
path-to-document: "http://git.jianshixingqiu.com/kgod/sub2api/src/branch/main/CLA.md" path-to-document: "https://github.com/Wei-Shaw/sub2api/blob/main/CLA.md"
branch: "cla-signatures" branch: "cla-signatures"
lock-pullrequest-aftermerge: true lock-pullrequest-aftermerge: true
+11 -11
View File
@@ -11,7 +11,7 @@ on:
required: true required: true
type: string type: string
simple_release: simple_release:
description: 'Simple release: only x86_64 Gitea registry image, skip other artifacts' description: 'Simple release: only x86_64 GHCR image, skip other artifacts'
required: false required: false
type: boolean type: boolean
default: false default: false
@@ -133,12 +133,12 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to Gitea Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
registry: git.jianshixingqiu.com registry: ghcr.io
username: kgod username: ${{ github.repository_owner }}
password: ${{ secrets.RELEASE_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Fetch tags with annotations - name: Fetch tags with annotations
run: | run: |
@@ -168,7 +168,7 @@ jobs:
echo "$TAG_MESSAGE" >> $GITHUB_OUTPUT echo "$TAG_MESSAGE" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT
- name: Set lowercase owner for registry - name: Set lowercase owner for GHCR
id: lowercase id: lowercase
run: echo "owner=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT run: echo "owner=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT
@@ -220,7 +220,7 @@ jobs:
fi fi
VERSION=${TAG_NAME#v} VERSION=${TAG_NAME#v}
REPO="${{ github.repository }}" REPO="${{ github.repository }}"
REGISTRY_IMAGE="git.jianshixingqiu.com/kgod/sub2api" GHCR_IMAGE="ghcr.io/${REPO,,}" # ${,,} converts to lowercase
# 获取 tag message 内容并转义 Markdown 特殊字符 # 获取 tag message 内容并转义 Markdown 特殊字符
TAG_MESSAGE='${{ steps.tag_message.outputs.message }}' TAG_MESSAGE='${{ steps.tag_message.outputs.message }}'
@@ -247,16 +247,16 @@ jobs:
DOCKER_IMAGE="${DOCKERHUB_USERNAME}/sub2api" DOCKER_IMAGE="${DOCKERHUB_USERNAME}/sub2api"
MESSAGE+="# Docker Hub"$'\n' MESSAGE+="# Docker Hub"$'\n'
MESSAGE+="docker pull ${DOCKER_IMAGE}:${VERSION}"$'\n' MESSAGE+="docker pull ${DOCKER_IMAGE}:${VERSION}"$'\n'
MESSAGE+="# Gitea Container Registry"$'\n' MESSAGE+="# GitHub Container Registry"$'\n'
fi fi
MESSAGE+="docker pull ${REGISTRY_IMAGE}:${VERSION}"$'\n' MESSAGE+="docker pull ${GHCR_IMAGE}:${VERSION}"$'\n'
MESSAGE+="\`\`\`"$'\n'$'\n' MESSAGE+="\`\`\`"$'\n'$'\n'
MESSAGE+="🔗 *相关链接:*"$'\n' MESSAGE+="🔗 *相关链接:*"$'\n'
MESSAGE+="• [Gitea Release](http://git.jianshixingqiu.com/kgod/sub2api/releases/tag/${TAG_NAME})"$'\n' MESSAGE+="• [GitHub Release](https://github.com/${REPO}/releases/tag/${TAG_NAME})"$'\n'
if [ -n "$DOCKERHUB_USERNAME" ]; then if [ -n "$DOCKERHUB_USERNAME" ]; then
MESSAGE+="• [Docker Hub](https://hub.docker.com/r/${DOCKER_IMAGE})"$'\n' MESSAGE+="• [Docker Hub](https://hub.docker.com/r/${DOCKER_IMAGE})"$'\n'
fi fi
MESSAGE+="• [Gitea Container Registry](http://git.jianshixingqiu.com/kgod/-/packages/container/sub2api/latest)"$'\n'$'\n' MESSAGE+="• [GitHub Packages](https://github.com/${REPO}/pkgs/container/sub2api)"$'\n'$'\n'
MESSAGE+="#Sub2API #Release #${TAG_NAME//./_}" MESSAGE+="#Sub2API #Release #${TAG_NAME//./_}"
# 发送消息 # 发送消息
+10 -10
View File
@@ -1,4 +1,4 @@
# 简化版 GoReleaser 配置 - 仅发布 x86_64 Gitea registry 镜像 # 简化版 GoReleaser 配置 - 仅发布 x86_64 GHCR 镜像
version: 2 version: 2
project_name: sub2api project_name: sub2api
@@ -36,15 +36,15 @@ checksum:
changelog: changelog:
disable: true disable: true
# 仅 Gitea registry x86_64 镜像 # 仅 GHCR x86_64 镜像
dockers: dockers:
- id: gitea-amd64 - id: ghcr-amd64
goos: linux goos: linux
goarch: amd64 goarch: amd64
image_templates: image_templates:
- "git.jianshixingqiu.com/kgod/sub2api:{{ .Version }}-amd64" - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-amd64"
- "git.jianshixingqiu.com/kgod/sub2api:{{ .Version }}" - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}"
- "git.jianshixingqiu.com/kgod/sub2api:latest" - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:latest"
dockerfile: Dockerfile.goreleaser dockerfile: Dockerfile.goreleaser
use: buildx use: buildx
extra_files: extra_files:
@@ -53,7 +53,7 @@ dockers:
- "--platform=linux/amd64" - "--platform=linux/amd64"
- "--label=org.opencontainers.image.version={{ .Version }}" - "--label=org.opencontainers.image.version={{ .Version }}"
- "--label=org.opencontainers.image.revision={{ .Commit }}" - "--label=org.opencontainers.image.revision={{ .Commit }}"
- "--label=org.opencontainers.image.source=http://git.jianshixingqiu.com/kgod/sub2api" - "--label=org.opencontainers.image.source=https://github.com/{{ .Env.GITHUB_REPO_OWNER }}/{{ .Env.GITHUB_REPO_NAME }}"
# 跳过 manifests(单架构不需要) # 跳过 manifests(单架构不需要)
docker_manifests: [] docker_manifests: []
@@ -69,7 +69,7 @@ release:
skip_upload: true skip_upload: true
header: | header: |
> AI API Gateway Platform - 将 AI 订阅配额分发和管理 > AI API Gateway Platform - 将 AI 订阅配额分发和管理
> ⚡ Simple Release: 仅包含 x86_64 Gitea registry 镜像 > ⚡ Simple Release: 仅包含 x86_64 GHCR 镜像
{{ .Env.TAG_MESSAGE }} {{ .Env.TAG_MESSAGE }}
@@ -80,9 +80,9 @@ release:
**Docker (x86_64 only):** **Docker (x86_64 only):**
```bash ```bash
docker pull git.jianshixingqiu.com/kgod/sub2api:{{ .Version }} docker pull ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}
``` ```
## 📚 Documentation ## 📚 Documentation
- [Gitea Repository](http://git.jianshixingqiu.com/kgod/sub2api) - [GitHub Repository](https://github.com/{{ .Env.GITHUB_REPO_OWNER }}/{{ .Env.GITHUB_REPO_NAME }})
+25 -25
View File
@@ -85,12 +85,12 @@ dockers:
- "--label=org.opencontainers.image.version={{ .Version }}" - "--label=org.opencontainers.image.version={{ .Version }}"
- "--label=org.opencontainers.image.revision={{ .Commit }}" - "--label=org.opencontainers.image.revision={{ .Commit }}"
# Gitea registry images (owner must be lowercase) # GHCR images (owner must be lowercase)
- id: gitea-amd64 - id: ghcr-amd64
goos: linux goos: linux
goarch: amd64 goarch: amd64
image_templates: image_templates:
- "git.jianshixingqiu.com/kgod/sub2api:{{ .Version }}-amd64" - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-amd64"
dockerfile: Dockerfile.goreleaser dockerfile: Dockerfile.goreleaser
use: buildx use: buildx
extra_files: extra_files:
@@ -99,13 +99,13 @@ dockers:
- "--platform=linux/amd64" - "--platform=linux/amd64"
- "--label=org.opencontainers.image.version={{ .Version }}" - "--label=org.opencontainers.image.version={{ .Version }}"
- "--label=org.opencontainers.image.revision={{ .Commit }}" - "--label=org.opencontainers.image.revision={{ .Commit }}"
- "--label=org.opencontainers.image.source=http://git.jianshixingqiu.com/kgod/sub2api" - "--label=org.opencontainers.image.source=https://github.com/{{ .Env.GITHUB_REPO_OWNER }}/{{ .Env.GITHUB_REPO_NAME }}"
- id: gitea-arm64 - id: ghcr-arm64
goos: linux goos: linux
goarch: arm64 goarch: arm64
image_templates: image_templates:
- "git.jianshixingqiu.com/kgod/sub2api:{{ .Version }}-arm64" - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-arm64"
dockerfile: Dockerfile.goreleaser dockerfile: Dockerfile.goreleaser
use: buildx use: buildx
extra_files: extra_files:
@@ -114,7 +114,7 @@ dockers:
- "--platform=linux/arm64" - "--platform=linux/arm64"
- "--label=org.opencontainers.image.version={{ .Version }}" - "--label=org.opencontainers.image.version={{ .Version }}"
- "--label=org.opencontainers.image.revision={{ .Commit }}" - "--label=org.opencontainers.image.revision={{ .Commit }}"
- "--label=org.opencontainers.image.source=http://git.jianshixingqiu.com/kgod/sub2api" - "--label=org.opencontainers.image.source=https://github.com/{{ .Env.GITHUB_REPO_OWNER }}/{{ .Env.GITHUB_REPO_NAME }}"
# Docker manifests for multi-arch support # Docker manifests for multi-arch support
docker_manifests: docker_manifests:
@@ -143,26 +143,26 @@ docker_manifests:
- "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }}-amd64" - "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }}-amd64"
- "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }}-arm64" - "{{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }}-arm64"
# Gitea registry manifests (owner must be lowercase) # GHCR manifests (owner must be lowercase)
- name_template: "git.jianshixingqiu.com/kgod/sub2api:{{ .Version }}" - name_template: "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}"
image_templates: image_templates:
- "git.jianshixingqiu.com/kgod/sub2api:{{ .Version }}-amd64" - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-amd64"
- "git.jianshixingqiu.com/kgod/sub2api:{{ .Version }}-arm64" - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-arm64"
- name_template: "git.jianshixingqiu.com/kgod/sub2api:latest" - name_template: "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:latest"
image_templates: image_templates:
- "git.jianshixingqiu.com/kgod/sub2api:{{ .Version }}-amd64" - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-amd64"
- "git.jianshixingqiu.com/kgod/sub2api:{{ .Version }}-arm64" - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-arm64"
- name_template: "git.jianshixingqiu.com/kgod/sub2api:{{ .Major }}.{{ .Minor }}" - name_template: "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Major }}.{{ .Minor }}"
image_templates: image_templates:
- "git.jianshixingqiu.com/kgod/sub2api:{{ .Version }}-amd64" - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-amd64"
- "git.jianshixingqiu.com/kgod/sub2api:{{ .Version }}-arm64" - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-arm64"
- name_template: "git.jianshixingqiu.com/kgod/sub2api:{{ .Major }}" - name_template: "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Major }}"
image_templates: image_templates:
- "git.jianshixingqiu.com/kgod/sub2api:{{ .Version }}-amd64" - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-amd64"
- "git.jianshixingqiu.com/kgod/sub2api:{{ .Version }}-arm64" - "ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}-arm64"
release: release:
github: github:
@@ -190,13 +190,13 @@ release:
docker pull {{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }} docker pull {{ .Env.DOCKERHUB_USERNAME }}/sub2api:{{ .Version }}
{{ end -}} {{ end -}}
# Gitea Container Registry # GitHub Container Registry
docker pull git.jianshixingqiu.com/kgod/sub2api:{{ .Version }} docker pull ghcr.io/{{ .Env.GITHUB_REPO_OWNER_LOWER }}/sub2api:{{ .Version }}
``` ```
**One-line install (Linux):** **One-line install (Linux):**
```bash ```bash
curl -sSL http://git.jianshixingqiu.com/kgod/sub2api/raw/branch/main/deploy/install.sh | sudo bash curl -sSL https://raw.githubusercontent.com/{{ .Env.GITHUB_REPO_OWNER }}/{{ .Env.GITHUB_REPO_NAME }}/main/deploy/install.sh | sudo bash
``` ```
**Manual download:** **Manual download:**
@@ -204,5 +204,5 @@ release:
## 📚 Documentation ## 📚 Documentation
- [Gitea Repository](http://git.jianshixingqiu.com/kgod/sub2api) - [GitHub Repository](https://github.com/{{ .Env.GITHUB_REPO_OWNER }}/{{ .Env.GITHUB_REPO_NAME }})
- [Installation Guide](http://git.jianshixingqiu.com/kgod/sub2api/src/branch/main/deploy/README.md) - [Installation Guide](https://github.com/{{ .Env.GITHUB_REPO_OWNER }}/{{ .Env.GITHUB_REPO_NAME }}/blob/main/deploy/README.md)
+2 -2
View File
@@ -6,7 +6,7 @@
| 项目 | 说明 | | 项目 | 说明 |
|------|------| |------|------|
| **上游仓库** | kgod/sub2api | | **上游仓库** | Wei-Shaw/sub2api |
| **Fork 仓库** | bayma888/sub2api-bmai | | **Fork 仓库** | bayma888/sub2api-bmai |
| **技术栈** | Go 后端 (Ent ORM + Gin) + Vue3 前端 (pnpm) | | **技术栈** | Go 后端 (Ent ORM + Gin) + Vue3 前端 (pnpm) |
| **数据库** | PostgreSQL 16 + Redis | | **数据库** | PostgreSQL 16 + Redis |
@@ -340,7 +340,7 @@ sub2api-bmai/
## 七、参考资源 ## 七、参考资源
- [上游仓库](http://git.jianshixingqiu.com/kgod/sub2api) - [上游仓库](https://github.com/Wei-Shaw/sub2api)
- [Ent 文档](https://entgo.io/docs/getting-started) - [Ent 文档](https://entgo.io/docs/getting-started)
- [Vue3 文档](https://vuejs.org/) - [Vue3 文档](https://vuejs.org/)
- [pnpm 文档](https://pnpm.io/) - [pnpm 文档](https://pnpm.io/)
+2 -2
View File
@@ -84,9 +84,9 @@ FROM ${POSTGRES_IMAGE} AS pg-client
FROM ${ALPINE_IMAGE} FROM ${ALPINE_IMAGE}
# Labels # Labels
LABEL maintainer="kgod <http://git.jianshixingqiu.com/kgod/sub2api>" LABEL maintainer="Wei-Shaw <github.com/Wei-Shaw>"
LABEL description="Sub2API - AI API Gateway Platform" LABEL description="Sub2API - AI API Gateway Platform"
LABEL org.opencontainers.image.source="http://git.jianshixingqiu.com/kgod/sub2api" LABEL org.opencontainers.image.source="https://github.com/Wei-Shaw/sub2api"
# Install runtime dependencies # Install runtime dependencies
RUN apk add --no-cache \ RUN apk add --no-cache \
+1 -1
View File
@@ -46,7 +46,7 @@ FROM ${POSTGRES_IMAGE} AS pg-client
FROM ${ALPINE_IMAGE} FROM ${ALPINE_IMAGE}
LABEL maintainer="kgod <http://git.jianshixingqiu.com/kgod/sub2api>" LABEL maintainer="Wei-Shaw <github.com/Wei-Shaw>"
LABEL description="Sub2API - AI API Gateway Platform" LABEL description="Sub2API - AI API Gateway Platform"
LABEL org.opencontainers.image.source="http://git.jianshixingqiu.com/kgod/sub2api" LABEL org.opencontainers.image.source="http://git.jianshixingqiu.com/kgod/sub2api"
+2 -2
View File
@@ -12,9 +12,9 @@ FROM ${POSTGRES_IMAGE} AS pg-client
FROM ${ALPINE_IMAGE} FROM ${ALPINE_IMAGE}
LABEL maintainer="kgod <http://git.jianshixingqiu.com/kgod/sub2api>" LABEL maintainer="Wei-Shaw <github.com/Wei-Shaw>"
LABEL description="Sub2API - AI API Gateway Platform" LABEL description="Sub2API - AI API Gateway Platform"
LABEL org.opencontainers.image.source="http://git.jianshixingqiu.com/kgod/sub2api" LABEL org.opencontainers.image.source="https://github.com/Wei-Shaw/sub2api"
# Install runtime dependencies # Install runtime dependencies
RUN apk add --no-cache \ RUN apk add --no-cache \
+10 -10
View File
@@ -146,7 +146,7 @@ Nginx drops headers containing underscores by default (e.g. `session_id`), which
### Method 1: Script Installation (Recommended) ### Method 1: Script Installation (Recommended)
One-click installation script that downloads pre-built binaries from Gitea Releases. One-click installation script that downloads pre-built binaries from GitHub Releases.
#### Prerequisites #### Prerequisites
@@ -158,7 +158,7 @@ One-click installation script that downloads pre-built binaries from Gitea Relea
#### Installation Steps #### Installation Steps
```bash ```bash
curl -sSL http://git.jianshixingqiu.com/kgod/sub2api/raw/branch/main/deploy/install.sh | sudo bash curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | sudo bash
``` ```
The script will: The script will:
@@ -208,7 +208,7 @@ sudo journalctl -u sub2api -f
sudo systemctl restart sub2api sudo systemctl restart sub2api
# Uninstall # Uninstall
curl -sSL http://git.jianshixingqiu.com/kgod/sub2api/raw/branch/main/deploy/install.sh | sudo bash -s -- uninstall -y curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | sudo bash -s -- uninstall -y
``` ```
--- ---
@@ -231,7 +231,7 @@ Use the automated deployment script for easy setup:
mkdir -p sub2api-deploy && cd sub2api-deploy mkdir -p sub2api-deploy && cd sub2api-deploy
# Download and run deployment preparation script # Download and run deployment preparation script
curl -sSL http://git.jianshixingqiu.com/kgod/sub2api/raw/branch/main/deploy/docker-deploy.sh | bash curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash
# Start services # Start services
docker compose up -d docker compose up -d
@@ -253,7 +253,7 @@ If you prefer manual setup:
```bash ```bash
# 1. Clone the repository # 1. Clone the repository
git clone http://git.jianshixingqiu.com/kgod/sub2api.git git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api/deploy cd sub2api/deploy
# 2. Copy environment configuration # 2. Copy environment configuration
@@ -392,7 +392,7 @@ Build and run from source code for development or customization.
```bash ```bash
# 1. Clone the repository # 1. Clone the repository
git clone http://git.jianshixingqiu.com/kgod/sub2api.git git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api cd sub2api
# 2. Install pnpm (if not already installed) # 2. Install pnpm (if not already installed)
@@ -618,11 +618,11 @@ sub2api/
## Star History ## Star History
<a href="https://star-history.com/#kgod/sub2api&Date"> <a href="https://star-history.com/#Wei-Shaw/sub2api&Date">
<picture> <picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=kgod/sub2api&type=Date&theme=dark" /> <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=kgod/sub2api&type=Date" /> <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=kgod/sub2api&type=Date" /> <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
</picture> </picture>
</a> </a>
+10 -10
View File
@@ -145,7 +145,7 @@ Nginx 默认会丢弃名称中含下划线的请求头(如 `session_id`),
### 方式一:脚本安装(推荐) ### 方式一:脚本安装(推荐)
一键安装脚本,自动从 Gitea Releases 下载预编译的二进制文件。 一键安装脚本,自动从 GitHub Releases 下载预编译的二进制文件。
#### 前置条件 #### 前置条件
@@ -157,7 +157,7 @@ Nginx 默认会丢弃名称中含下划线的请求头(如 `session_id`),
#### 安装步骤 #### 安装步骤
```bash ```bash
curl -sSL http://git.jianshixingqiu.com/kgod/sub2api/raw/branch/main/deploy/install.sh | sudo bash curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | sudo bash
``` ```
脚本会自动: 脚本会自动:
@@ -207,7 +207,7 @@ sudo journalctl -u sub2api -f
sudo systemctl restart sub2api sudo systemctl restart sub2api
# 卸载 # 卸载
curl -sSL http://git.jianshixingqiu.com/kgod/sub2api/raw/branch/main/deploy/install.sh | sudo bash -s -- uninstall -y curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | sudo bash -s -- uninstall -y
``` ```
--- ---
@@ -230,7 +230,7 @@ curl -sSL http://git.jianshixingqiu.com/kgod/sub2api/raw/branch/main/deploy/inst
mkdir -p sub2api-deploy && cd sub2api-deploy mkdir -p sub2api-deploy && cd sub2api-deploy
# 下载并运行部署准备脚本 # 下载并运行部署准备脚本
curl -sSL http://git.jianshixingqiu.com/kgod/sub2api/raw/branch/main/deploy/docker-deploy.sh | bash curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash
# 启动服务 # 启动服务
docker compose up -d docker compose up -d
@@ -252,7 +252,7 @@ docker compose logs -f sub2api
```bash ```bash
# 1. 克隆仓库 # 1. 克隆仓库
git clone http://git.jianshixingqiu.com/kgod/sub2api.git git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api/deploy cd sub2api/deploy
# 2. 复制环境配置文件 # 2. 复制环境配置文件
@@ -403,7 +403,7 @@ rm -rf data/ postgres_data/ redis_data/
```bash ```bash
# 1. 克隆仓库 # 1. 克隆仓库
git clone http://git.jianshixingqiu.com/kgod/sub2api.git git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api cd sub2api
# 2. 安装 pnpm(如果还没有安装) # 2. 安装 pnpm(如果还没有安装)
@@ -679,11 +679,11 @@ sub2api/
## Star History ## Star History
<a href="https://star-history.com/#kgod/sub2api&Date"> <a href="https://star-history.com/#Wei-Shaw/sub2api&Date">
<picture> <picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=kgod/sub2api&type=Date&theme=dark" /> <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=kgod/sub2api&type=Date" /> <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=kgod/sub2api&type=Date" /> <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
</picture> </picture>
</a> </a>
+10 -10
View File
@@ -145,7 +145,7 @@ Nginx はデフォルトでアンダースコアを含むヘッダー(例: `se
### 方法1: スクリプトによるインストール(推奨) ### 方法1: スクリプトによるインストール(推奨)
Gitea Releases からビルド済みバイナリをダウンロードするワンクリックインストールスクリプトです。 GitHub Releases からビルド済みバイナリをダウンロードするワンクリックインストールスクリプトです。
#### 前提条件 #### 前提条件
@@ -157,7 +157,7 @@ Gitea Releases からビルド済みバイナリをダウンロードするワ
#### インストール手順 #### インストール手順
```bash ```bash
curl -sSL http://git.jianshixingqiu.com/kgod/sub2api/raw/branch/main/deploy/install.sh | sudo bash curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | sudo bash
``` ```
スクリプトは以下を実行します: スクリプトは以下を実行します:
@@ -207,7 +207,7 @@ sudo journalctl -u sub2api -f
sudo systemctl restart sub2api sudo systemctl restart sub2api
# アンインストール # アンインストール
curl -sSL http://git.jianshixingqiu.com/kgod/sub2api/raw/branch/main/deploy/install.sh | sudo bash -s -- uninstall -y curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | sudo bash -s -- uninstall -y
``` ```
--- ---
@@ -230,7 +230,7 @@ PostgreSQL と Redis のコンテナを含む Docker Compose でデプロイし
mkdir -p sub2api-deploy && cd sub2api-deploy mkdir -p sub2api-deploy && cd sub2api-deploy
# デプロイ準備スクリプトをダウンロードして実行 # デプロイ準備スクリプトをダウンロードして実行
curl -sSL http://git.jianshixingqiu.com/kgod/sub2api/raw/branch/main/deploy/docker-deploy.sh | bash curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash
# サービスを起動 # サービスを起動
docker compose up -d docker compose up -d
@@ -252,7 +252,7 @@ docker compose logs -f sub2api
```bash ```bash
# 1. リポジトリをクローン # 1. リポジトリをクローン
git clone http://git.jianshixingqiu.com/kgod/sub2api.git git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api/deploy cd sub2api/deploy
# 2. 環境設定ファイルをコピー # 2. 環境設定ファイルをコピー
@@ -391,7 +391,7 @@ rm -rf data/ postgres_data/ redis_data/
```bash ```bash
# 1. リポジトリをクローン # 1. リポジトリをクローン
git clone http://git.jianshixingqiu.com/kgod/sub2api.git git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api cd sub2api
# 2. pnpm をインストール(未インストールの場合) # 2. pnpm をインストール(未インストールの場合)
@@ -617,11 +617,11 @@ sub2api/
## スター履歴 ## スター履歴
<a href="https://star-history.com/#kgod/sub2api&Date"> <a href="https://star-history.com/#Wei-Shaw/sub2api&Date">
<picture> <picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=kgod/sub2api&type=Date&theme=dark" /> <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=kgod/sub2api&type=Date" /> <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=kgod/sub2api&type=Date" /> <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Wei-Shaw/sub2api&type=Date" />
</picture> </picture>
</a> </a>
+1 -1
View File
@@ -1 +1 @@
0.1.141 0.1.140
+1 -1
View File
@@ -1615,7 +1615,7 @@ func setDefaults() {
// Update // Update
viper.SetDefault("update.github_repo", "kgod/sub2api") viper.SetDefault("update.github_repo", "kgod/sub2api")
viper.SetDefault("update.proxy_url", "socks5://admin%40sub2api.local:m729066849@172.16.32.16:3389") viper.SetDefault("update.proxy_url", "")
// Timezone (default to Asia/Shanghai for Chinese users) // Timezone (default to Asia/Shanghai for Chinese users)
viper.SetDefault("timezone", "Asia/Shanghai") viper.SetDefault("timezone", "Asia/Shanghai")
+4 -2
View File
@@ -200,13 +200,15 @@ func (h *AuthHandler) SendVerifyCode(c *gin.Context) {
return return
} }
clientIP := ip.GetClientIP(c)
// Turnstile 验证 // Turnstile 验证
if err := h.authService.VerifyTurnstile(c.Request.Context(), req.TurnstileToken, ip.GetClientIP(c)); err != nil { if err := h.authService.VerifyTurnstile(c.Request.Context(), req.TurnstileToken, clientIP); err != nil {
response.ErrorFrom(c, err) response.ErrorFrom(c, err)
return return
} }
result, err := h.authService.SendVerifyCodeAsync(c.Request.Context(), req.Email) result, err := h.authService.SendVerifyCodeAsync(c.Request.Context(), req.Email, clientIP)
if err != nil { if err != nil {
response.ErrorFrom(c, err) response.ErrorFrom(c, err)
return return
@@ -24,10 +24,8 @@ type githubReleaseClientError struct {
err error err error
} }
const giteaAPIBaseURL = "http://git.jianshixingqiu.com/api/v1" // NewGitHubReleaseClient 创建 GitHub Release 客户端
// proxyURL 为空时直连 GitHub,支持 http/https/socks5/socks5h 协议
// NewGitHubReleaseClient 创建 Release 客户端
// proxyURL 为空时直连 Gitea,支持 http/https/socks5/socks5h 协议
// 代理配置失败时行为由 allowDirectOnProxyError 控制: // 代理配置失败时行为由 allowDirectOnProxyError 控制:
// - false(默认):返回错误占位客户端,禁止回退到直连 // - false(默认):返回错误占位客户端,禁止回退到直连
// - true:回退到直连(仅限管理员显式开启) // - true:回退到直连(仅限管理员显式开启)
@@ -40,7 +38,7 @@ func NewGitHubReleaseClient(proxyURL string, allowDirectOnProxyError bool) servi
}) })
if err != nil { if err != nil {
if strings.TrimSpace(proxyURL) != "" && !allowDirectOnProxyError { if strings.TrimSpace(proxyURL) != "" && !allowDirectOnProxyError {
slog.Warn("proxy client init failed, all requests will fail", "service", "gitea_release", "error", err) slog.Warn("proxy client init failed, all requests will fail", "service", "github_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)} 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} sharedClient = &http.Client{Timeout: 30 * time.Second}
@@ -53,7 +51,7 @@ func NewGitHubReleaseClient(proxyURL string, allowDirectOnProxyError bool) servi
}) })
if err != nil { if err != nil {
if strings.TrimSpace(proxyURL) != "" && !allowDirectOnProxyError { if strings.TrimSpace(proxyURL) != "" && !allowDirectOnProxyError {
slog.Warn("proxy download client init failed, all requests will fail", "service", "gitea_release", "error", err) slog.Warn("proxy download client init failed, all requests will fail", "service", "github_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)} 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} downloadClient = &http.Client{Timeout: 10 * time.Minute}
@@ -78,8 +76,8 @@ 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) {
repo = strings.Trim(strings.TrimSpace(repo), "/") // 使用 Gitea API(兼容 GitHub Release API 格式)
url := fmt.Sprintf("%s/repos/%s/releases/latest", giteaAPIBaseURL, repo) 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 {
@@ -211,18 +211,18 @@ func (s *GitHubReleaseServiceSuite) TestFetchLatestRelease_Success() {
"tag_name": "v1.0.0", "tag_name": "v1.0.0",
"name": "Release 1.0.0", "name": "Release 1.0.0",
"body": "Release notes", "body": "Release notes",
"html_url": "http://git.jianshixingqiu.com/test/repo/releases/tag/v1.0.0", "html_url": "https://github.com/test/repo/releases/v1.0.0",
"assets": [ "assets": [
{ {
"name": "app-linux-amd64.tar.gz", "name": "app-linux-amd64.tar.gz",
"browser_download_url": "http://git.jianshixingqiu.com/test/repo/releases/download/v1.0.0/app-linux-amd64.tar.gz" "browser_download_url": "https://github.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) { s.srv = newLocalTestServer(s.T(), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(s.T(), "/api/v1/repos/test/repo/releases/latest", r.URL.Path) require.Equal(s.T(), "/repos/test/repo/releases/latest", r.URL.Path)
require.Equal(s.T(), "application/json", r.Header.Get("Accept")) require.Equal(s.T(), "application/vnd.github.v3+json", r.Header.Get("Accept"))
require.Equal(s.T(), "Sub2API-Updater", r.Header.Get("User-Agent")) require.Equal(s.T(), "Sub2API-Updater", r.Header.Get("User-Agent"))
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
+23
View File
@@ -1113,3 +1113,26 @@ func (r *userRepository) DisableTotp(ctx context.Context, userID int64) error {
} }
return nil return nil
} }
// CountByRegistrationIP 统计指定 IP 注册的用户数量
func (r *userRepository) CountByRegistrationIP(ctx context.Context, ip string) (int, error) {
if strings.TrimSpace(ip) == "" {
return 0, nil
}
rows, err := r.sql.QueryContext(ctx,
`SELECT COUNT(*) FROM users WHERE register_ip_address = $1 AND deleted_at IS NULL`,
ip,
)
if err != nil {
return 0, err
}
defer rows.Close()
var count int
if rows.Next() {
if err := rows.Scan(&count); err != nil {
return 0, err
}
}
return count, nil
}
+2 -2
View File
@@ -25,8 +25,8 @@ func ProvideConcurrencyCache(rdb *redis.Client, cfg *config.Config) service.Conc
return NewConcurrencyCache(rdb, cfg.Gateway.ConcurrencySlotTTLMinutes, waitTTLSeconds) return NewConcurrencyCache(rdb, cfg.Gateway.ConcurrencySlotTTLMinutes, waitTTLSeconds)
} }
// ProvideGitHubReleaseClient 创建代码仓库 Release 客户端 // ProvideGitHubReleaseClient 创建 GitHub Release 客户端
// 从配置中读取代理设置,支持国内服务器通过代理访问更新仓库。 // 从配置中读取代理设置,支持国内服务器通过代理访问 GitHub
func ProvideGitHubReleaseClient(cfg *config.Config) service.GitHubReleaseClient { func ProvideGitHubReleaseClient(cfg *config.Config) service.GitHubReleaseClient {
return NewGitHubReleaseClient(cfg.Update.ProxyURL, cfg.Security.ProxyFallback.AllowDirectOnError) return NewGitHubReleaseClient(cfg.Update.ProxyURL, cfg.Security.ProxyFallback.AllowDirectOnError)
} }
+41 -2
View File
@@ -311,8 +311,9 @@ func (s *AuthService) SendVerifyCode(ctx context.Context, email string) error {
} }
// SendVerifyCodeAsync 异步发送邮箱验证码并返回倒计时 // SendVerifyCodeAsync 异步发送邮箱验证码并返回倒计时
func (s *AuthService) SendVerifyCodeAsync(ctx context.Context, email string) (*SendVerifyCodeResult, error) { // clientIP 用于检查同一 IP 注册账号数量限制
logger.LegacyPrintf("service.auth", "[Auth] SendVerifyCodeAsync called for email: %s", email) func (s *AuthService) SendVerifyCodeAsync(ctx context.Context, email string, clientIP string) (*SendVerifyCodeResult, error) {
logger.LegacyPrintf("service.auth", "[Auth] SendVerifyCodeAsync called for email: %s, ip: %s", email, clientIP)
// 检查是否开放注册(默认关闭) // 检查是否开放注册(默认关闭)
if s.settingService == nil || !s.settingService.IsRegistrationEnabled(ctx) { if s.settingService == nil || !s.settingService.IsRegistrationEnabled(ctx) {
@@ -338,6 +339,28 @@ func (s *AuthService) SendVerifyCodeAsync(ctx context.Context, email string) (*S
return nil, ErrEmailExists return nil, ErrEmailExists
} }
// 检查 Gmail 别名邮箱(含 + 或本地部分含 . 的),静默假装发送成功
if isGmailAliasEmail(email) {
logger.LegacyPrintf("service.auth", "[Auth] Gmail alias email detected: %s, returning fake success", email)
return &SendVerifyCodeResult{
Countdown: 60,
}, nil
}
// 检查同一 IP 注册账号数量(>=2 则静默假装发送成功,不实际发送)
if clientIP != "" {
ipRegCount, err := s.userRepo.CountByRegistrationIP(ctx, clientIP)
if err != nil {
logger.LegacyPrintf("service.auth", "[Auth] Failed to count users by registration IP: %v", err)
// 查询失败不阻塞,继续正常流程
} else if ipRegCount >= 2 {
logger.LegacyPrintf("service.auth", "[Auth] IP %s already registered %d accounts, returning fake success", clientIP, ipRegCount)
return &SendVerifyCodeResult{
Countdown: 60,
}, nil
}
}
// 检查邮件队列服务是否配置 // 检查邮件队列服务是否配置
if s.emailQueueService == nil { if s.emailQueueService == nil {
logger.LegacyPrintf("service.auth", "%s", "[Auth] Email queue service not configured") logger.LegacyPrintf("service.auth", "%s", "[Auth] Email queue service not configured")
@@ -1092,6 +1115,22 @@ func isReservedEmail(email string) bool {
strings.HasSuffix(normalized, WeChatConnectSyntheticEmailDomain) strings.HasSuffix(normalized, WeChatConnectSyntheticEmailDomain)
} }
// isGmailAliasEmail 检测 Gmail 别名邮箱
// Gmail 支持两种别名方式:
// 1. 加号别名:user+anything@gmail.com -> user@gmail.com
// 2. 点号忽略:u.s.e.r@gmail.com -> user@gmail.com
// 为防止滥用注册,检测到这类邮箱时返回 true
func isGmailAliasEmail(email string) bool {
normalized := strings.ToLower(strings.TrimSpace(email))
if !strings.HasSuffix(normalized, "@gmail.com") {
return false
}
// 提取本地部分(@前面的部分)
localPart := strings.TrimSuffix(normalized, "@gmail.com")
// 检查是否包含 + 或 .
return strings.Contains(localPart, "+") || strings.Contains(localPart, ".")
}
// GenerateToken 生成JWT access token // GenerateToken 生成JWT access token
// 使用新的access_token_expire_minutes配置项(如果配置了),否则回退到expire_hour // 使用新的access_token_expire_minutes配置项(如果配置了),否则回退到expire_hour
func (s *AuthService) GenerateToken(user *User) (string, error) { func (s *AuthService) GenerateToken(user *User) (string, error) {
@@ -231,6 +231,10 @@ func (r *contentModerationTestUserRepo) DisableTotp(ctx context.Context, userID
panic("unexpected DisableTotp call") panic("unexpected DisableTotp call")
} }
func (r *contentModerationTestUserRepo) CountByRegistrationIP(ctx context.Context, ip string) (int, error) {
return 0, nil
}
type contentModerationTestAuthCacheInvalidator struct { type contentModerationTestAuthCacheInvalidator struct {
userIDs []int64 userIDs []int64
} }
@@ -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)
+11 -10
View File
@@ -38,7 +38,7 @@ type UpdateCache interface {
SetUpdateInfo(ctx context.Context, data string, ttl time.Duration) error SetUpdateInfo(ctx context.Context, data string, ttl time.Duration) error
} }
// GitHubReleaseClient 获取代码仓库 release 信息的接口 // GitHubReleaseClient 获取 GitHub release 信息的接口
type GitHubReleaseClient interface { type GitHubReleaseClient interface {
FetchLatestRelease(ctx context.Context, repo string) (*GitHubRelease, error) FetchLatestRelease(ctx context.Context, repo string) (*GitHubRelease, error)
DownloadFile(ctx context.Context, url, dest string, maxSize int64) error DownloadFile(ctx context.Context, url, dest string, maxSize int64) error
@@ -81,7 +81,7 @@ type UpdateInfo struct {
GitHubRepo string `json:"github_repo"` GitHubRepo string `json:"github_repo"`
} }
// ReleaseInfo contains repository release details // ReleaseInfo contains GitHub release details
type ReleaseInfo struct { type ReleaseInfo struct {
Name string `json:"name"` Name string `json:"name"`
Body string `json:"body"` Body string `json:"body"`
@@ -97,7 +97,7 @@ type Asset struct {
Size int64 `json:"size"` Size int64 `json:"size"`
} }
// GitHubRelease represents repository release API response // GitHubRelease represents GitHub API response
type GitHubRelease struct { type GitHubRelease struct {
TagName string `json:"tag_name"` TagName string `json:"tag_name"`
Name string `json:"name"` Name string `json:"name"`
@@ -122,7 +122,7 @@ func (s *UpdateService) CheckUpdate(ctx context.Context, force bool) (*UpdateInf
} }
} }
// Fetch from the configured code repository // Fetch from GitHub
info, err := s.fetchLatestRelease(ctx) info, err := s.fetchLatestRelease(ctx)
if err != nil { if err != nil {
// Return cached on error // Return cached on error
@@ -325,21 +325,22 @@ func (s *UpdateService) getArchiveName() string {
return fmt.Sprintf("%s_%s", osName, arch) return fmt.Sprintf("%s_%s", osName, arch)
} }
// validateDownloadURL checks if the URL is from an allowed domain. // validateDownloadURL checks if the URL is from an allowed domain
// SECURITY: This prevents SSRF and ensures downloads only come from trusted release hosts. // SECURITY: This prevents SSRF and ensures downloads only come from trusted GitHub domains
func validateDownloadURL(rawURL string) error { func validateDownloadURL(rawURL string) error {
parsedURL, err := url.Parse(rawURL) parsedURL, err := url.Parse(rawURL)
if err != nil { if err != nil {
return fmt.Errorf("invalid URL: %w", err) return fmt.Errorf("invalid URL: %w", err)
} }
// The self-hosted Gitea instance currently serves releases over HTTP. // Must be HTTPS
if parsedURL.Scheme != "https" && parsedURL.Scheme != "http" { if parsedURL.Scheme != "https" {
return fmt.Errorf("only HTTP(S) URLs are allowed") return fmt.Errorf("only HTTPS URLs are allowed")
} }
// Check against allowed hosts // Check against allowed hosts
host := parsedURL.Hostname() host := parsedURL.Host
// GitHub release URLs can be from github.com or objects.githubusercontent.com
if host != allowedDownloadHost && if host != allowedDownloadHost &&
!strings.HasSuffix(host, "."+allowedDownloadHost) && !strings.HasSuffix(host, "."+allowedDownloadHost) &&
host != allowedAssetHost && host != allowedAssetHost &&
+3
View File
@@ -111,6 +111,9 @@ type UserRepository interface {
UpdateTotpSecret(ctx context.Context, userID int64, encryptedSecret *string) error UpdateTotpSecret(ctx context.Context, userID int64, encryptedSecret *string) error
EnableTotp(ctx context.Context, userID int64) error EnableTotp(ctx context.Context, userID int64) error
DisableTotp(ctx context.Context, userID int64) error DisableTotp(ctx context.Context, userID int64) error
// CountByRegistrationIP 统计指定 IP 注册的用户数量
CountByRegistrationIP(ctx context.Context, ip string) (int, error)
} }
type UserAuthIdentityRecord struct { type UserAuthIdentityRecord struct {
+5 -5
View File
@@ -401,12 +401,12 @@ OPS_ENABLED=true
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Update Configuration (在线更新配置) # Update Configuration (在线更新配置)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# code repository used for online update checks and binary downloads # GitHub repository used for online update checks and binary downloads
# 在线更新检查和二进制下载使用的 代码仓库 # 在线更新检查和二进制下载使用的 GitHub 仓库
UPDATE_GITHUB_REPO=kgod/sub2api UPDATE_GITHUB_REPO=man209111-cpu/sub2api
# Proxy URL for repository update checks and pricing data # Proxy URL for accessing GitHub (used for online updates and pricing data)
# 用于访问代码仓库更新接口和定价数据的代理地址 # 用于访问 GitHub 的代理地址(用于在线更新和定价数据获取)
# Supports: http, https, socks5, socks5h # Supports: http, https, socks5, socks5h
# Examples: # Examples:
# HTTP proxy: http://127.0.0.1:7890 # HTTP proxy: http://127.0.0.1:7890
+4 -4
View File
@@ -10,7 +10,7 @@ docker run -d \
-p 8080:8080 \ -p 8080:8080 \
-e DATABASE_URL="postgres://user:pass@host:5432/sub2api" \ -e DATABASE_URL="postgres://user:pass@host:5432/sub2api" \
-e REDIS_URL="redis://host:6379" \ -e REDIS_URL="redis://host:6379" \
git.jianshixingqiu.com/kgod/sub2api:latest weishaw/sub2api:latest
``` ```
## Docker Compose ## Docker Compose
@@ -20,7 +20,7 @@ version: '3.8'
services: services:
sub2api: sub2api:
image: git.jianshixingqiu.com/kgod/sub2api:latest image: weishaw/sub2api:latest
ports: ports:
- "8080:8080" - "8080:8080"
environment: environment:
@@ -72,5 +72,5 @@ volumes:
## Links ## Links
- [Gitea Repository](http://git.jianshixingqiu.com/kgod/sub2api) - [GitHub Repository](https://github.com/weishaw/sub2api)
- [Documentation](http://git.jianshixingqiu.com/kgod/sub2api#readme) - [Documentation](https://github.com/weishaw/sub2api#readme)
+2 -2
View File
@@ -73,9 +73,9 @@ RUN CGO_ENABLED=0 GOOS=linux go build \
FROM ${ALPINE_IMAGE} FROM ${ALPINE_IMAGE}
# Labels # Labels
LABEL maintainer="kgod <http://git.jianshixingqiu.com/kgod/sub2api>" LABEL maintainer="Wei-Shaw <github.com/Wei-Shaw>"
LABEL description="Sub2API - AI API Gateway Platform" LABEL description="Sub2API - AI API Gateway Platform"
LABEL org.opencontainers.image.source="http://git.jianshixingqiu.com/kgod/sub2api" LABEL org.opencontainers.image.source="https://github.com/Wei-Shaw/sub2api"
# Install runtime dependencies # Install runtime dependencies
RUN apk add --no-cache \ RUN apk add --no-cache \
+5 -5
View File
@@ -35,10 +35,10 @@ Use the automated preparation script for the easiest setup:
```bash ```bash
# Download and run the preparation script # Download and run the preparation script
curl -sSL http://git.jianshixingqiu.com/kgod/sub2api/raw/branch/main/deploy/docker-deploy.sh | bash curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh | bash
# Or download first, then run # Or download first, then run
curl -sSL http://git.jianshixingqiu.com/kgod/sub2api/raw/branch/main/deploy/docker-deploy.sh -o docker-deploy.sh curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/docker-deploy.sh -o docker-deploy.sh
chmod +x docker-deploy.sh chmod +x docker-deploy.sh
./docker-deploy.sh ./docker-deploy.sh
``` ```
@@ -71,7 +71,7 @@ If you prefer manual control:
```bash ```bash
# Clone repository # Clone repository
git clone http://git.jianshixingqiu.com/kgod/sub2api.git git clone https://github.com/Wei-Shaw/sub2api.git
cd sub2api/deploy cd sub2api/deploy
# Configure environment # Configure environment
@@ -353,12 +353,12 @@ For production servers using systemd.
### One-Line Installation ### One-Line Installation
```bash ```bash
curl -sSL http://git.jianshixingqiu.com/kgod/sub2api/raw/branch/main/deploy/install.sh | sudo bash curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | sudo bash
``` ```
### Manual Installation ### Manual Installation
1. Download the latest release from [Gitea Releases](http://git.jianshixingqiu.com/kgod/sub2api/releases) 1. Download the latest release from [GitHub Releases](https://github.com/Wei-Shaw/sub2api/releases)
2. Extract and copy the binary to `/opt/sub2api/` 2. Extract and copy the binary to `/opt/sub2api/`
3. Copy `sub2api.service` to `/etc/systemd/system/` 3. Copy `sub2api.service` to `/etc/systemd/system/`
4. Run: 4. Run:
+6 -6
View File
@@ -4,7 +4,7 @@
# Copy this file to /etc/sub2api/config.yaml and modify as needed # Copy this file to /etc/sub2api/config.yaml and modify as needed
# 复制此文件到 /etc/sub2api/config.yaml 并根据需要修改 # 复制此文件到 /etc/sub2api/config.yaml 并根据需要修改
# #
# Documentation / 文档: http://git.jianshixingqiu.com/kgod/sub2api # Documentation / 文档: https://github.com/Wei-Shaw/sub2api
# ============================================================================= # =============================================================================
# Server Configuration # Server Configuration
@@ -1093,11 +1093,11 @@ gemini:
# Update Configuration (在线更新配置) # Update Configuration (在线更新配置)
# ============================================================================= # =============================================================================
update: update:
# code repository used for online update checks and binary downloads. # GitHub repository used for online update checks and binary downloads.
# 在线更新检查和二进制下载使用的 代码仓库。 # 在线更新检查和二进制下载使用的 GitHub 仓库。
github_repo: "kgod/sub2api" github_repo: "man209111-cpu/sub2api"
# Proxy URL for repository update checks and pricing data # Proxy URL for accessing GitHub (used for online updates and pricing data)
# 用于访问代码仓库更新接口和定价数据的代理地址 # 用于访问 GitHub 的代理地址(用于在线更新和定价数据获取)
# Supports: http, https, socks5, socks5h # Supports: http, https, socks5, socks5h
# Examples: # Examples:
# - HTTP proxy: "http://127.0.0.1:7890" # - HTTP proxy: "http://127.0.0.1:7890"
+4 -4
View File
@@ -24,7 +24,7 @@ services:
# Sub2API Application # Sub2API Application
# =========================================================================== # ===========================================================================
sub2api: sub2api:
image: git.jianshixingqiu.com/kgod/sub2api:latest image: ghcr.io/man209111-cpu/sub2api:latest
container_name: sub2api container_name: sub2api
restart: unless-stopped restart: unless-stopped
ulimits: ulimits:
@@ -144,9 +144,9 @@ services:
# ======================================================================= # =======================================================================
# Update Configuration (在线更新配置) # Update Configuration (在线更新配置)
# ======================================================================= # =======================================================================
# code repository for online update checks and binary downloads # GitHub repo for online update checks and binary downloads
- UPDATE_GITHUB_REPO=${UPDATE_GITHUB_REPO:-kgod/sub2api} - UPDATE_GITHUB_REPO=${UPDATE_GITHUB_REPO:-man209111-cpu/sub2api}
# Proxy for repository update checks and pricing data # Proxy for accessing GitHub (online updates + pricing data)
# Examples: http://host:port, socks5://host:port # Examples: http://host:port, socks5://host:port
- UPDATE_PROXY_URL=${UPDATE_PROXY_URL:-socks5://admin%40sub2api.local:m729066849@172.16.32.16:3389} - UPDATE_PROXY_URL=${UPDATE_PROXY_URL:-socks5://admin%40sub2api.local:m729066849@172.16.32.16:3389}
+2 -2
View File
@@ -12,7 +12,7 @@
services: services:
sub2api: sub2api:
image: git.jianshixingqiu.com/kgod/sub2api:latest image: weishaw/sub2api:latest
container_name: sub2api container_name: sub2api
restart: unless-stopped restart: unless-stopped
ulimits: ulimits:
@@ -92,7 +92,7 @@ services:
# ======================================================================= # =======================================================================
# Update Configuration (在线更新配置) # Update Configuration (在线更新配置)
# ======================================================================= # =======================================================================
- UPDATE_GITHUB_REPO=${UPDATE_GITHUB_REPO:-kgod/sub2api} - UPDATE_GITHUB_REPO=${UPDATE_GITHUB_REPO:-man209111-cpu/sub2api}
- UPDATE_PROXY_URL=${UPDATE_PROXY_URL:-socks5://admin%40sub2api.local:m729066849@172.16.32.16:3389} - UPDATE_PROXY_URL=${UPDATE_PROXY_URL:-socks5://admin%40sub2api.local:m729066849@172.16.32.16:3389}
# Built-in OAuth client secrets (optional) # Built-in OAuth client secrets (optional)
+4 -4
View File
@@ -16,7 +16,7 @@ services:
# Sub2API Application # Sub2API Application
# =========================================================================== # ===========================================================================
sub2api: sub2api:
image: git.jianshixingqiu.com/kgod/sub2api:latest image: ghcr.io/man209111-cpu/sub2api:latest
container_name: sub2api container_name: sub2api
restart: unless-stopped restart: unless-stopped
ulimits: ulimits:
@@ -140,9 +140,9 @@ services:
# ======================================================================= # =======================================================================
# Update Configuration (在线更新配置) # Update Configuration (在线更新配置)
# ======================================================================= # =======================================================================
# code repository for online update checks and binary downloads # GitHub repo for online update checks and binary downloads
- UPDATE_GITHUB_REPO=${UPDATE_GITHUB_REPO:-kgod/sub2api} - UPDATE_GITHUB_REPO=${UPDATE_GITHUB_REPO:-man209111-cpu/sub2api}
# Proxy for repository update checks and pricing data # Proxy for accessing GitHub (online updates + pricing data)
# Examples: http://host:port, socks5://host:port # Examples: http://host:port, socks5://host:port
- UPDATE_PROXY_URL=${UPDATE_PROXY_URL:-socks5://admin%40sub2api.local:m729066849@172.16.32.16:3389} - UPDATE_PROXY_URL=${UPDATE_PROXY_URL:-socks5://admin%40sub2api.local:m729066849@172.16.32.16:3389}
+6 -6
View File
@@ -20,8 +20,8 @@ YELLOW='\033[1;33m'
BLUE='\033[0;34m' BLUE='\033[0;34m'
NC='\033[0m' # No Color NC='\033[0m' # No Color
# Gitea raw content base URL # GitHub raw content base URL
REPO_RAW_URL="http://git.jianshixingqiu.com/kgod/sub2api/raw/branch/main/deploy" GITHUB_RAW_URL="https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy"
# Print colored message # Print colored message
print_info() { print_info() {
@@ -78,9 +78,9 @@ main() {
# Download docker-compose.local.yml and save as docker-compose.yml # Download docker-compose.local.yml and save as docker-compose.yml
print_info "Downloading docker-compose.yml..." print_info "Downloading docker-compose.yml..."
if command_exists curl; then if command_exists curl; then
curl -sSL "${REPO_RAW_URL}/docker-compose.local.yml" -o docker-compose.yml curl -sSL "${GITHUB_RAW_URL}/docker-compose.local.yml" -o docker-compose.yml
elif command_exists wget; then elif command_exists wget; then
wget -q "${REPO_RAW_URL}/docker-compose.local.yml" -O docker-compose.yml wget -q "${GITHUB_RAW_URL}/docker-compose.local.yml" -O docker-compose.yml
else else
print_error "Neither curl nor wget is installed. Please install one of them." print_error "Neither curl nor wget is installed. Please install one of them."
exit 1 exit 1
@@ -90,9 +90,9 @@ main() {
# Download .env.example # Download .env.example
print_info "Downloading .env.example..." print_info "Downloading .env.example..."
if command_exists curl; then if command_exists curl; then
curl -sSL "${REPO_RAW_URL}/.env.example" -o .env.example curl -sSL "${GITHUB_RAW_URL}/.env.example" -o .env.example
else else
wget -q "${REPO_RAW_URL}/.env.example" -O .env.example wget -q "${GITHUB_RAW_URL}/.env.example" -O .env.example
fi fi
print_success "Downloaded .env.example" print_success "Downloaded .env.example"
+11 -24
View File
@@ -2,7 +2,7 @@
# #
# Sub2API Installation Script # Sub2API Installation Script
# Sub2API 安装脚本 # Sub2API 安装脚本
# Usage: curl -sSL http://git.jianshixingqiu.com/kgod/sub2api/raw/branch/main/deploy/install.sh | bash # Usage: curl -sSL https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/deploy/install.sh | bash
# #
set -e set -e
@@ -15,10 +15,8 @@ BLUE='\033[0;34m'
CYAN='\033[0;36m' CYAN='\033[0;36m'
NC='\033[0m' # No Color NC='\033[0m' # No Color
# Repository configuration # Configuration
GITEA_BASE_URL="http://git.jianshixingqiu.com" GITHUB_REPO="Wei-Shaw/sub2api"
GITEA_API_URL="${GITEA_BASE_URL}/api/v1"
GITEA_REPO="kgod/sub2api"
INSTALL_DIR="/opt/sub2api" INSTALL_DIR="/opt/sub2api"
SERVICE_NAME="sub2api" SERVICE_NAME="sub2api"
SERVICE_USER="sub2api" SERVICE_USER="sub2api"
@@ -470,7 +468,7 @@ check_dependencies() {
# Get latest release version # Get latest release version
get_latest_version() { get_latest_version() {
print_info "$(msg 'fetching_version')" print_info "$(msg 'fetching_version')"
LATEST_VERSION=$(curl -s --connect-timeout 10 --max-time 30 "${GITEA_API_URL}/repos/${GITEA_REPO}/releases/latest" 2>/dev/null | grep '"tag_name"' | sed -E 's/.*"tag_name"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/') LATEST_VERSION=$(curl -s --connect-timeout 10 --max-time 30 "https://api.github.com/repos/${GITHUB_REPO}/releases/latest" 2>/dev/null | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/')
if [ -z "$LATEST_VERSION" ]; then if [ -z "$LATEST_VERSION" ]; then
print_error "$(msg 'failed_get_version')" print_error "$(msg 'failed_get_version')"
@@ -486,7 +484,7 @@ list_versions() {
print_info "$(msg 'fetching_versions')" print_info "$(msg 'fetching_versions')"
local versions local versions
versions=$(curl -s --connect-timeout 10 --max-time 30 "${GITEA_API_URL}/repos/${GITEA_REPO}/releases" 2>/dev/null | grep '"tag_name"' | sed -E 's/.*"tag_name"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/' | head -20) versions=$(curl -s --connect-timeout 10 --max-time 30 "https://api.github.com/repos/${GITHUB_REPO}/releases" 2>/dev/null | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/' | head -20)
if [ -z "$versions" ]; then if [ -z "$versions" ]; then
print_error "$(msg 'failed_get_version')" print_error "$(msg 'failed_get_version')"
@@ -523,11 +521,11 @@ validate_version() {
# Check if the release exists # Check if the release exists
local http_code local http_code
http_code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 10 --max-time 30 "${GITEA_API_URL}/repos/${GITEA_REPO}/releases/tags/${version}" 2>/dev/null) http_code=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 10 --max-time 30 "https://api.github.com/repos/${GITHUB_REPO}/releases/tags/${version}" 2>/dev/null)
# Check for network errors (empty or non-numeric response) # Check for network errors (empty or non-numeric response)
if [ -z "$http_code" ] || ! [[ "$http_code" =~ ^[0-9]+$ ]]; then if [ -z "$http_code" ] || ! [[ "$http_code" =~ ^[0-9]+$ ]]; then
print_error "Network error: Failed to connect to Gitea API" >&2 print_error "Network error: Failed to connect to GitHub API" >&2
exit 1 exit 1
fi fi
@@ -556,19 +554,8 @@ get_current_version() {
download_and_extract() { download_and_extract() {
local version_num=${LATEST_VERSION#v} local version_num=${LATEST_VERSION#v}
local archive_name="sub2api_${version_num}_${OS}_${ARCH}.tar.gz" local archive_name="sub2api_${version_num}_${OS}_${ARCH}.tar.gz"
local release_json local download_url="https://github.com/${GITHUB_REPO}/releases/download/${LATEST_VERSION}/${archive_name}"
local download_url local checksum_url="https://github.com/${GITHUB_REPO}/releases/download/${LATEST_VERSION}/checksums.txt"
local checksum_url
release_json=$(curl -s --connect-timeout 10 --max-time 30 "${GITEA_API_URL}/repos/${GITEA_REPO}/releases/tags/${LATEST_VERSION}" 2>/dev/null || true)
download_url=$(printf '%s' "$release_json" | tr '{' '\n' | grep "\"name\"[[:space:]]*:[[:space:]]*\"${archive_name}\"" | sed -n -E 's/.*"browser_download_url"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' | head -1)
checksum_url=$(printf '%s' "$release_json" | tr '{' '\n' | grep '"name"[[:space:]]*:[[:space:]]*"checksums.txt"' | sed -n -E 's/.*"browser_download_url"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' | head -1)
if [ -z "$download_url" ]; then
print_error "$(msg 'download_failed'): ${archive_name}"
print_info "No matching release asset found at ${GITEA_BASE_URL}/${GITEA_REPO}/releases/tag/${LATEST_VERSION}"
exit 1
fi
print_info "$(msg 'downloading') ${archive_name}..." print_info "$(msg 'downloading') ${archive_name}..."
@@ -584,7 +571,7 @@ download_and_extract() {
# Download and verify checksum # Download and verify checksum
print_info "$(msg 'verifying_checksum')" print_info "$(msg 'verifying_checksum')"
if [ -n "$checksum_url" ] && curl -sL "$checksum_url" -o "$TEMP_DIR/checksums.txt" 2>/dev/null; then if curl -sL "$checksum_url" -o "$TEMP_DIR/checksums.txt" 2>/dev/null; then
local expected_checksum=$(grep "$archive_name" "$TEMP_DIR/checksums.txt" | awk '{print $1}') local expected_checksum=$(grep "$archive_name" "$TEMP_DIR/checksums.txt" | awk '{print $1}')
local actual_checksum=$(sha256sum "$TEMP_DIR/$archive_name" | awk '{print $1}') local actual_checksum=$(sha256sum "$TEMP_DIR/$archive_name" | awk '{print $1}')
@@ -668,7 +655,7 @@ install_service() {
cat > /etc/systemd/system/sub2api.service << EOF cat > /etc/systemd/system/sub2api.service << EOF
[Unit] [Unit]
Description=Sub2API - AI API Gateway Platform Description=Sub2API - AI API Gateway Platform
Documentation=http://git.jianshixingqiu.com/kgod/sub2api Documentation=https://github.com/Wei-Shaw/sub2api
After=network.target postgresql.service redis.service After=network.target postgresql.service redis.service
Wants=postgresql.service redis.service Wants=postgresql.service redis.service
+4 -4
View File
@@ -119,8 +119,8 @@ https://pay.example.com/pay?user_id=123&token=<jwt>&theme=light&lang=zh&ui_mode=
- 重试保持相同 `code`,并使用新的 `Idempotency-Key` - 重试保持相同 `code`,并使用新的 `Idempotency-Key`
### 6) `doc_url` 配置建议 ### 6) `doc_url` 配置建议
- 查看链接:`http://git.jianshixingqiu.com/kgod/sub2api/src/branch/main/ADMIN_PAYMENT_INTEGRATION_API.md` - 查看链接:`https://github.com/Wei-Shaw/sub2api/blob/main/ADMIN_PAYMENT_INTEGRATION_API.md`
- 下载链接:`http://git.jianshixingqiu.com/kgod/sub2api/raw/branch/main/ADMIN_PAYMENT_INTEGRATION_API.md` - 下载链接:`https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/ADMIN_PAYMENT_INTEGRATION_API.md`
--- ---
@@ -239,5 +239,5 @@ https://pay.example.com/pay?user_id=123&token=<jwt>&theme=light&lang=zh&ui_mode=
- Keep the same `code` for retry, and use a new `Idempotency-Key` - Keep the same `code` for retry, and use a new `Idempotency-Key`
### 6) Recommended `doc_url` ### 6) Recommended `doc_url`
- View URL: `http://git.jianshixingqiu.com/kgod/sub2api/src/branch/main/ADMIN_PAYMENT_INTEGRATION_API.md` - View URL: `https://github.com/Wei-Shaw/sub2api/blob/main/ADMIN_PAYMENT_INTEGRATION_API.md`
- Download URL: `http://git.jianshixingqiu.com/kgod/sub2api/raw/branch/main/ADMIN_PAYMENT_INTEGRATION_API.md` - Download URL: `https://raw.githubusercontent.com/Wei-Shaw/sub2api/main/ADMIN_PAYMENT_INTEGRATION_API.md`
+1 -1
View File
@@ -31,7 +31,7 @@ export async function getVersion(): Promise<{ version: string }> {
/** /**
* Check for updates * Check for updates
* @param force - Force refresh from Gitea API * @param force - Force refresh from GitHub API
*/ */
export async function checkUpdates(force = false): Promise<VersionInfo> { export async function checkUpdates(force = false): Promise<VersionInfo> {
const { data } = await apiClient.get<VersionInfo>('/admin/system/check-updates', { const { data } = await apiClient.get<VersionInfo>('/admin/system/check-updates', {
+1 -1
View File
@@ -128,7 +128,7 @@
<a <a
v-if="authStore.isAdmin" v-if="authStore.isAdmin"
href="http://git.jianshixingqiu.com/kgod/sub2api" href="https://github.com/Wei-Shaw/sub2api"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
@click="closeDropdown" @click="closeDropdown"
+1 -1
View File
@@ -467,7 +467,7 @@ const isHomeContentUrl = computed(() => {
const { isDarkTheme: isDark, toggleTheme } = useTheme() const { isDarkTheme: isDark, toggleTheme } = useTheme()
// GitHub URL // GitHub URL
const githubUrl = 'http://git.jianshixingqiu.com/kgod/sub2api' const githubUrl = 'https://github.com/Wei-Shaw/sub2api'
// Auth state // Auth state
const isAuthenticated = computed(() => authStore.isAuthenticated) const isAuthenticated = computed(() => authStore.isAuthenticated)
+1 -1
View File
@@ -376,7 +376,7 @@ const appStore = useAppStore()
const siteName = computed(() => appStore.cachedPublicSettings?.site_name || appStore.siteName || 'Sub2API') const siteName = computed(() => appStore.cachedPublicSettings?.site_name || appStore.siteName || 'Sub2API')
const siteLogo = computed(() => appStore.cachedPublicSettings?.site_logo || appStore.siteLogo || '') const siteLogo = computed(() => appStore.cachedPublicSettings?.site_logo || appStore.siteLogo || '')
const docUrl = computed(() => appStore.cachedPublicSettings?.doc_url || appStore.docUrl || '') const docUrl = computed(() => appStore.cachedPublicSettings?.doc_url || appStore.docUrl || '')
const githubUrl = 'http://git.jianshixingqiu.com/kgod/sub2api' const githubUrl = 'https://github.com/Wei-Shaw/sub2api'
// ==================== Theme (same as HomeView) ==================== // ==================== Theme (same as HomeView) ====================
+4 -4
View File
@@ -6571,14 +6571,14 @@ function applyCustomMenuIcon(item: { icon_svg: string }, optionID: string) {
const paymentGuideHref = computed(() => const paymentGuideHref = computed(() =>
locale.value.startsWith("zh") locale.value.startsWith("zh")
? "http://git.jianshixingqiu.com/kgod/sub2api/src/branch/main/docs/PAYMENT_CN.md" ? "https://github.com/Wei-Shaw/sub2api/blob/main/docs/PAYMENT_CN.md"
: "http://git.jianshixingqiu.com/kgod/sub2api/src/branch/main/docs/PAYMENT.md", : "https://github.com/Wei-Shaw/sub2api/blob/main/docs/PAYMENT.md",
); );
const paymentMethodsHref = computed(() => const paymentMethodsHref = computed(() =>
locale.value.startsWith("zh") locale.value.startsWith("zh")
? "http://git.jianshixingqiu.com/kgod/sub2api/src/branch/main/docs/PAYMENT_CN.md#支持的支付方式" ? "https://github.com/Wei-Shaw/sub2api/blob/main/docs/PAYMENT_CN.md#支持的支付方式"
: "http://git.jianshixingqiu.com/kgod/sub2api/src/branch/main/docs/PAYMENT.md#supported-payment-methods", : "https://github.com/Wei-Shaw/sub2api/blob/main/docs/PAYMENT.md#supported-payment-methods",
); );
type SettingsTab = type SettingsTab =
@@ -557,10 +557,10 @@ describe("admin SettingsView payment visible method controls", () => {
expect(paymentLinks).toHaveLength(2); expect(paymentLinks).toHaveLength(2);
expect(paymentLinks[0]?.attributes("href")).toBe( expect(paymentLinks[0]?.attributes("href")).toBe(
"http://git.jianshixingqiu.com/kgod/sub2api/src/branch/main/docs/PAYMENT_CN.md", "https://github.com/Wei-Shaw/sub2api/blob/main/docs/PAYMENT_CN.md",
); );
expect(paymentLinks[1]?.attributes("href")).toBe( expect(paymentLinks[1]?.attributes("href")).toBe(
"http://git.jianshixingqiu.com/kgod/sub2api/src/branch/main/docs/PAYMENT_CN.md#支持的支付方式", "https://github.com/Wei-Shaw/sub2api/blob/main/docs/PAYMENT_CN.md#支持的支付方式",
); );
for (const link of paymentLinks) { for (const link of paymentLinks) {
expect(link.attributes("href")).toContain("docs/PAYMENT"); expect(link.attributes("href")).toContain("docs/PAYMENT");