Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# IDE
.idea/
.vscode/
*.swp
*.swo

# Git
.git/
.gitignore
.gitattributes

# Node
web/node_modules/
web/dist/
web/electron/
web/.nuxt/
web/.output/

# Python
app/__pycache__/
app/src/__pycache__/
app/.venv/
app/.uv/
**/*.pyc
**/*.pyo
**/__pycache__/
**/.pytest_cache/
**/.ruff_cache/

# Docker
docker/data/
docker/postgresql-16/data/
docker/redis/data/
docker/minio/data/

# Docs
docs/
/*.md
*.drawio

# Tests
app/tests/
web/src/**/*.test.ts
web/src/**/*.spec.ts

# Misc
.claude/
.superpowers/
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ app/artifacts/
.playwright*
.snow/

/node_modules/
/node_modules/docker/.env.production
113 changes: 113 additions & 0 deletions deploy.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#!/bin/bash
# FileFlash 一键部署脚本
# 用法: 将整个项目上传到服务器后,在项目根目录执行 bash deploy.sh
# bash deploy.sh --fresh # 强制全量重建镜像(首次部署或大版本更新)

set -e

echo "========================================"
echo " FileFlash 生产环境部署"
echo "========================================"

RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'

# ---------- 1. 检查环境 ----------
echo -e "\n${YELLOW}[1/5] 检查环境...${NC}"

if ! command -v docker &>/dev/null; then
echo -e "${RED}未安装 Docker,请先安装${NC}"
exit 1
fi

if ! docker compose version &>/dev/null; then
echo -e "${RED}需要 Docker Compose v2+${NC}"
exit 1
fi

# ---------- 2. 生成密钥 ----------
echo -e "\n${YELLOW}[2/5] 生成安全密钥...${NC}"

JWT_SECRET=$(openssl rand -hex 32 2>/dev/null || python3 -c "import secrets;print(secrets.token_hex(32))" 2>/dev/null)
ADMIN_PASSWORD=$(openssl rand -hex 32 2>/dev/null || python3 -c "import secrets;print(secrets.token_hex(32))" 2>/dev/null)
DB_PASSWORD=$(openssl rand -hex 16 2>/dev/null || python3 -c "import secrets;print(secrets.token_hex(16))" 2>/dev/null)
MINIO_PASSWORD=$(openssl rand -hex 16 2>/dev/null || python3 -c "import secrets;print(secrets.token_hex(16))" 2>/dev/null)

if [ -z "$JWT_SECRET" ] || [ -z "$ADMIN_PASSWORD" ] || [ -z "$DB_PASSWORD" ] || [ -z "$MINIO_PASSWORD" ]; then
echo -e "${RED}无法生成密钥,请手动修改 docker/.env.production${NC}"
exit 1
fi

# 替换 .env.production
sed -i "s|^JWT_SECRET_KEY=.*|JWT_SECRET_KEY=$JWT_SECRET|" docker/.env.production
sed -i "s|^DEFAULT_ADMIN_PASSWORD=.*|DEFAULT_ADMIN_PASSWORD=$ADMIN_PASSWORD|" docker/.env.production
sed -i "s|^OBJECT_STORAGE_SECRET_KEY=.*|OBJECT_STORAGE_SECRET_KEY=$MINIO_PASSWORD|" docker/.env.production
sed -i "s|:psgl-ff-db@|:$DB_PASSWORD@|" docker/.env.production

# 替换 docker-compose.prod.yml
sed -i "s|POSTGRES_PASSWORD: psgl-ff-db|POSTGRES_PASSWORD: $DB_PASSWORD|" docker/docker-compose.prod.yml
sed -i "s|MINIO_ROOT_PASSWORD: minio-admin|MINIO_ROOT_PASSWORD: $MINIO_PASSWORD|" docker/docker-compose.prod.yml
sed -i "s|-password=psgl-ff-db|-password=$DB_PASSWORD|" docker/docker-compose.prod.yml

