diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..089f897
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,82 @@
+# Dependencies
+node_modules
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Testing
+coverage
+.nyc_output
+test-results
+
+# Production build
+dist
+build
+
+# Environment variables
+.env
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+# IDE and editor files
+.vscode
+.idea
+*.swp
+*.swo
+*~
+
+# OS generated files
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+
+# Logs
+logs
+*.log
+
+# Git
+.git
+.gitignore
+
+# GitHub
+.github
+
+# Temporary files
+tmp
+temp
+
+# Documentation
+*.md
+!README.md
+
+# Vercel
+.vercel
+vercel.json
+
+# Cloudflare Workers
+wrangler.toml
+
+# ESLint
+.eslintcache
+
+# Package manager
+.npm
+.pnpm-debug.log*
+
+# Optional npm cache directory
+.npm
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
new file mode 100644
index 0000000..e1f345f
--- /dev/null
+++ b/.github/workflows/docker.yml
@@ -0,0 +1,94 @@
+name: Build and Push Docker Image to GHCR
+
+on:
+ push:
+ branches:
+ - main
+ - develop
+ tags:
+ - 'v*'
+ pull_request:
+ branches:
+ - main
+ workflow_dispatch:
+
+env:
+ REGISTRY: ghcr.io
+ IMAGE_NAME: ${{ github.repository }}
+
+jobs:
+ build-and-push:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Log in to Container Registry
+ uses: docker/login-action@v3
+ with:
+ registry: ${{ env.REGISTRY }}
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Extract metadata
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
+ tags: |
+ type=ref,event=branch
+ type=ref,event=pr
+ type=semver,pattern={{version}}
+ type=semver,pattern={{major}}.{{minor}}
+ type=semver,pattern={{major}}
+ type=sha,prefix={{branch}}-
+ type=raw,value=latest,enable={{is_default_branch}}
+
+ - name: Build and push Docker image
+ uses: docker/build-push-action@v5
+ with:
+ context: .
+ file: ./Dockerfile
+ push: true
+ tags: ${{ steps.meta.outputs.tags }}
+ labels: ${{ steps.meta.outputs.labels }}
+ platforms: linux/amd64,linux/arm64
+ cache-from: type=gha
+ cache-to: type=gha,mode=max
+
+ - name: Generate artifact attestation
+ uses: actions/attest-build-provenance@v1
+ with:
+ subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
+ subject-digest: ${{ steps.build.outputs.digest }}
+ push-to-registry: true
+
+ security-scan:
+ runs-on: ubuntu-latest
+ needs: build-and-push
+ if: github.event_name != 'pull_request'
+ permissions:
+ contents: read
+ packages: read
+ security-events: write
+
+ steps:
+ - name: Run Trivy vulnerability scanner
+ uses: aquasecurity/trivy-action@master
+ with:
+ image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
+ format: 'sarif'
+ output: 'trivy-results.sarif'
+
+ - name: Upload Trivy scan results to GitHub Security tab
+ uses: github/codeql-action/upload-sarif@v3
+ if: always()
+ with:
+ sarif_file: 'trivy-results.sarif'
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..bebd37d
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,38 @@
+# Use the official Node.js runtime as the base image
+FROM node:20-alpine
+
+# Set the working directory inside the container
+WORKDIR /app
+
+# Copy package.json first to leverage Docker cache for dependencies
+COPY package.json ./
+
+# Install dependencies including express for standalone server
+RUN npm ci --only=production && npm install express
+
+# Copy the rest of the application code
+COPY . .
+
+# Create a non-root user for security
+RUN addgroup -g 1001 -S nodejs && \
+ adduser -S xget -u 1001
+
+# Change ownership of the app directory to the nodejs user
+RUN chown -R xget:nodejs /app
+
+# Switch to the non-root user
+USER xget
+
+# Expose the port the app runs on
+EXPOSE 3000
+
+# Set environment variables
+ENV NODE_ENV=production
+ENV PORT=3000
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
+ CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1
+
+# Start the application
+CMD ["node", "server.js"]
diff --git a/README.md b/README.md
index cb696c0..04ae7ed 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,7 @@
[](#-生态系统集成)
[](#cloudflare-workers-一键部署)
[](#vercel-一键部署)
+[](#docker-部署)
[](#github)
[](#gitlab)
@@ -990,7 +991,7 @@ conda env update -f environment.yml
https://xget.xi-xu.me/maven/maven2
-
+
xget-maven-central
@@ -1563,7 +1564,7 @@ import requests
def download_arxiv_paper(arxiv_id, output_path):
url = f"https://xget.xi-xu.me/arxiv/pdf/{arxiv_id}.pdf"
response = requests.get(url)
-
+
if response.status_code == 200:
with open(output_path, 'wb') as f:
f.write(response.content)
@@ -1731,10 +1732,10 @@ import requests
class XgetTransport:
def __init__(self, base_url):
self.base_url = base_url
-
+
def request(self, method, url, **kwargs):
# 将请求转发到 Xget 加速服务
- accelerated_url = url.replace("https://generativelanguage.googleapis.com",
+ accelerated_url = url.replace("https://generativelanguage.googleapis.com",
"https://xget.xi-xu.me/ip/gemini")
return requests.request(method, accelerated_url, **kwargs)
@@ -1760,10 +1761,10 @@ def call_ai_api(provider, endpoint, data, api_key):
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
-
+
# 使用 Xget 加速 URL
url = f"https://xget.xi-xu.me/ip/{provider}/{endpoint}"
-
+
response = requests.post(url, headers=headers, json=data)
return response.json()
@@ -1824,7 +1825,7 @@ async function chatWithGPT() {
messages: [{ role: 'user', content: 'Hello!' }],
model: 'gpt-4',
});
-
+
console.log(completion.choices[0].message.content);
}
@@ -1842,7 +1843,7 @@ async function chatWithClaude() {
max_tokens: 1000,
messages: [{ role: 'user', content: 'Hello!' }],
});
-
+
console.log(message.content);
}
```
@@ -1949,7 +1950,7 @@ services:
- "80:80"
volumes:
- ./html:/usr/share/nginx/html
-
+
database:
image: xget.xi-xu.me/cr/mcr/mssql/server:2022-latest
environment:
@@ -1957,7 +1958,7 @@ services:
SA_PASSWORD: "MyStrongPassword123!"
volumes:
- mssql_data:/var/opt/mssql
-
+
cache:
image: xget.xi-xu.me/cr/ghcr/bitnami/redis:alpine
ports:
@@ -2003,13 +2004,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
-
+
- name: Build with accelerated base images
run: |
# 构建时使用 Xget 加速的基础镜像
docker build -t myapp:latest \
--build-arg BASE_IMAGE=xget.xi-xu.me/cr/ghcr/nodejs/node:18-alpine .
-
+
- name: Test with accelerated images
run: |
# 使用加速镜像进行测试
@@ -2063,17 +2064,17 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
-
+
- name: Download model files
run: |
# 使用 Xget 加速下载大型模型文件
wget https://xget.xi-xu.me/hf/microsoft/DialoGPT-medium/resolve/main/pytorch_model.bin
-
+
- name: Clone dependency repo
run: |
# 使用 Xget 加速 Git 克隆
git clone https://xget.xi-xu.me/gh/[所有者]/[存储库].git
-
+
- name: Download release assets
run: |
# 批量下载发布文件
@@ -2146,6 +2147,146 @@ WORKDIR /app
部署后,你的 Xget 服务将在 `your-project-name.vercel.app` 上可用。
+### Docker 部署
+
+[](https://github.com/xixu-me/Xget/pkgs/container/xget)
+
+#### 使用预构建镜像(推荐)
+
+```bash
+# 拉取最新镜像
+docker pull ghcr.io/xixu-me/xget:latest
+
+# 运行容器
+docker run -d \
+ --name xget \
+ -p 3000:3000 \
+ --restart unless-stopped \
+ ghcr.io/xixu-me/xget:latest
+```
+
+#### 本地构建镜像
+
+```bash
+# 克隆存储库
+git clone https://github.com/xixu-me/Xget.git
+cd Xget
+
+# 构建镜像
+docker build -t xget .
+
+# 运行容器
+docker run -d \
+ --name xget \
+ -p 3000:3000 \
+ --restart unless-stopped \
+ xget
+```
+
+#### Docker Compose
+
+创建 `docker-compose.yml` 文件:
+
+```yaml
+version: '3.8'
+
+services:
+ xget:
+ image: ghcr.io/xixu-me/xget:latest
+ container_name: xget
+ ports:
+ - "3000:3000"
+ restart: unless-stopped
+ environment:
+ - NODE_ENV=production
+ - PORT=3000
+ healthcheck:
+ test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ start_period: 40s
+```
+
+然后运行:
+
+```bash
+docker-compose up -d
+```
+
+#### Kubernetes 部署
+
+创建 `k8s-deployment.yaml`:
+
+```yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: xget
+ labels:
+ app: xget
+spec:
+ replicas: 3
+ selector:
+ matchLabels:
+ app: xget
+ template:
+ metadata:
+ labels:
+ app: xget
+ spec:
+ containers:
+ - name: xget
+ image: ghcr.io/xixu-me/xget:latest
+ ports:
+ - containerPort: 3000
+ env:
+ - name: NODE_ENV
+ value: "production"
+ - name: PORT
+ value: "3000"
+ livenessProbe:
+ httpGet:
+ path: /api/health
+ port: 3000
+ initialDelaySeconds: 30
+ periodSeconds: 10
+ readinessProbe:
+ httpGet:
+ path: /api/health
+ port: 3000
+ initialDelaySeconds: 5
+ periodSeconds: 5
+ resources:
+ requests:
+ memory: "128Mi"
+ cpu: "100m"
+ limits:
+ memory: "256Mi"
+ cpu: "500m"
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: xget-service
+spec:
+ selector:
+ app: xget
+ ports:
+ - protocol: TCP
+ port: 80
+ targetPort: 3000
+ type: LoadBalancer
+```
+
+部署到 Kubernetes:
+
+```bash
+kubectl apply -f k8s-deployment.yaml
+```
+
+部署完成后,你的 Xget 服务将在 `http://localhost:3000` 上可用。通过 `/api/health` 端点可以检查服务状态。
+
### 手动部署
如果你更喜欢手动部署或需要自定义配置:
@@ -2220,7 +2361,7 @@ export const CONFIG = {
```javascript
export const PLATFORMS = {
// 现有平台...
-
+
// 新平台示例
custom: {
base: "https://example.com",
@@ -2282,16 +2423,16 @@ npm run test:watch
### 常见问题
-**Q: 下载速度没有明显提升?**
+**Q: 下载速度没有明显提升?**
A: 检查源文件是否已经在 CDN 边缘节点缓存,首次访问可能较慢,后续访问会显著提升。
-**Q: Git 操作失败?**
+**Q: Git 操作失败?**
A: 确认使用了正确的 URL 格式,且 Git 客户端版本支持 HTTPS 代理。
-**Q: 部署后无法访问?**
+**Q: 部署后无法访问?**
A: 检查 Cloudflare Workers 域名是否正确绑定,确认 `wrangler.toml` 配置正确。
-**Q: 出现 400 错误?**
+**Q: 出现 400 错误?**
A: 检查 URL 路径格式,确认平台前缀正确使用。
### 性能监控
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..a16d835
--- /dev/null
+++ b/server.js
@@ -0,0 +1,78 @@
+import express from 'express';
+import handler from './api/[...path].js';
+import healthHandler from './api/health.js';
+
+const app = express();
+const PORT = process.env.PORT || 3000;
+
+// Middleware to parse JSON bodies
+app.use(express.json());
+app.use(express.raw({ type: 'application/octet-stream', limit: '50mb' }));
+
+// Health endpoint
+app.get('/api/health', async (req, res) => {
+ try {
+ const request = new Request(`http://localhost:${PORT}${req.url}`, {
+ method: req.method,
+ headers: req.headers
+ });
+ const response = await healthHandler(request);
+
+ res.status(response.status);
+ response.headers.forEach((value, key) => {
+ res.set(key, value);
+ });
+
+ const contentType = response.headers.get('content-type');
+ if (contentType && contentType.includes('application/json')) {
+ res.json(await response.json());
+ } else {
+ res.send(await response.text());
+ }
+ } catch (error) {
+ console.error('Health check error:', error);
+ res.status(500).json({ error: 'Health check failed' });
+ }
+});
+
+// Main handler for all other routes
+app.use('*', async (req, res) => {
+ try {
+ const request = new Request(`http://localhost:${PORT}${req.originalUrl}`, {
+ method: req.method,
+ headers: req.headers,
+ body: req.method !== 'GET' && req.method !== 'HEAD' ? req.body : undefined
+ });
+
+ const response = await handler(request);
+
+ res.status(response.status);
+ response.headers.forEach((value, key) => {
+ res.set(key, value);
+ });
+
+ const contentType = response.headers.get('content-type');
+ if (contentType && contentType.includes('application/json')) {
+ res.json(await response.json());
+ } else if (response.body) {
+ // Handle binary data
+ const arrayBuffer = await response.arrayBuffer();
+ const buffer = Buffer.from(arrayBuffer);
+ res.send(buffer);
+ } else {
+ res.send(await response.text());
+ }
+ } catch (error) {
+ console.error('Request handler error:', error);
+ res.status(500).json({
+ error: 'Internal server error',
+ message: error.message,
+ timestamp: new Date().toISOString()
+ });
+ }
+});
+
+app.listen(PORT, '0.0.0.0', () => {
+ console.log(`🚀 Xget server running on port ${PORT}`);
+ console.log(`📡 Health check: http://localhost:${PORT}/api/health`);
+});