Build lightweight AI agent admin
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
FROM python:3.12-slim AS builder
|
||||
|
||||
RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources 2>/dev/null || \
|
||||
sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list && \
|
||||
sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list 2>/dev/null; \
|
||||
true
|
||||
|
||||
RUN pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
gcc \
|
||||
libpq-dev \
|
||||
default-libmysqlclient-dev \
|
||||
unixodbc-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY backend-fastapi/requirements.txt .
|
||||
RUN pip install --no-cache-dir --user -r requirements.txt
|
||||
|
||||
FROM python:3.12-slim
|
||||
|
||||
RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources 2>/dev/null || \
|
||||
sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list && \
|
||||
sed -i 's/security.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list 2>/dev/null; \
|
||||
true
|
||||
|
||||
RUN pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
gnupg \
|
||||
ca-certificates \
|
||||
&& curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /usr/share/keyrings/microsoft-prod.gpg \
|
||||
&& curl -fsSL https://packages.microsoft.com/config/debian/12/prod.list -o /etc/apt/sources.list.d/mssql-release.list \
|
||||
&& apt-get update && ACCEPT_EULA=Y apt-get install -y --no-install-recommends \
|
||||
libpq-dev \
|
||||
default-libmysqlclient-dev \
|
||||
unixodbc \
|
||||
msodbcsql18 \
|
||||
libpango-1.0-0 \
|
||||
libpangocairo-1.0-0 \
|
||||
libgdk-pixbuf-2.0-0 \
|
||||
libffi-dev \
|
||||
libcairo2 \
|
||||
shared-mime-info \
|
||||
fonts-noto-cjk \
|
||||
fonts-wqy-zenhei \
|
||||
fonts-wqy-microhei \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& fc-cache -fv
|
||||
|
||||
COPY --from=builder /root/.local /root/.local
|
||||
|
||||
ENV PATH=/root/.local/bin:$PATH \
|
||||
PYTHONPATH=/app
|
||||
|
||||
COPY backend-fastapi/ .
|
||||
|
||||
COPY docker/backend/entrypoint.sh /entrypoint.sh
|
||||
COPY docker/backend/wait-for-it.sh /wait-for-it.sh
|
||||
RUN chmod +x /entrypoint.sh /wait-for-it.sh
|
||||
|
||||
RUN mkdir -p /app/env && echo "" > /app/env/prod.env
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
@@ -0,0 +1,61 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "=========================================="
|
||||
echo " ZQ Platform Backend - Docker Entrypoint"
|
||||
echo "=========================================="
|
||||
|
||||
echo "[1/4] 等待数据库连接..."
|
||||
if [ -n "$DB_HOST" ] && [ -n "$DB_PORT" ]; then
|
||||
/wait-for-it.sh "$DB_HOST" "$DB_PORT" 60
|
||||
fi
|
||||
|
||||
echo "[2/4] 等待 Redis 连接..."
|
||||
if [ -n "$REDIS_HOST" ] && [ -n "$REDIS_PORT" ]; then
|
||||
/wait-for-it.sh "$REDIS_HOST" "$REDIS_PORT" 30
|
||||
fi
|
||||
|
||||
echo "[3/4] 数据库初始化..."
|
||||
|
||||
MIGRATION_COUNT=$(ls alembic/versions/*.py 2>/dev/null | wc -l)
|
||||
|
||||
if [ "$MIGRATION_COUNT" -eq "0" ]; then
|
||||
echo " 未发现迁移文件,自动生成..."
|
||||
python -m alembic revision --autogenerate -m "initial" 2>&1 | tail -3
|
||||
fi
|
||||
|
||||
python -m alembic upgrade head 2>&1 || {
|
||||
echo " [WARN] 迁移失败,5秒后重试..."
|
||||
sleep 5
|
||||
python -m alembic upgrade head 2>&1 || echo " [WARN] 迁移再次失败,跳过"
|
||||
}
|
||||
|
||||
USER_COUNT=$(python -c "
|
||||
import asyncio
|
||||
from app.database import AsyncSessionLocal
|
||||
from sqlalchemy import text
|
||||
async def check():
|
||||
try:
|
||||
async with AsyncSessionLocal() as db:
|
||||
result = await db.execute(text('SELECT COUNT(*) FROM core_user'))
|
||||
print(result.scalar())
|
||||
except Exception:
|
||||
print(0)
|
||||
asyncio.run(check())
|
||||
" 2>/dev/null || echo "0")
|
||||
|
||||
if [ "$USER_COUNT" -eq "0" ] 2>/dev/null; then
|
||||
if [ -f "db_init.json" ]; then
|
||||
echo " 首次部署,导入初始化数据..."
|
||||
python scripts/loaddata.py db_init.json 2>&1
|
||||
else
|
||||
echo " [WARN] 未找到 db_init.json,跳过数据导入"
|
||||
fi
|
||||
else
|
||||
echo " 数据库已有数据 ($USER_COUNT 个用户),跳过导入"
|
||||
fi
|
||||
|
||||
echo " 数据库就绪"
|
||||
|
||||
echo "[4/4] 启动 FastAPI..."
|
||||
exec python -m uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4
|
||||
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
host="$1"
|
||||
port="$2"
|
||||
timeout="${3:-60}"
|
||||
|
||||
if [ -z "$host" ] || [ -z "$port" ]; then
|
||||
echo "用法: $0 <host> <port> [timeout]"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "等待 $host:$port (超时: ${timeout}s)..."
|
||||
|
||||
end_time=$(($(date +%s) + timeout))
|
||||
while [ $(date +%s) -lt $end_time ]; do
|
||||
if timeout 1 bash -c "echo > /dev/tcp/${host}/${port}" 2>/dev/null; then
|
||||
echo "$host:$port 已可用"
|
||||
exit 0
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "错误: $host:$port 在 ${timeout} 秒内未能连接"
|
||||
exit 1
|
||||
@@ -0,0 +1,61 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
gzip on;
|
||||
gzip_min_length 1k;
|
||||
gzip_comp_level 6;
|
||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
|
||||
location /basic-api/ {
|
||||
rewrite ^/basic-api/(.*) /$1 break;
|
||||
proxy_pass http://backend:8000;
|
||||
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_connect_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
}
|
||||
|
||||
location /ws {
|
||||
proxy_pass http://backend:8000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
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_read_timeout 3600s;
|
||||
proxy_send_timeout 3600s;
|
||||
}
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
expires -1;
|
||||
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||
}
|
||||
|
||||
location /assets/ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||
expires 1y;
|
||||
add_header Cache-Control "public, immutable";
|
||||
log_not_found off;
|
||||
}
|
||||
|
||||
error_page 404 /index.html;
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
user nginx;
|
||||
worker_processes auto;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
multi_accept on;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 2048;
|
||||
client_max_body_size 100M;
|
||||
server_tokens off;
|
||||
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
}
|
||||
Reference in New Issue
Block a user