echo -e "${GREEN}JWT_SECRET_KEY 已生成${NC}"
echo -e "${GREEN}DEFAULT_ADMIN_PASSWORD 已生成${NC}"
echo -e "${GREEN}数据库密码 已生成${NC}"
echo -e "${GREEN}MinIO 密码 已生成${NC}"

# ---------- 3. 构建镜像 ----------
echo -e "\n${YELLOW}[3/5] 构建 Docker 镜像...${NC}"

BUILD_FLAGS=""
if [ "$1" = "--fresh" ]; then
BUILD_FLAGS="--no-cache"
echo -e "${YELLOW} 首次部署模式: 强制全量构建${NC}"
else
echo " 增量构建 (使用层缓存,加 --fresh 可强制全量)"
fi

docker compose -f docker/docker-compose.prod.yml build $BUILD_FLAGS

echo -e "${GREEN}镜像构建完成${NC}"

# ---------- 4. 启动服务 ----------
echo -e "\n${YELLOW}[4/5] 启动服务...${NC}"

docker compose -f docker/docker-compose.prod.yml up -d

# ---------- 5. 等待就绪 ----------
echo -e "\n${YELLOW}[5/5] 等待服务就绪...${NC}"

echo -n "等待服务启动"
for i in $(seq 1 60); do
if curl -s -o /dev/null http://localhost/health 2>/dev/null; then
echo ""
echo -e "${GREEN}服务已就绪${NC}"
break
fi
echo -n "."
sleep 3
done

echo ""
echo "========================================"
echo -e "${GREEN} 部署完成!${NC}"
echo "========================================"
echo ""
echo "管理员账号:"
echo " 用户名: administrator"
echo " 密码: $ADMIN_PASSWORD"
echo ""
echo "数据库密码: $DB_PASSWORD"
echo "MinIO 密码: $MINIO_PASSWORD"
echo ""
echo "访问 http://$(hostname -I | awk '{print $1}')"
echo ""
echo "常用命令:"
echo " docker compose -f docker/docker-compose.prod.yml logs -f # 查看日志"
echo " docker compose -f docker/docker-compose.prod.yml down # 停止"
echo " docker compose -f docker/docker-compose.prod.yml restart # 重启"
echo " docker compose -f docker/docker-compose.prod.yml ps # 状态"
echo ""
echo "如需开启 AI Agent:"
echo " 1. 修改 docker/.env.production 中 AGENT_ENABLED=true"
echo " 2. 配置 AGENT_LLM_BASE_URL 和 AGENT_LLM_API_KEY"
echo " 3. docker compose -f docker/docker-compose.prod.yml --profile agent up -d"
29 changes: 29 additions & 0 deletions docker/Dockerfile.backend
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
FROM docker.m.daocloud.io/library/python:3.12-slim

WORKDIR /app

# 换成阿里云 debian 镜像源
RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources

