chore: switch file storage default to minio and align deploy workflow

This commit is contained in:
chengkai3
2026-05-01 21:49:45 +08:00
parent 64d07df734
commit 04524d57dc
3 changed files with 278 additions and 4 deletions
+260 -3
View File
@@ -104,6 +104,7 @@ jobs:
WEB_IMAGE: ${{ needs.build-and-push.outputs.web_image }}
IMAGE_TAG: ${{ needs.build-and-push.outputs.image_tag }}
NEXT_PUBLIC_API_BASE_URL: ${{ vars.NEXT_PUBLIC_API_BASE_URL || 'http://127.0.0.1:8000' }}
FLOWER_BASIC_AUTH: ${{ secrets.FLOWER_BASIC_AUTH || vars.FLOWER_BASIC_AUTH || 'admin:admin' }}
GHCR_USERNAME: ${{ github.actor }}
GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
@@ -114,7 +115,7 @@ jobs:
command_timeout: 45m
key: ${{ secrets.SERVER_SSH_KEY }}
password: ${{ secrets.SERVER_PASSWORD }}
envs: DEPLOY_PATH,API_IMAGE,WEB_IMAGE,IMAGE_TAG,NEXT_PUBLIC_API_BASE_URL,GHCR_USERNAME,GHCR_TOKEN
envs: DEPLOY_PATH,API_IMAGE,WEB_IMAGE,IMAGE_TAG,NEXT_PUBLIC_API_BASE_URL,FLOWER_BASIC_AUTH,GHCR_USERNAME,GHCR_TOKEN
script: |
set -euo pipefail
export DOCKER_CLIENT_TIMEOUT="${DOCKER_CLIENT_TIMEOUT:-600}"
@@ -145,20 +146,91 @@ jobs:
start_period: 10s
restart: unless-stopped
redis:
image: ${REDIS_IMAGE:-docker.m.daocloud.io/library/redis:7-alpine}
container_name: fquiz-redis
command: redis-server --appendonly yes
ports:
- "${REDIS_PORT:-6379}:6379"
volumes:
- fquiz_redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
start_period: 5s
restart: unless-stopped
minio:
image: ${MINIO_IMAGE:-minio/minio:latest}
container_name: fquiz-minio
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY:-minioadmin}
MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY:-minioadmin}
ports:
- "${MINIO_API_PORT:-9000}:9000"
- "${MINIO_CONSOLE_PORT:-9001}:9001"
volumes:
- fquiz_minio_data:/data
restart: unless-stopped
minio-init:
image: ${MINIO_MC_IMAGE:-minio/mc:latest}
container_name: fquiz-minio-init
depends_on:
minio:
condition: service_started
environment:
MINIO_ENDPOINT: ${MINIO_ENDPOINT:-http://minio:9000}
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:-minioadmin}
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:-minioadmin}
MINIO_BUCKET: ${MINIO_BUCKET:-fquiz-files}
entrypoint: /bin/sh
command:
- -c
- >
until mc alias set local "$MINIO_ENDPOINT" "$MINIO_ACCESS_KEY" "$MINIO_SECRET_KEY"; do
sleep 1;
done;
mc mb -p "local/$MINIO_BUCKET" || true;
restart: "no"
api:
image: ${API_IMAGE}
container_name: fquiz-api
depends_on:
db:
condition: service_healthy
minio:
condition: service_started
minio-init:
condition: service_completed_successfully
environment:
API_HOST: ${API_HOST:-0.0.0.0}
API_PORT: ${API_PORT:-8000}
API_CORS_ORIGINS: ${API_CORS_ORIGINS:-http://localhost:3000,http://127.0.0.1:3000}
API_CORS_ORIGIN_REGEX: ${API_CORS_ORIGIN_REGEX:-}
DATABASE_URL: ${DATABASE_URL:-postgresql+psycopg://fquiz:fquiz@db:5432/fquiz}
DB_HOST: ${DB_HOST:-db}
DB_PORT: ${DB_PORT:-5432}
DB_NAME: ${DB_NAME:-postgres}
DB_SCHEMA: ${DB_SCHEMA:-public}
DB_USERNAME: ${DB_USERNAME:-fquiz}
DB_PASSWORD: ${DB_PASSWORD:-fquiz}
USER_USERNAME_COLUMN: ${USER_USERNAME_COLUMN:-username}
USER_PASSWORD_COLUMN: ${USER_PASSWORD_COLUMN:-password_hash}
USER_STATUS_COLUMN: ${USER_STATUS_COLUMN:-status}
FILE_VFS_ROOT: ${FILE_VFS_ROOT:-./data/vfs}
MINIO_ENABLED: ${MINIO_ENABLED:-true}
MINIO_ENDPOINT: ${MINIO_ENDPOINT:-http://minio:9000}
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:-minioadmin}
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:-minioadmin}
MINIO_BUCKET: ${MINIO_BUCKET:-fquiz-files}
MINIO_REGION: ${MINIO_REGION:-us-east-1}
JWT_SECRET_KEY: ${JWT_SECRET_KEY:-change-this-in-production}
ACCESS_TOKEN_EXPIRE_MINUTES: ${ACCESS_TOKEN_EXPIRE_MINUTES:-15}
ACCESS_TOKEN_EXPIRE_MINUTES: ${ACCESS_TOKEN_EXPIRE_MINUTES:-480}
REFRESH_TOKEN_EXPIRE_DAYS: ${REFRESH_TOKEN_EXPIRE_DAYS:-30}
REFRESH_COOKIE_SECURE: ${REFRESH_COOKIE_SECURE:-false}
REFRESH_COOKIE_SAMESITE: ${REFRESH_COOKIE_SAMESITE:-lax}
@@ -166,6 +238,17 @@ jobs:
LLM_REQUEST_TIMEOUT_SECONDS: ${LLM_REQUEST_TIMEOUT_SECONDS:-60}
CHAT_CONTEXT_MESSAGE_LIMIT: ${CHAT_CONTEXT_MESSAGE_LIMIT:-12}
CHAT_DEFAULT_SYSTEM_PROMPT: ${CHAT_DEFAULT_SYSTEM_PROMPT:-You are a helpful assistant.}
CELERY_BROKER_URL: ${CELERY_BROKER_URL:-redis://redis:6379/0}
CELERY_RESULT_BACKEND: ${CELERY_RESULT_BACKEND:-redis://redis:6379/1}
CELERY_TIMEZONE: ${CELERY_TIMEZONE:-Asia/Shanghai}
SCHEDULER_EXPIRE_INTERVAL_SECONDS: ${SCHEDULER_EXPIRE_INTERVAL_SECONDS:-60}
SCHEDULER_API_BASE_URL: ${SCHEDULER_API_BASE_URL:-http://scheduler:19100}
SCHEDULER_API_TOKEN: ${SCHEDULER_API_TOKEN:-}
SCHEDULER_DEFAULT_QUEUE: ${SCHEDULER_DEFAULT_QUEUE:-default}
WINE_BINARY_PATH: ${WINE_BINARY_PATH:-wine}
WINE_ALLOWED_ROOT: ${WINE_ALLOWED_ROOT:-./data/wine}
WINE_DEFAULT_TIMEOUT_SECONDS: ${WINE_DEFAULT_TIMEOUT_SECONDS:-300}
WINE_MAX_TIMEOUT_SECONDS: ${WINE_MAX_TIMEOUT_SECONDS:-1800}
INITIAL_ADMIN_EMAIL: ${INITIAL_ADMIN_EMAIL:-admin@example.com}
INITIAL_ADMIN_USERNAME: ${INITIAL_ADMIN_USERNAME:-admin}
INITIAL_ADMIN_PASSWORD: ${INITIAL_ADMIN_PASSWORD:-change-me-strong-password}
@@ -179,6 +262,131 @@ jobs:
start_period: 10s
restart: unless-stopped
celery-worker:
image: ${API_IMAGE}
container_name: fquiz-celery-worker
command:
- celery
- -A
- app.core.celery_app.celery_app
- worker
- --loglevel=${CELERY_LOG_LEVEL:-INFO}
- --concurrency=${CELERY_WORKER_CONCURRENCY:-2}
- --queues=${CELERY_WORKER_QUEUES:-default,celery}
depends_on:
api:
condition: service_healthy
redis:
condition: service_healthy
environment:
DATABASE_URL: ${DATABASE_URL:-postgresql+psycopg://fquiz:fquiz@db:5432/fquiz}
DB_HOST: ${DB_HOST:-db}
DB_PORT: ${DB_PORT:-5432}
DB_NAME: ${DB_NAME:-postgres}
DB_SCHEMA: ${DB_SCHEMA:-public}
DB_USERNAME: ${DB_USERNAME:-fquiz}
DB_PASSWORD: ${DB_PASSWORD:-fquiz}
USER_USERNAME_COLUMN: ${USER_USERNAME_COLUMN:-username}
USER_PASSWORD_COLUMN: ${USER_PASSWORD_COLUMN:-password_hash}
USER_STATUS_COLUMN: ${USER_STATUS_COLUMN:-status}
CELERY_BROKER_URL: ${CELERY_BROKER_URL:-redis://redis:6379/0}
CELERY_RESULT_BACKEND: ${CELERY_RESULT_BACKEND:-redis://redis:6379/1}
CELERY_TIMEZONE: ${CELERY_TIMEZONE:-Asia/Shanghai}
SCHEDULER_EXPIRE_INTERVAL_SECONDS: ${SCHEDULER_EXPIRE_INTERVAL_SECONDS:-60}
SCHEDULER_API_BASE_URL: ${SCHEDULER_API_BASE_URL:-http://scheduler:19100}
SCHEDULER_API_TOKEN: ${SCHEDULER_API_TOKEN:-}
SCHEDULER_DEFAULT_QUEUE: ${SCHEDULER_DEFAULT_QUEUE:-default}
FLOWER_API_BASE_URL: ${FLOWER_API_BASE_URL:-http://flower:5555}
FLOWER_API_TIMEOUT_SECONDS: ${FLOWER_API_TIMEOUT_SECONDS:-10}
FLOWER_BASIC_AUTH: ${FLOWER_BASIC_AUTH:-admin:admin}
WORKER_REGISTRY_TTL_SECONDS: ${WORKER_REGISTRY_TTL_SECONDS:-90}
restart: unless-stopped
celery-beat:
image: ${API_IMAGE}
container_name: fquiz-celery-beat
command:
- celery
- -A
- app.core.celery_app.celery_app
- beat
- --loglevel=${CELERY_LOG_LEVEL:-INFO}
- --schedule=/tmp/celerybeat-schedule
depends_on:
api:
condition: service_healthy
redis:
condition: service_healthy
environment:
DATABASE_URL: ${DATABASE_URL:-postgresql+psycopg://fquiz:fquiz@db:5432/fquiz}
DB_HOST: ${DB_HOST:-db}
DB_PORT: ${DB_PORT:-5432}
DB_NAME: ${DB_NAME:-postgres}
DB_SCHEMA: ${DB_SCHEMA:-public}
DB_USERNAME: ${DB_USERNAME:-fquiz}
DB_PASSWORD: ${DB_PASSWORD:-fquiz}
USER_USERNAME_COLUMN: ${USER_USERNAME_COLUMN:-username}
USER_PASSWORD_COLUMN: ${USER_PASSWORD_COLUMN:-password_hash}
USER_STATUS_COLUMN: ${USER_STATUS_COLUMN:-status}
CELERY_BROKER_URL: ${CELERY_BROKER_URL:-redis://redis:6379/0}
CELERY_RESULT_BACKEND: ${CELERY_RESULT_BACKEND:-redis://redis:6379/1}
CELERY_TIMEZONE: ${CELERY_TIMEZONE:-Asia/Shanghai}
SCHEDULER_EXPIRE_INTERVAL_SECONDS: ${SCHEDULER_EXPIRE_INTERVAL_SECONDS:-60}
SCHEDULER_API_BASE_URL: ${SCHEDULER_API_BASE_URL:-http://scheduler:19100}
SCHEDULER_API_TOKEN: ${SCHEDULER_API_TOKEN:-}
SCHEDULER_DEFAULT_QUEUE: ${SCHEDULER_DEFAULT_QUEUE:-default}
WORKER_REGISTRY_TTL_SECONDS: ${WORKER_REGISTRY_TTL_SECONDS:-90}
restart: unless-stopped
scheduler:
image: ${API_IMAGE}
container_name: fquiz-scheduler
command:
- uvicorn
- app.scheduler_main:app
- --host
- 0.0.0.0
- --port
- "19100"
depends_on:
redis:
condition: service_healthy
api:
condition: service_healthy
environment:
CELERY_BROKER_URL: ${CELERY_BROKER_URL:-redis://redis:6379/0}
CELERY_RESULT_BACKEND: ${CELERY_RESULT_BACKEND:-redis://redis:6379/1}
CELERY_TIMEZONE: ${CELERY_TIMEZONE:-Asia/Shanghai}
SCHEDULER_API_TOKEN: ${SCHEDULER_API_TOKEN:-}
SCHEDULER_DEFAULT_QUEUE: ${SCHEDULER_DEFAULT_QUEUE:-default}
ports:
- "${SCHEDULER_PORT:-19100}:19100"
restart: unless-stopped
flower:
image: ${API_IMAGE}
container_name: fquiz-flower
command:
- celery
- -A
- app.core.celery_app.celery_app
- flower
- --address=0.0.0.0
- --port=5555
- --persistent=False
- --basic-auth=${FLOWER_BASIC_AUTH:-admin:admin}
depends_on:
redis:
condition: service_healthy
environment:
CELERY_BROKER_URL: ${CELERY_BROKER_URL:-redis://redis:6379/0}
CELERY_RESULT_BACKEND: ${CELERY_RESULT_BACKEND:-redis://redis:6379/1}
CELERY_TIMEZONE: ${CELERY_TIMEZONE:-Asia/Shanghai}
FLOWER_BASIC_AUTH: ${FLOWER_BASIC_AUTH:-admin:admin}
ports:
- "${FLOWER_PORT:-5555}:5555"
restart: unless-stopped
web:
image: ${WEB_IMAGE}
container_name: fquiz-web
@@ -194,6 +402,8 @@ jobs:
volumes:
fquiz_db_data:
fquiz_redis_data:
fquiz_minio_data:
YAML
if [ ! -f .env ]; then
@@ -204,8 +414,24 @@ jobs:
API_CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
API_CORS_ORIGIN_REGEX=
DATABASE_URL=postgresql+psycopg://fquiz:fquiz@db:5432/fquiz
DB_HOST=db
DB_PORT=5432
DB_NAME=postgres
DB_SCHEMA=public
DB_USERNAME=fquiz
DB_PASSWORD=fquiz
USER_USERNAME_COLUMN=username
USER_PASSWORD_COLUMN=password_hash
USER_STATUS_COLUMN=status
FILE_VFS_ROOT=./data/vfs
MINIO_ENABLED=true
MINIO_ENDPOINT=http://minio:9000
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin
MINIO_BUCKET=fquiz-files
MINIO_REGION=us-east-1
JWT_SECRET_KEY=change-this-in-production
ACCESS_TOKEN_EXPIRE_MINUTES=15
ACCESS_TOKEN_EXPIRE_MINUTES=480
REFRESH_TOKEN_EXPIRE_DAYS=30
REFRESH_COOKIE_SECURE=false
REFRESH_COOKIE_SAMESITE=lax
@@ -213,6 +439,26 @@ jobs:
LLM_REQUEST_TIMEOUT_SECONDS=60
CHAT_CONTEXT_MESSAGE_LIMIT=12
CHAT_DEFAULT_SYSTEM_PROMPT=You are a helpful assistant.
CELERY_BROKER_URL=redis://redis:6379/0
CELERY_RESULT_BACKEND=redis://redis:6379/1
CELERY_TIMEZONE=Asia/Shanghai
CELERY_LOG_LEVEL=INFO
CELERY_WORKER_CONCURRENCY=2
CELERY_WORKER_QUEUES=default,celery
SCHEDULER_EXPIRE_INTERVAL_SECONDS=60
SCHEDULER_API_BASE_URL=http://scheduler:19100
SCHEDULER_API_TOKEN=
SCHEDULER_DEFAULT_QUEUE=default
SCHEDULER_PORT=19100
FLOWER_API_BASE_URL=http://flower:5555
FLOWER_API_TIMEOUT_SECONDS=10
FLOWER_BASIC_AUTH=admin:admin
FLOWER_PORT=5555
WORKER_REGISTRY_TTL_SECONDS=90
WINE_BINARY_PATH=wine
WINE_ALLOWED_ROOT=./data/wine
WINE_DEFAULT_TIMEOUT_SECONDS=300
WINE_MAX_TIMEOUT_SECONDS=1800
INITIAL_ADMIN_EMAIL=admin@example.com
INITIAL_ADMIN_USERNAME=admin
INITIAL_ADMIN_PASSWORD=change-me-strong-password
@@ -221,6 +467,12 @@ jobs:
POSTGRES_PASSWORD=fquiz
POSTGRES_PORT=5434
POSTGRES_IMAGE=docker.m.daocloud.io/pgvector/pgvector:pg16
REDIS_IMAGE=docker.m.daocloud.io/library/redis:7-alpine
REDIS_PORT=6379
MINIO_IMAGE=minio/minio:latest
MINIO_MC_IMAGE=minio/mc:latest
MINIO_API_PORT=9000
MINIO_CONSOLE_PORT=9001
ENV
echo "[warn] .env 不存在,已写入默认模板,请尽快改成生产配置。"
fi
@@ -262,6 +514,11 @@ jobs:
${COMPOSE_CMD} --env-file .env --env-file .images.env -f docker-compose.prod.yml ps || true
docker logs --tail 300 fquiz-api || true
docker logs --tail 200 fquiz-db || true
docker logs --tail 200 fquiz-redis || true
docker logs --tail 200 fquiz-celery-worker || true
docker logs --tail 200 fquiz-celery-beat || true
docker logs --tail 200 fquiz-scheduler || true
docker logs --tail 200 fquiz-flower || true
exit 1
fi
${COMPOSE_CMD} --env-file .env --env-file .images.env -f docker-compose.prod.yml ps
+1 -1
View File
@@ -27,7 +27,7 @@ class Settings(BaseSettings):
user_password_column: Literal["password", "password_hash"] = "password_hash"
user_status_column: Literal["status", "state"] = "status"
file_vfs_root: str = "./data/vfs"
minio_enabled: bool = False
minio_enabled: bool = True
minio_endpoint: str = "http://minio:9000"
minio_access_key: str = "minioadmin"
minio_secret_key: str = "minioadmin"
+17
View File
@@ -747,3 +747,20 @@
- Flower 代理链路新增了对 `FLOWER_BASIC_AUTH``FLOWER_API_BASE_URL` 的配置依赖,配置不一致会导致监控页 502。
- `task-monitor` 改为按 worker 聚合调用,worker 数量多时会增加 API 聚合开销。
- `dispatchMode=scheduler_api` 需确保 `scheduler` 服务可达;默认 `celery_direct` 兼容旧行为。
## Work Log - 文件管理默认后端切换为 MinIO2026-05-01
- 背景:
- 需求要求“文件管理改成 MinIO”。
- 运行层(`docker-compose.yml` / `.env.example` / workflow 部署模板)已是 `MINIO_ENABLED=true`,但后端配置默认值仍为 `false`,在未显式配置环境变量时会回退为 VFS。
- 本次改动(最小改动):
- 文件:`api/app/core/config.py`
-`minio_enabled` 默认值从 `False` 调整为 `True`
- 影响:
- 在无环境变量覆盖时,`seed_service` 的默认存储后端与挂载将落到 `files.s3.default`MinIO)而非 `files.vfs.default`
- 若环境显式设置 `MINIO_ENABLED=false`,仍可回退 VFS。
- 验证:
- 代码走读确认 `seed_service._default_file_storage_backends/_default_file_storage_mounts` 基于 `settings.minio_enabled` 分支,默认行为已切换到 MinIO。