Skip to content

Commit 115417d

Browse files
committed
feat: docker compose + storage improvements
1 parent 9fc08cc commit 115417d

25 files changed

Lines changed: 696 additions & 45 deletions

.dockerignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
__pycache__
2+
.venv
3+
.wrangler
4+
node_modules
25
*.pyc
36
*.pyo
47
*.pyd

.env.example

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Compose will automatically read `.env` from the project root.
2+
# Copy this file to `.env` and adjust values as needed.
3+
4+
# ---- Image / Port ----
5+
GROK2API_PORT=8000
6+
# GROK2API_IMAGE=ghcr.io/tqzhr/grok2api:latest
7+
8+
# ---- Runtime ----
9+
TZ=Asia/Shanghai
10+
LOG_LEVEL=INFO
11+
SERVER_HOST=0.0.0.0
12+
SERVER_PORT=8000
13+
SERVER_WORKERS=1
14+
15+
# ---- Storage (default: local) ----
16+
SERVER_STORAGE_TYPE=local
17+
SERVER_STORAGE_URL=
18+
STORAGE_WAIT_TIMEOUT=60
19+
20+
# ---- Optional profiles (uncomment one) ----
21+
# COMPOSE_PROFILES=redis
22+
# COMPOSE_PROFILES=pgsql
23+
# COMPOSE_PROFILES=mysql
24+
25+
# ---- Redis example ----
26+
# COMPOSE_PROFILES=redis
27+
# SERVER_STORAGE_TYPE=redis
28+
# SERVER_STORAGE_URL=redis://redis:6379/0
29+
30+
# ---- PostgreSQL example ----
31+
# COMPOSE_PROFILES=pgsql
32+
# SERVER_STORAGE_TYPE=pgsql
33+
# PGSQL_DB=grok2api
34+
# PGSQL_USER=grok2api
35+
# PGSQL_PASSWORD=change_me
36+
# SERVER_STORAGE_URL=postgresql+asyncpg://grok2api:change_me@pgsql:5432/grok2api
37+
38+
# ---- MySQL example ----
39+
# COMPOSE_PROFILES=mysql
40+
# SERVER_STORAGE_TYPE=mysql
41+
# MYSQL_DB=grok2api
42+
# MYSQL_USER=grok2api
43+
# MYSQL_PASSWORD=change_me
44+
# MYSQL_ROOT_PASSWORD=change_me
45+
# SERVER_STORAGE_URL=mysql+aiomysql://grok2api:change_me@mysql:3306/grok2api
46+

.github/workflows/cloudflare-workers.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ jobs:
2929
- name: Checkout
3030
uses: actions/checkout@v4
3131

32+
- name: Setup Python
33+
uses: actions/setup-python@v5
34+
with:
35+
python-version: "3.12"
36+
3237
- name: Setup Node
3338
uses: actions/setup-node@v4
3439
with:

.github/workflows/docker-ghcr.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: Docker (GHCR)
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
tags: ["v*"]
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: read
11+
packages: write
12+
13+
jobs:
14+
build-and-push:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v4
19+
20+
- name: Set up QEMU
21+
uses: docker/setup-qemu-action@v3
22+
23+
- name: Set up Docker Buildx
24+
uses: docker/setup-buildx-action@v3
25+
26+
- name: Login to GHCR
27+
uses: docker/login-action@v3
28+
with:
29+
registry: ghcr.io
30+
username: ${{ github.actor }}
31+
password: ${{ secrets.GITHUB_TOKEN }}
32+
33+
- name: Extract Docker metadata
34+
id: meta
35+
uses: docker/metadata-action@v5
36+
with:
37+
images: ghcr.io/${{ github.repository }}
38+
tags: |
39+
type=raw,value=latest,enable={{is_default_branch}}
40+
type=ref,event=tag
41+
type=sha,format=short
42+
43+
- name: Build and push
44+
uses: docker/build-push-action@v6
45+
with:
46+
context: .
47+
file: ./Dockerfile
48+
platforms: linux/amd64,linux/arm64
49+
push: true
50+
tags: ${{ steps.meta.outputs.tags }}
51+
labels: ${{ steps.meta.outputs.labels }}
52+
cache-from: type=gha
53+
cache-to: type=gha,mode=max
54+

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,4 @@ RUN mkdir -p /app/data /app/data/tmp /app/logs
3939
EXPOSE 8000
4040

4141
ENTRYPOINT ["/app/scripts/entrypoint.sh"]
42-
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
42+
CMD ["/app/scripts/start.sh"]

README.cloudflare.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,12 @@ npx wrangler deploy
117117
- `GET https://<你的域名或workers.dev>/health`
118118
- 打开 `https://<你的域名或workers.dev>/login`
119119

