diff --git a/.gitignore b/.gitignore index ac124d0..91c3899 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ __pycache__/ backend/.venv/ frontend/node_modules/ frontend/dist/ +logs/ +.run/ storage/uploads/* storage/processed/* storage/covers/* diff --git a/README.md b/README.md index bca96b2..0f96063 100644 --- a/README.md +++ b/README.md @@ -201,6 +201,37 @@ npm run dev - 后端:http://localhost:8000 - 健康检查:http://localhost:8000/api/health +## 后台启动脚本 + +如果你不想用 Docker,可以直接用项目根目录下的脚本把前后端挂到后台: + +```bash +chmod +x start.sh stop.sh status.sh +./start.sh +``` + +脚本会做这些事: + +- 后台启动 FastAPI,默认端口 `8000` +- 后台启动 Vite,默认端口 `8451` +- 把 PID 写到 `.run/` +- 把日志写到 `logs/` + +常用命令: + +```bash +./status.sh +./stop.sh +tail -f logs/backend.log +tail -f logs/frontend.log +``` + +如果你要改端口,可以在启动前传环境变量,前端代理也会自动跟着后端端口变化: + +```bash +BACKEND_PORT=9000 FRONTEND_PORT=8451 ./start.sh +``` + ## Docker 启动 如果你希望在 Ubuntu 24.04 或其他装有 Docker 的环境中快速运行,直接使用: diff --git a/backend/Dockerfile b/backend/Dockerfile index 7093162..f750130 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,5 +1,3 @@ -# syntax=docker/dockerfile:1.7 - FROM python:3.12-slim-bookworm ENV PYTHONDONTWRITEBYTECODE=1 @@ -10,9 +8,7 @@ WORKDIR /app/backend RUN sed -i 's|deb.debian.org|mirrors.aliyun.com|g' /etc/apt/sources.list.d/debian.sources \ && sed -i 's|security.debian.org|mirrors.aliyun.com|g' /etc/apt/sources.list.d/debian.sources -RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt,sharing=locked \ - apt-get update \ +RUN apt-get update \ && apt-get install -y --no-install-recommends ffmpeg \ && rm -rf /var/lib/apt/lists/* diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 4d91e9e..27cd9b1 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -1,6 +1,8 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' +const proxyTarget = process.env.VITE_PROXY_TARGET || 'http://localhost:8000' + export default defineConfig({ plugins: [react()], server: { @@ -8,11 +10,11 @@ export default defineConfig({ port: 8451, proxy: { '/api': { - target: 'http://localhost:8000', + target: proxyTarget, changeOrigin: true, }, '/media': { - target: 'http://localhost:8000', + target: proxyTarget, changeOrigin: true, }, }, diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..74ff9dc --- /dev/null +++ b/start.sh @@ -0,0 +1,148 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RUN_DIR="$ROOT_DIR/.run" +LOG_DIR="$ROOT_DIR/logs" + +BACKEND_PID_FILE="$RUN_DIR/backend.pid" +FRONTEND_PID_FILE="$RUN_DIR/frontend.pid" +BACKEND_LOG_FILE="$LOG_DIR/backend.log" +FRONTEND_LOG_FILE="$LOG_DIR/frontend.log" + +BACKEND_HOST="${BACKEND_HOST:-0.0.0.0}" +BACKEND_PORT="${BACKEND_PORT:-8000}" +FRONTEND_HOST="${FRONTEND_HOST:-0.0.0.0}" +FRONTEND_PORT="${FRONTEND_PORT:-8451}" +CORS_ORIGINS="${CORS_ORIGINS:-http://localhost:${FRONTEND_PORT},http://127.0.0.1:${FRONTEND_PORT}}" +VITE_PROXY_TARGET="${VITE_PROXY_TARGET:-http://127.0.0.1:${BACKEND_PORT}}" + +mkdir -p "$RUN_DIR" "$LOG_DIR" + +require_command() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Missing command: $1" >&2 + exit 1 + fi +} + +cleanup_stale_pid() { + local pid_file="$1" + if [[ -f "$pid_file" ]]; then + local pid + pid="$(cat "$pid_file")" + if [[ -n "$pid" ]] && kill -0 "$pid" >/dev/null 2>&1; then + return 0 + fi + rm -f "$pid_file" + fi + return 1 +} + +wait_for_port() { + local host="$1" + local port="$2" + local name="$3" + local retries=30 + + while (( retries > 0 )); do + if python3 - "$host" "$port" <<'PY' +import socket +import sys + +host = sys.argv[1] +port = int(sys.argv[2]) +sock = socket.socket() +sock.settimeout(0.5) +try: + sock.connect((host, port)) +except OSError: + sys.exit(1) +finally: + sock.close() +sys.exit(0) +PY + then + echo "$name is ready on ${host}:${port}" + return 0 + fi + sleep 1 + retries=$((retries - 1)) + done + + echo "$name failed to start on ${host}:${port}. Check logs in $LOG_DIR" >&2 + return 1 +} + +require_command python3 +require_command node +require_command npm +require_command ffmpeg + +if ! python3 - <<'PY' >/dev/null 2>&1 +import fastapi +import uvicorn +import multipart +PY +then + echo "Missing Python packages. Run: cd backend && python3 -m pip install --user -r requirements.txt" >&2 + exit 1 +fi + +if [[ ! -d "$ROOT_DIR/frontend/node_modules" ]]; then + echo "Installing frontend dependencies..." + ( + cd "$ROOT_DIR/frontend" + npm install + ) +fi + +if cleanup_stale_pid "$BACKEND_PID_FILE"; then + echo "Backend already running with PID $(cat "$BACKEND_PID_FILE")" +else + echo "Starting backend..." + ( + cd "$ROOT_DIR/backend" + nohup env \ + CORS_ORIGINS="$CORS_ORIGINS" \ + python3 -m uvicorn app.main:app \ + --host "$BACKEND_HOST" \ + --port "$BACKEND_PORT" \ + >"$BACKEND_LOG_FILE" 2>&1 & + echo $! > "$BACKEND_PID_FILE" + ) +fi + +wait_for_port "127.0.0.1" "$BACKEND_PORT" "backend" + +if cleanup_stale_pid "$FRONTEND_PID_FILE"; then + echo "Frontend already running with PID $(cat "$FRONTEND_PID_FILE")" +else + echo "Starting frontend..." + ( + cd "$ROOT_DIR/frontend" + nohup env \ + VITE_PROXY_TARGET="$VITE_PROXY_TARGET" \ + npm run dev -- --host "$FRONTEND_HOST" --port "$FRONTEND_PORT" \ + >"$FRONTEND_LOG_FILE" 2>&1 & + echo $! > "$FRONTEND_PID_FILE" + ) +fi + +wait_for_port "127.0.0.1" "$FRONTEND_PORT" "frontend" + +SERVER_IP="$(hostname -I 2>/dev/null | awk '{print $1}')" +if [[ -z "$SERVER_IP" ]]; then + SERVER_IP="127.0.0.1" +fi + +cat </dev/null 2>&1; then + echo "$name: running (PID $pid)" + else + echo "$name: stale pid file" + fi +} + +show_status "backend" "$RUN_DIR/backend.pid" +show_status "frontend" "$RUN_DIR/frontend.pid" diff --git a/stop.sh b/stop.sh new file mode 100755 index 0000000..635511d --- /dev/null +++ b/stop.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +RUN_DIR="$ROOT_DIR/.run" + +stop_one() { + local name="$1" + local pid_file="$2" + + if [[ ! -f "$pid_file" ]]; then + echo "$name is not running" + return 0 + fi + + local pid + pid="$(cat "$pid_file")" + + if [[ -n "$pid" ]] && kill -0 "$pid" >/dev/null 2>&1; then + kill "$pid" + echo "Stopped $name (PID $pid)" + else + echo "$name pid file was stale" + fi + + rm -f "$pid_file" +} + +stop_one "backend" "$RUN_DIR/backend.pid" +stop_one "frontend" "$RUN_DIR/frontend.pid"