diff --git a/Dockerfile b/Dockerfile index 01857f6..49e1fe6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,10 +25,16 @@ ARG HOST_ARCH="amd64" # 1. Install System Dependencies & Node.js (for runtime) # We need Node.js to run the Next.js production server (npm start) +# curl is needed for health check +# WeasyPrint dependencies for PDF export RUN apt-get update && apt-get install -y \ curl \ nodejs \ npm \ + libpango-1.0-0 \ + libharfbuzz0b \ + libpangoft2-1.0-0 \ + libpangocairo-1.0-0 \ && rm -rf /var/lib/apt/lists/* WORKDIR /app @@ -58,24 +64,29 @@ COPY --from=frontend-builder /app/frontend/node_modules ./frontend/node_modules/ WORKDIR /app COPY backend/ ./backend/ COPY *.py ./ -COPY *.sh ./ -COPY entrypoint.sh /usr/local/bin/ + +# 6. Copy Scripts +COPY entrypoint.sh /usr/local/bin/entrypoint.sh +COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh # Make scripts executable -RUN chmod +x /usr/local/bin/entrypoint.sh ./start_app.sh +RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/bin/docker-entrypoint.sh # Environment Variables Defaults ENV PW_LOCAL_PORT=3001 # Disable Next.js Telemetry ENV NEXT_TELEMETRY_DISABLED=1 -# Expose ports? -# Technically tunnel needs NO EXPOSE, but for local debugging we might want it. -# EXPOSE 3000 8000 +# Expose ports (for local debugging) +EXPOSE 3001 8000 -# Entrypoint & Command +# Health Check +# 检查后端健康状态,前端通过后端代理访问 +HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ + CMD curl -f http://localhost:8000/health || exit 1 + +# Entrypoint: Portwarden tunnel client ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] -# 启动前端 (Next.js 生产模式) 和后端 (FastAPI) -# 前端监听 3001 端口,后端监听 8000 端口 -# 使用虚拟环境中的 Python 运行后端 -CMD ["bash", "-c", "cd /app/frontend && npm start & cd /app/backend && /app/.venv/bin/python -m uvicorn app.main:app --host 0.0.0.0 --port 8000 && wait"] + +# Command: Start backend and frontend with process management +CMD ["/usr/local/bin/docker-entrypoint.sh"] diff --git a/backend/app/main.py b/backend/app/main.py index 62a285c..e801280 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -9,12 +9,12 @@ import logging from typing import List, Optional # 配置日志系统 - 在最开始配置,确保所有模块都能使用 +# 仅输出到 stdout,容器环境下通过 docker logs 收集日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ - logging.StreamHandler(sys.stdout), - logging.FileHandler("server.log", encoding='utf-8') + logging.StreamHandler(sys.stdout) ] ) diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100644 index 0000000..f7c4ffe --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,105 @@ +#!/bin/bash +# ============================================================================= +# FA3 Datafetch 容器启动脚本 +# 功能:启动后端 (FastAPI) 和前端 (Next.js),处理进程管理 +# ============================================================================= + +set -e + +# 信号处理函数 +cleanup() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] 收到停止信号,正在关闭服务..." + if [ -n "$BACKEND_PID" ]; then + kill "$BACKEND_PID" 2>/dev/null || true + fi + if [ -n "$FRONTEND_PID" ]; then + kill "$FRONTEND_PID" 2>/dev/null || true + fi + # 等待进程结束 + wait $BACKEND_PID $FRONTEND_PID 2>/dev/null || true + echo "[$(date '+%Y-%m-%d %H:%M:%S')] 所有服务已停止" + exit 0 +} + +trap cleanup SIGTERM SIGINT + +# ============================================================================= +# 1. 启动后端服务 +# ============================================================================= +echo "[$(date '+%Y-%m-%d %H:%M:%S')] 启动后端服务 (FastAPI on :8000)..." + +cd /app/backend +export PYTHONPATH=/app:/app/backend + +# 启动后端,日志输出到stdout +/app/.venv/bin/python -m uvicorn app.main:app \ + --host 0.0.0.0 \ + --port 8000 \ + --access-log & + +BACKEND_PID=$! +echo "[$(date '+%Y-%m-%d %H:%M:%S')] 后端PID: $BACKEND_PID" + +# 等待后端启动 +sleep 3 + +# 检查后端是否启动成功 +if ! kill -0 "$BACKEND_PID" 2>/dev/null; then + echo "[$(date '+%Y-%m-%d %H:%M:%S')] 错误:后端启动失败!" + exit 1 +fi + +# ============================================================================= +# 2. 启动前端服务 +# ============================================================================= +echo "[$(date '+%Y-%m-%d %H:%M:%S')] 启动前端服务 (Next.js on :3001)..." + +cd /app/frontend + +# 设置生产环境变量 +export NODE_ENV=production +export PORT=3001 + +# 启动前端,日志输出到stdout +npm start & + +FRONTEND_PID=$! +echo "[$(date '+%Y-%m-%d %H:%M:%S')] 前端PID: $FRONTEND_PID" + +# 等待前端启动 +sleep 3 + +# 检查前端是否启动成功 +if ! kill -0 "$FRONTEND_PID" 2>/dev/null; then + echo "[$(date '+%Y-%m-%d %H:%M:%S')] 错误:前端启动失败!" + kill "$BACKEND_PID" 2>/dev/null || true + exit 1 +fi + +# ============================================================================= +# 3. 监控服务状态 +# ============================================================================= +echo "[$(date '+%Y-%m-%d %H:%M:%S')] ============================================" +echo "[$(date '+%Y-%m-%d %H:%M:%S')] 所有服务启动成功!" +echo "[$(date '+%Y-%m-%d %H:%M:%S')] - 前端: http://localhost:3001 (通过隧道暴露)" +echo "[$(date '+%Y-%m-%d %H:%M:%S')] - 后端: http://localhost:8000 (仅容器内部)" +echo "[$(date '+%Y-%m-%d %H:%M:%S')] ============================================" + +# 持续监控进程状态 +while true; do + # 检查后端 + if ! kill -0 "$BACKEND_PID" 2>/dev/null; then + echo "[$(date '+%Y-%m-%d %H:%M:%S')] 错误:后端进程已停止!" + kill "$FRONTEND_PID" 2>/dev/null || true + exit 1 + fi + + # 检查前端 + if ! kill -0 "$FRONTEND_PID" 2>/dev/null; then + echo "[$(date '+%Y-%m-%d %H:%M:%S')] 错误:前端进程已停止!" + kill "$BACKEND_PID" 2>/dev/null || true + exit 1 + fi + + sleep 5 +done diff --git a/docker-run.sh b/docker-run.sh index 846aab0..9790cbc 100755 --- a/docker-run.sh +++ b/docker-run.sh @@ -1,6 +1,9 @@ #!/bin/bash # FA3-Datafetch Docker 启动脚本 +# 固定使用 sudo +DOCKER="sudo docker" + # 容器名称 CONTAINER_NAME="fa3-app" @@ -33,15 +36,16 @@ CLIENT_KEY="$CERT_DIR/client.key" # 停止并删除旧容器 echo "停止旧容器..." -docker rm -f $CONTAINER_NAME 2>/dev/null || true +$DOCKER rm -f $CONTAINER_NAME 2>/dev/null || true # 启动新容器 echo "启动新容器..." -docker run -d \ +$DOCKER run -d \ --name $CONTAINER_NAME \ --dns 8.8.8.8 \ --dns 8.8.4.4 \ --dns 114.114.114.114 \ + --restart unless-stopped \ -e PW_SERVICE_ID="$PW_SERVICE_ID" \ -e PW_SERVER_ADDRS="$PW_SERVER_ADDRS" \ -e PW_LOCAL_PORT="$PW_LOCAL_PORT" \ @@ -61,5 +65,5 @@ docker run -d \ fa3-datafetch echo "容器已启动!" -echo "查看日志: docker logs $CONTAINER_NAME" -echo "进入容器: docker exec -it $CONTAINER_NAME sh" +echo "查看日志: $DOCKER logs -f $CONTAINER_NAME" +echo "进入容器: $DOCKER exec -it $CONTAINER_NAME sh" diff --git a/update-and-run.sh b/update-and-run.sh index 0722ec3..8ecf835 100755 --- a/update-and-run.sh +++ b/update-and-run.sh @@ -4,6 +4,10 @@ set -e +# 固定使用 sudo +DOCKER="sudo docker" +SUDO="sudo" + # 颜色定义 RED='\033[0;31m' GREEN='\033[0;32m' @@ -18,40 +22,50 @@ echo -e "${GREEN}========================================${NC}" cd "$(dirname "$0")" # 1. 拉取最新代码 -echo -e "\n${YELLOW}[1/4] 拉取最新代码...${NC}" +echo -e "\n${YELLOW}[1/3] 拉取最新代码...${NC}" git fetch origin git pull origin main echo -e "${GREEN}✓ 代码更新完成${NC}" -# 2. 停止旧容器(如果存在) -CONTAINER_NAME="fa3-app" -echo -e "\n${YELLOW}[2/4] 停止旧容器...${NC}" -if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then - docker stop $CONTAINER_NAME 2>/dev/null || true - docker rm $CONTAINER_NAME 2>/dev/null || true - echo -e "${GREEN}✓ 旧容器已停止并删除${NC}" -else - echo -e "${GREEN}✓ 没有运行中的旧容器${NC}" -fi - -# 3. 重新构建 Docker 镜像 -echo -e "\n${YELLOW}[3/4] 重新构建 Docker 镜像...${NC}" +# 2. 重新构建 Docker 镜像 +echo -e "\n${YELLOW}[2/3] 重新构建 Docker 镜像...${NC}" echo -e " 这可能需要几分钟时间..." -docker build -t fa3-datafetch . +$DOCKER build --network=host -t fa3-datafetch . echo -e "${GREEN}✓ Docker 镜像构建完成${NC}" -# 4. 启动新容器 -echo -e "\n${YELLOW}[4/4] 启动新容器...${NC}" +# 3. 启动新容器 +echo -e "\n${YELLOW}[3/3] 启动新容器...${NC}" ./docker-run.sh echo -e "${GREEN}✓ 新容器已启动${NC}" -# 清理未使用的镜像(可选) +# 等待容器启动并检查健康状态 +echo -e "\n${YELLOW}等待容器启动...${NC}" +sleep 5 + +# 检查容器状态 +if $DOCKER ps --format '{{.Names}}' | grep -q "^fa3-app$"; then + echo -e "${GREEN}✓ 容器运行中${NC}" + + # 显示容器日志(最后几行) + echo -e "\n${YELLOW}最新日志:${NC}" + $DOCKER logs fa3-app --tail 10 + + # 检查健康状态 + HEALTH_STATUS=$($DOCKER inspect fa3-app --format='{{.State.Health.Status}}' 2>/dev/null || echo "checking") + echo -e "\n健康状态: ${GREEN}$HEALTH_STATUS${NC}" +else + echo -e "${RED}✗ 容器启动失败${NC}" + echo -e "查看错误日志: $DOCKER logs fa3-app" + exit 1 +fi + +# 清理未使用的镜像 echo -e "\n${YELLOW}清理旧镜像...${NC}" -docker image prune -f +$DOCKER image prune -f echo -e "${GREEN}✓ 清理完成${NC}" echo -e "\n${GREEN}========================================${NC}" echo -e "${GREEN} 更新完成!${NC}" echo -e "${GREEN}========================================${NC}" -echo -e "查看日志: docker logs -f $CONTAINER_NAME" -echo -e "进入容器: docker exec -it $CONTAINER_NAME sh" +echo -e "查看日志: $DOCKER logs -f fa3-app" +echo -e "进入容器: $DOCKER exec -it fa3-app sh"