120+
(可选)冒烟测试:
121+
122+
```bash
123+
python scripts/smoke_test.py --base-url https://<你的域名或workers.dev>
124+
```
125+
120126
默认管理员账号密码:
121127
- `admin / admin`
122128

@@ -138,6 +144,8 @@ npx wrangler deploy
138144
- `CLOUDFLARE_API_TOKEN`
139145
- `CLOUDFLARE_ACCOUNT_ID`(必填)
140146

147+
> 提示:`CLOUDFLARE_API_TOKEN` 建议使用 **API Token**(不要用 Global API Key),并确保至少包含 **Workers Scripts / D1 / Workers KV Storage** 的编辑权限;否则工作流可能无法自动创建/复用 D1/KV 或部署 Worker。
148+
141149
然后直接 push 到 `main`(或在 Actions 页面手动 Run workflow)即可一键部署(无需你手动创建/填写 D1 或 KV 的 ID)。
142150

143151
> 注意:此版本不再使用 R2。GitHub Actions 会自动创建/复用 D1 与 KV,但你仍需在 GitHub 配好 `CLOUDFLARE_API_TOKEN` / `CLOUDFLARE_ACCOUNT_ID`

app/api/v1/image.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,7 @@ async def create_image(request: ImageGenerationRequest):
121121
try:
122122
token_mgr = await get_token_manager()
123123
await token_mgr.reload_if_stale()
124-
pool_name = ModelService.pool_for_model(request.model)
125-
token = token_mgr.get_token(pool_name)
124+
token = token_mgr.get_token_for_model(request.model)
126125
except Exception as e:
127126
logger.error(f"Failed to get token: {e}")
128127
try:

app/api/v1/models.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
Models API 路由
33
"""
44

5-
from fastapi import APIRouter
5+
import time
6+
7+
from fastapi import APIRouter, HTTPException
68

79
from app.services.grok.model import ModelService
810

@@ -13,16 +15,37 @@
1315
@router.get("/models")
1416
async def list_models():
1517
"""OpenAI 兼容 models 列表接口"""
18+
ts = int(time.time())
1619
data = [
1720
{
1821
"id": m.model_id,
1922
"object": "model",
20-
"created": 0,
23+
"created": ts,
2124
"owned_by": "grok2api",
25+
"display_name": m.display_name,
26+
"description": m.description,
2227
}
2328
for m in ModelService.list()
2429
]
2530
return {"object": "list", "data": data}
2631

2732

33+
@router.get("/models/{model_id}")
34+
async def get_model(model_id: str):
35+
"""OpenAI compatible: single model detail."""
36+
m = ModelService.get(model_id)
37+
if not m:
38+
raise HTTPException(status_code=404, detail=f"Model '{model_id}' not found")
39+
40+
ts = int(time.time())
41+
return {
42+
"id": m.model_id,
43+
"object": "model",
44+
"created": ts,
45+
"owned_by": "grok2api",
46+
"display_name": m.display_name,
47+
"description": m.description,
48+
}
49+
50+
2851
__all__ = ["router"]

app/services/grok/chat.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -211,14 +211,21 @@ def build_payload(
211211
temporary = get_config("grok.temporary", True)
212212
if think is None:
213213
think = get_config("grok.thinking", False)
214+
215+
# Upstream payload expects image attachments merged into fileAttachments.
216+
merged_attachments: List[str] = []
217+
if file_attachments:
218+
merged_attachments.extend(file_attachments)
219+
if image_attachments:
220+
merged_attachments.extend(image_attachments)
214221

215222
return {
216223
"temporary": temporary,
217224
"modelName": model,
218225
"modelMode": mode,
219226
"message": message,
220-
"fileAttachments": file_attachments or [],
221-
"imageAttachments": image_attachments or [],
227+
"fileAttachments": merged_attachments,
228+
"imageAttachments": [],
222229
"disableSearch": False,
223230
"enableImageGeneration": True,
224231
"returnImageBytes": False,
@@ -460,8 +467,7 @@ async def completions(
460467
try:
461468
token_mgr = await get_token_manager()
462469
await token_mgr.reload_if_stale()
463-
pool_name = ModelService.pool_for_model(model)
464-
token = token_mgr.get_token(pool_name)
470+
token = token_mgr.get_token_for_model(model)
465471
except Exception as e:
466472
logger.error(f"Failed to get token: {e}")
467473
try:

app/services/grok/media.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -398,8 +398,7 @@ async def completions(
398398
try:
399399
token_mgr = await get_token_manager()
400400
await token_mgr.reload_if_stale()
401-
pool_name = ModelService.pool_for_model(model)
402-
token = token_mgr.get_token(pool_name)
401+
token = token_mgr.get_token_for_model(model)
403402
except Exception as e:
404403
logger.error(f"Failed to get token: {e}")
405404
try:

0 commit comments

Comments
 (0)