feat(deploy): add compose deployment

This commit is contained in:
2026-05-04 23:16:08 +08:00
parent f939a50950
commit 817540f8a0
12 changed files with 451 additions and 106 deletions
+47
View File
@@ -0,0 +1,47 @@
.git
.gitignore
.dockerignore
.venv/
venv/
env/
ENV/
__pycache__/
*.py[cod]
*.egg-info/
.pytest_cache/
.ruff_cache/
.mypy_cache/
apps/frontend/node_modules/
apps/frontend/dist/
apps/frontend/.vite/
data/
sessions/
logs/
*.log
*.pid
*.lock
!uv.lock
.env
.env.*
!.env.example
!deploy/compose.env.example
debug_page_source.html
debug_screenshot.png
chrome-linux64/
chrome-win64/
chromedriver
chromedriver.exe
.playwright-cli/
output/
.DS_Store
Thumbs.db
.claude/
.codex/
openspec/
+6 -3
View File
@@ -20,7 +20,8 @@ debug_page_source.html
debug_screenshot.png
# 运行时文件
sessions/
sessions/*
!sessions/.gitkeep
*.lock
!/uv.lock
*.log
@@ -29,14 +30,16 @@ backend.pid
frontend.pid
# 数据库
data/
data/*
!data/.gitkeep
# 配置文件
.env
config.ini
# 日志
logs/
logs/*
!logs/.gitkeep
# IDE
.vscode/
+13 -1
View File
@@ -49,6 +49,18 @@ pnpm dev
uv run python apps/backend/scripts/create_admin.py
```
### Docker Compose 部署
生产环境推荐使用 Docker Compose。主机只需要 Docker/Compose,不需要单独安装 Python、Node.js、pnpm 或 Chromium。
```bash
cp deploy/compose.env.example .env
# 编辑 .env,至少修改 SECRET_KEY、CORS_ORIGINS、FRONTEND_URL
docker compose up -d --build
```
默认访问地址:<http://localhost:8080>
### 访问地址
- 前端: <http://localhost:3000>
@@ -68,7 +80,7 @@ python main.py frontend-build
复制 `.env.example``.env`
nginx 与 systemd 的配置文件参考已给出,见 `.example`
Docker Compose 环境变量参考 `deploy/compose.env.example`nginx 与 systemd 的传统部署配置文件参考已给出,见 `.example`
## 文档
+49
View File
@@ -0,0 +1,49 @@
services:
backend:
build:
context: .
dockerfile: deploy/docker/backend/Dockerfile
image: checkin-app-backend:local
container_name: checkin-backend
env_file:
- path: .env
required: false
environment:
DATABASE_URL: ${DATABASE_URL:-sqlite:////app/data/checkin.db}
LOG_FILE: ${LOG_FILE:-/app/logs/backend.log}
SESSION_DIR: ${SESSION_DIR:-/app/sessions}
BROWSER_EXECUTABLE_PATH: ${BROWSER_EXECUTABLE_PATH:-}
volumes:
- ./data:/app/data
- ./sessions:/app/sessions
- ./logs:/app/logs
restart: unless-stopped
healthcheck:
test:
[
"CMD-SHELL",
"python -c \"import json, urllib.request; r=urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=5); raise SystemExit(0 if json.load(r).get('status') == 'healthy' else 1)\"",
]
interval: 30s
timeout: 10s
retries: 5
start_period: 40s
web:
build:
context: .
dockerfile: deploy/docker/web/Dockerfile
image: checkin-app-web:local
container_name: checkin-web
depends_on:
backend:
condition: service_healthy
ports:
- "${CHECKIN_WEB_PORT:-8080}:80"
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1/health >/dev/null"]
interval: 30s
timeout: 10s
retries: 5
start_period: 10s
+1
View File
@@ -0,0 +1 @@
+38
View File
@@ -0,0 +1,38 @@
# Copy this file to .env before running Docker Compose:
# cp deploy/compose.env.example .env
# Public web port exposed by the web service.
CHECKIN_WEB_PORT=8080
# ==================== Backend runtime paths ====================
# Container-local defaults for the Compose deployment.
DATABASE_URL=sqlite:////app/data/checkin.db
LOG_FILE=/app/logs/backend.log
SESSION_DIR=/app/sessions
# ==================== Security and public URLs ====================
# Change this before production use.
SECRET_KEY=change-this-to-a-long-random-secret
# Use the browser origins that will open the frontend.
# Public domain + optional intranet origins.
CORS_ORIGINS=https://checkin.example.com,http://192.168.1.10:8080,http://checkin.lan:8080
FRONTEND_URL=https://checkin.example.com
# ==================== Logging ====================
LOG_LEVEL=INFO
# ==================== Mail settings ====================
SMTP_SERVER=smtp.example.com
SMTP_PORT=465
SMTP_SENDER_EMAIL=your-email@example.com
SMTP_SENDER_PASSWORD=your-auth-code-here
SMTP_USE_SSL=True
# ==================== Playwright browser settings ====================
# Leave empty to use the Chromium installed in the backend image.
BROWSER_EXECUTABLE_PATH=
# ==================== Scheduler settings ====================
TOKEN_CHECK_INTERVAL_MINUTES=30
SESSION_CLEANUP_INTERVAL_HOURS=24
+23
View File
@@ -0,0 +1,23 @@
FROM python:3.14-slim AS runtime
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PYTHONPATH=/app/apps \
PLAYWRIGHT_BROWSERS_PATH=/ms-playwright \
PATH="/app/.venv/bin:${PATH}"
WORKDIR /app
RUN python -m pip install --no-cache-dir --upgrade pip uv
COPY pyproject.toml uv.lock README.md ./
COPY main.py ./main.py
COPY apps ./apps
RUN uv sync --frozen --no-dev --extra production \
&& playwright install --with-deps chromium \
&& mkdir -p /app/data /app/sessions /app/logs
EXPOSE 8000
CMD ["python", "main.py", "backend", "--host", "0.0.0.0", "--port", "8000", "--no-reload", "--log-level", "info"]
+18
View File
@@ -0,0 +1,18 @@
FROM node:24-trixie-slim AS frontend-build
WORKDIR /app/apps/frontend
RUN corepack enable
COPY apps/frontend/package.json apps/frontend/pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY apps/frontend/ ./
RUN pnpm build
FROM nginx:1.27-alpine AS runtime
COPY deploy/docker/web/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=frontend-build /app/apps/frontend/dist /usr/share/nginx/html
EXPOSE 80
+47
View File
@@ -0,0 +1,47 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
client_max_body_size 10M;
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript application/xml image/svg+xml;
location /api/ {
proxy_pass http://backend:8000/api/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_buffering off;
proxy_request_buffering off;
}
location ~ ^/(docs|redoc|openapi.json|health)$ {
proxy_pass http://backend:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
}
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
location / {
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
}
+207 -102
View File
@@ -1,6 +1,161 @@
# 部署指南
## 环境准备
## 推荐方式:Docker Compose
Docker Compose 是当前推荐的生产部署方式。主机只需要 Docker Engine 和 Docker ComposePython、Node.js、pnpm、Playwright Chromium 都由镜像构建和容器运行时负责。
### 系统要求
- Linux 服务器,建议 Ubuntu 20.04+ / Debian 11+ / CentOS 7+
- Docker Engine 24+
- Docker Compose v2
- 2GB+ RAM
- 可访问外网以构建镜像和下载依赖
### 首次部署
```bash
git clone <repository>
cd CheckInApp
cp deploy/compose.env.example .env
```
编辑 `.env`,至少修改:
- `SECRET_KEY`: 改成足够长的随机字符串。
- `CORS_ORIGINS`: 改成浏览器实际访问的站点,例如 `https://checkin.example.com`
- `FRONTEND_URL`: 改成同一个公开站点,用于邮件里的链接。
- `CHECKIN_WEB_PORT`: 默认 `8080`,按服务器端口规划调整。
- SMTP 配置:需要邮件通知时填写真实 SMTP 参数。
启动:
```bash
docker compose up -d --build
```
默认访问:
- Web UI: `http://localhost:8080`
- API 健康检查: `http://localhost:8080/health`
- API 文档: `http://localhost:8080/docs`
### 运行结构
Compose 启动两个服务:
- `backend`: FastAPI、APScheduler、SQLAlchemy、Playwright Chromium。
- `web`: nginx,负责前端静态资源、SPA fallback,以及代理 `/api``/docs``/redoc``/openapi.json``/health` 到 backend。
持久化目录:
- `./data:/app/data`: SQLite 数据库,默认 `data/checkin.db`
- `./sessions:/app/sessions`: Playwright 登录会话状态。
- `./logs:/app/logs`: 后端日志文件。
容器重建或镜像更新不会删除这些目录。
### 创建管理员
先让目标用户完成一次 QQ 扫码注册,然后在容器内运行管理员脚本:
```bash
docker compose exec backend uv run python apps/backend/scripts/create_admin.py
```
脚本会提示输入要升级的用户别名。
### 常用运维命令
```bash
# 查看服务状态
docker compose ps
# 查看日志
docker compose logs -f backend
docker compose logs -f web
# 重启
docker compose restart
# 停止
docker compose down
```
### 更新
```bash
git pull
docker compose up -d --build
```
不要使用 `docker compose down -v`,否则会删除 Compose 管理的卷。当前默认使用项目目录 bind mount,仍应避免误删 `data/``sessions/``logs/`
### 备份与恢复
备份 SQLite 和运行时状态:
```bash
mkdir -p backups
tar -czf backups/checkin-$(date +%Y%m%d-%H%M%S).tar.gz data sessions logs .env
```
恢复:
```bash
docker compose down
tar -xzf backups/<backup-file>.tar.gz
docker compose up -d
```
### 回滚
```bash
git checkout <previous-tag-or-commit>
docker compose up -d --build
```
回滚时复用同一份 `.env``data/``sessions/``logs/`。如果未来版本引入数据库迁移,按对应版本说明先确认迁移是否可逆。
### Compose 故障排查
端口占用:
```bash
sudo lsof -i :8080
```
修改 `.env` 中的 `CHECKIN_WEB_PORT` 后重新启动:
```bash
docker compose up -d
```
后端健康检查失败:
```bash
docker compose logs backend
docker compose exec backend uv run python main.py backend --check
```
Playwright 问题:
- Compose 部署不需要在主机安装 Chrome/Chromium。
- 如果 QQ 登录或 payload 捕获失败,先查看 `docker compose logs backend``logs/backend.log`
- 重新构建镜像可刷新 Playwright 浏览器依赖:`docker compose build --no-cache backend`
权限问题:
```bash
mkdir -p data sessions logs
chmod -R u+rwX data sessions logs
docker compose restart backend
```
## 备选方式:传统部署
传统部署适合已经有主机级 Python、Node.js、pnpm、Chromium、nginx 和 systemd 管理经验的环境。
### 系统要求
@@ -27,137 +182,127 @@ npm install -g pnpm
curl -LsSf https://astral.sh/uv/install.sh | sh
```
## 生产部署
### 方式一:传统部署
#### 1. 后端部署
### 后端部署
```bash
# 克隆项目
git clone <repository>
cd CheckInApp
# 安装依赖
uv sync
# 生产环境额外依赖
uv sync --extra production
# 配置环境变量
cp .env.example .env
vim .env # 修改环境变量
vim .env
uv run playwright install chromium
uv run python main.py backend --no-reload
```
#### 2. 前端部署
### 前端部署
```bash
cd apps/frontend
# 安装依赖
pnpm install --frozen-lockfile
# 构建生产版本
pnpm build
# 输出在 dist/ 目录
```
**使用 Nginx 托管**:
生产静态资源输出在 `apps/frontend/dist/`
[示例文件](../deploy/nginx/checkin-app.conf.example)
nginx 示例文件:[deploy/nginx/checkin-app.conf.example](../deploy/nginx/checkin-app.conf.example)
#### 3. 使用 Systemd 管理
[示例文件](../deploy/systemd/checkin-app.service.example)
### 方式二:Docker 部署(推荐)
TODO(Maybe never)
systemd 示例文件:[deploy/systemd/checkin-app.service.example](../deploy/systemd/checkin-app.service.example)
## 配置优化
### 生产环境变量
[示例文件](../.env.example)
Compose 部署参考:[deploy/compose.env.example](../deploy/compose.env.example)
传统部署参考:[.env.example](../.env.example)
### 数据库迁移到 PostgreSQL
当前 Compose baseline 使用 SQLite 单后端部署。需要 PostgreSQL 时,先按传统方式准备数据库并修改 `DATABASE_URL`
```bash
# 安装 PostgreSQL
sudo apt install postgresql postgresql-contrib
# 创建数据库
sudo -u postgres createdb checkin
sudo -u postgres createuser checkin_user
sudo -u postgres psql -c "ALTER USER checkin_user WITH PASSWORD 'password';"
# 修改 .env
DATABASE_URL=postgresql://checkin_user:password@localhost/checkin
```
## 安全加固
### 1. 防火墙配置
### 防火墙配置
```bash
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
```
### 2. SSL 证书(Let's Encrypt
如果直接暴露 Compose web 端口,也需要允许 `CHECKIN_WEB_PORT` 对应端口。
### HTTPS
推荐在 Compose web 服务前放置外部反向代理或云厂商负载均衡来终止 TLS。传统 nginx 部署可以使用 Let's Encrypt
```bash
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d your-domain.com
# 自动续期
sudo systemctl enable certbot.timer
```
### 3. 限制访问
### 访问限制
- 修改 `.env` 中的 `CORS_ORIGINS` 为实际域名
- 在 Nginx 中配置 rate limiting
- 使用 fail2ban 防止暴力破解
- 生产环境必须修改默认 `SECRET_KEY`
- `CORS_ORIGINS``FRONTEND_URL` 设置为实际公开域名。
- 在外部反向代理中配置 rate limiting。
- 使用 fail2ban 防止暴力破解。
## 监控维护
### 日志管理
### 日志
Compose
```bash
docker compose logs -f backend
tail -f logs/backend.log
```
传统部署:
```bash
# 查看后端日志
tail -f logs/backend.log
```
### 数据库备份
SQLite
```bash
# SQLite 备份
cp data/checkin.db data/checkin.db.backup
# PostgreSQL 备份
pg_dump checkin > backup.sql
# 定时备份(crontab
0 2 * * * /path/to/backup.sh
```
### 性能监控
PostgreSQL
使用工具:
- Prometheus + Grafana
- New Relic
- Sentry(错误追踪)
```bash
pg_dump checkin > backup.sql
```
## 扩展部署
### 负载均衡
当前应用包含内置调度器,默认 Compose baseline 只运行一个 backend 服务。多 backend 实例需要先设计调度器互斥或外部调度机制。
传统 nginx upstream 示例:
```nginx
upstream backend {
server 127.0.0.1:8000;
@@ -174,50 +319,10 @@ server {
### Redis 缓存
```python
# 安装 redis
```bash
uv sync --extra redis
```
# 配置会话存储
```env
REDIS_URL=redis://localhost:6379/0
```
## 故障排查
### 端口占用
```bash
sudo lsof -i :8000
sudo kill -9 <PID>
```
### Playwright 问题
```bash
# 安装浏览器
uv run playwright install chromium
# 检查系统浏览器(可选)
chromium --version
google-chrome --version
```
### 权限问题
```bash
# 确保目录权限正确
sudo chown -R www-data:www-data /path/to/CheckInApp
sudo chmod -R 755 /path/to/CheckInApp
```
## 回滚策略
```bash
# 保存当前版本
git tag -a v2.0.0 -m "Production release"
# 回滚到上一版本
git checkout v1.9.0
docker-compose down
docker-compose up -d --build
```
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@