# 再安装 ffmpeg
RUN apt-get update && apt-get install -y --no-install-recommends ffmpeg && rm -rf /var/lib/apt/lists/*

# pip 走阿里云
RUN pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/
RUN pip install --no-cache-dir uv

# uv 走阿里云
ENV UV_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/

# Layer 1: 仅依赖(除非 pyproject.toml / uv.lock 变动,否则缓存命中)
COPY app/pyproject.toml app/uv.lock ./
RUN uv sync --no-dev --frozen --no-install-project

# Layer 2: 源码 + 安装项目本身
COPY app/src ./src
COPY app/README.md ./README.md
RUN uv sync --no-dev --frozen

EXPOSE 8080

CMD ["uv", "run", "uvicorn", "fileflash.main:app", "--host", "0.0.0.0", "--port", "8080"]
35 changes: 35 additions & 0 deletions docker/Dockerfile.frontend
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
FROM docker.m.daocloud.io/library/node:22-alpine AS builder

WORKDIR /build

RUN npm config set registry https://registry.npmmirror.com
ENV BUN_CONFIG_REGISTRY=https://registry.npmmirror.com

RUN npm install -g bun

COPY web/package.json web/bun.lock* ./

# Docker 里只需要打包 Vue,不需要 Electron 二进制
ENV ELECTRON_SKIP_BINARY_DOWNLOAD=1

RUN bun install --frozen-lockfile
COPY web ./

ARG VITE_BASE_URL=/api/v1
ENV VITE_BASE_URL=$VITE_BASE_URL

ARG VITE_ENABLE_MOCKS=false
ENV VITE_ENABLE_MOCKS=$VITE_ENABLE_MOCKS

# 跳过 vue-tsc 类型检查(CI 已做),仅生产构建
RUN bun x vite build

# Stage 2: Serve static files with nginx
FROM docker.m.daocloud.io/library/nginx:1.27-alpine

COPY --from=builder /build/dist /usr/share/nginx/html
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
159 changes: 159 additions & 0 deletions docker/docker-compose.prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
services:
# ========== PostgreSQL + Flyway 迁移 ==========
postgres:
image: docker.m.daocloud.io/library/postgres:16
container_name: ff-postgres
restart: always
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD: psgl-ff-db
POSTGRES_DB: fileflash
TZ: Asia/Shanghai
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U admin -d fileflash"]
interval: 10s
timeout: 5s
retries: 5
networks:
- fileflash

flyway:
image: docker.m.daocloud.io/flyway/flyway:latest
container_name: ff-flyway
command: >
-url=jdbc:postgresql://postgres:5432/fileflash
-user=admin
-password=psgl-ff-db
-connectRetries=15
migrate
volumes:
- ./flyway/migrations:/flyway/sql
depends_on:
postgres:
condition: service_healthy
networks:
- fileflash

# ========== Redis ==========
redis:
image: docker.m.daocloud.io/library/redis:7-alpine
container_name: ff-redis
restart: always
command: redis-server --appendonly yes
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- fileflash

# ========== MinIO 对象存储 ==========
minio:
image: quay.io/minio/minio:latest
container_name: ff-minio
restart: always
environment:
MINIO_ROOT_USER: admin
MINIO_ROOT_PASSWORD: minio-admin
volumes:
- minio_data:/data
command: server /data
healthcheck:
test: ["CMD", "curl" ,"-f","http://localhost:9000/minio/health/live"]
interval: 30s
timeout: 20s
retries: 3
networks:
- fileflash

# ========== FastAPI 后端 ==========
backend:
build:
context: ..
dockerfile: docker/Dockerfile.backend
container_name: ff-backend
restart: always
env_file:
- .env.production
depends_on:
flyway:
condition: service_completed_successfully
redis:
condition: service_healthy
minio:
condition: service_healthy
networks:
- fileflash

# ========== Worker 后台任务(转码/扫描等) ==========
worker:
build:
context: ..
dockerfile: docker/Dockerfile.backend
container_name: ff-worker
restart: always
env_file:
- .env.production
command: ["uv", "run", "python", "-m", "fileflash.workers.consumer"]
depends_on:
flyway:
condition: service_completed_successfully
redis:
condition: service_healthy
minio:
condition: service_healthy
networks:
- fileflash

# ========== Agent Worker (开启 AGENT_ENABLED=true 时需要) ==========
agent-worker:
build:
context: ..
dockerfile: docker/Dockerfile.backend
container_name: ff-agent-worker
restart: always
env_file:
- .env.production
command: ["uv", "run", "python", "-m", "fileflash.agents.worker"]
depends_on:
flyway:
condition: service_completed_successfully
redis:
condition: service_healthy
minio:
condition: service_healthy
networks:
- fileflash
profiles:
- agent

# ========== Vue 前端 + nginx ==========
frontend:
build:
context: ..
dockerfile: docker/Dockerfile.frontend
args:
VITE_BASE_URL: /api/v1
VITE_ENABLE_MOCKS: 'false'
container_name: ff-frontend
restart: always
ports:
- "80:80"
depends_on:
- backend
networks:
- fileflash

volumes:
postgres_data:
redis_data:
minio_data:

networks:
fileflash:
driver: bridge
Loading
Loading