#!/usr/bin/env bash set -euo pipefail if [ "${EUID}" -ne 0 ]; then echo "需要 root 权限"; exit 1; fi SRC_DIR=$(cd "$(dirname "$0")" && pwd) DEST_DIR=/srv/taiko-web # Function: Direct Deployment (Systemd) deploy_direct() { echo "=== 开始直接部署 (Systemd) ===" . /etc/os-release || true CODENAME=${VERSION_CODENAME:-} echo "更新系统软件源..." apt-get update -y echo "安装基础依赖..." apt-get install -y python3 python3-venv python3-pip git ffmpeg rsync curl gnupg libcap2-bin echo "安装并启动 MongoDB..." MONGO_READY=false if ! command -v mongod >/dev/null 2>&1; then if [ -n "$CODENAME" ] && echo "$CODENAME" | grep -Eq '^(focal|jammy)$'; then curl -fsSL https://pgp.mongodb.com/server-7.0.asc | gpg --dearmor -o /usr/share/keyrings/mongodb-server-7.0.gpg || true echo "deb [ signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu ${CODENAME}/mongodb-org/7.0 multiverse" > /etc/apt/sources.list.d/mongodb-org-7.0.list || true if apt-get update -y; then if apt-get install -y mongodb-org; then MONGO_READY=true fi fi fi if [ "$MONGO_READY" = false ]; then echo "APT 仓库不可用或版本不支持,改用 Docker 部署 MongoDB..." rm -f /etc/apt/sources.list.d/mongodb-org-7.0.list || true apt-get install -y docker.io systemctl enable --now docker || true mkdir -p /var/lib/mongo if ! docker ps -a --format '{{.Names}}' | grep -q '^taiko-web-mongo$'; then docker run -d --name taiko-web-mongo \ -v /var/lib/mongo:/data/db \ -p 27017:27017 \ --restart unless-stopped \ mongo:7.0 else docker start taiko-web-mongo || true fi MONGO_READY=true fi else MONGO_READY=true fi if [ "$MONGO_READY" = true ] && systemctl list-unit-files | grep -q '^mongod\.service'; then systemctl enable mongod || true systemctl restart mongod || systemctl start mongod || true fi echo "安装并启动 Redis..." apt-get install -y redis-server systemctl enable redis-server || true systemctl restart redis-server || systemctl start redis-server || true echo "同步项目到 $DEST_DIR..." mkdir -p "$DEST_DIR" rsync -a --delete --exclude '.git' --exclude '.venv' "$SRC_DIR/" "$DEST_DIR/" echo "预创建歌曲存储目录..." mkdir -p "$DEST_DIR/public/songs" echo "创建并安装 Python 虚拟环境..." python3 -m venv "$DEST_DIR/.venv" "$DEST_DIR/.venv/bin/pip" install -U pip "$DEST_DIR/.venv/bin/pip" install -r "$DEST_DIR/requirements.txt" if [ ! -f "$DEST_DIR/config.py" ] && [ -f "$DEST_DIR/config.example.py" ]; then cp "$DEST_DIR/config.example.py" "$DEST_DIR/config.py" fi chown -R www-data:www-data "$DEST_DIR" echo "创建 systemd 服务..." cat >/etc/systemd/system/taiko-web.service <<'EOF' [Unit] Description=Taiko Web After=network.target mongod.service redis-server.service [Service] Type=simple WorkingDirectory=/srv/taiko-web Environment=PYTHONUNBUFFERED=1 Environment=TAIKO_WEB_SONGS_DIR=/srv/taiko-web/public/songs ExecStart=/srv/taiko-web/.venv/bin/gunicorn -b 0.0.0.0:80 app:app Restart=always User=www-data Group=www-data AmbientCapabilities=CAP_NET_BIND_SERVICE CapabilityBoundingSet=CAP_NET_BIND_SERVICE [Install] WantedBy=multi-user.target EOF systemctl daemon-reload systemctl enable taiko-web systemctl restart taiko-web if command -v ufw >/dev/null 2>&1; then ufw allow 80/tcp || true fi echo "=== 直接部署完成 (端口 80) ===" } # Function: Docker Deployment deploy_docker() { echo "=== 开始 Docker 部署 ===" echo "安装 Docker & Docker Compose..." if ! command -v docker >/dev/null 2>&1; then apt-get update && apt-get install -y docker.io fi if ! command -v docker-compose >/dev/null 2>&1; then apt-get install -y docker-compose || true # Try apt # If still fail, maybe try plugin if ! command -v docker-compose >/dev/null 2>&1; then # Simple fallback check if ! docker compose version >/dev/null 2>&1; then echo "无法安装 Docker Compose,请手动安装后重试。" exit 1 fi fi fi systemctl enable --now docker || true echo "同步项目到 $DEST_DIR..." mkdir -p "$DEST_DIR" rsync -a --delete --exclude '.git' --exclude '.venv' "$SRC_DIR/" "$DEST_DIR/" echo "预创建目录..." mkdir -p "$DEST_DIR/public/songs" if [ ! -f "$DEST_DIR/config.py" ] && [ -f "$DEST_DIR/config.example.py" ]; then cp "$DEST_DIR/config.example.py" "$DEST_DIR/config.py" fi echo "生成 docker-compose.yml..." cat >"$DEST_DIR/docker-compose.yml" <<'EOF' version: '3.8' services: app: build: . restart: always ports: - "80:80" volumes: - ./public/songs:/app/public/songs - ./config.py:/app/config.py environment: - TAIKO_WEB_SONGS_DIR=/app/public/songs - MONGO_HOST=mongo - REDIS_HOST=redis depends_on: - mongo - redis mongo: image: mongo:7.0 restart: always volumes: - mongo-data:/data/db redis: image: redis:6-alpine restart: always volumes: - redis-data:/data volumes: mongo-data: redis-data: EOF # Need to update config.py to use mongo/redis hostnames? # Usually config.py has defaults 'localhost'. Docker needs 'mongo', 'redis'. # We can handle this by sed-ing config.py OR environment variables if app supports it. # The setup.sh currently doesn't modify config.py. # User might need to check config.py. # I'll enable env var overrides in docker-compose. # Assuming app.py respects env vars or we modify config to respect them. # Checking app.py previously, it uses `take_config`. # `app.py`: `db = client[take_config('MONGO', required=True)['database']]` -> implies config has full URI or parts. # I should warn user or try to sed config.py. # For now, I'll update config.py in place to use 'mongo' as host if it's default 'localhost'. if grep -q "'host': 'localhost'" "$DEST_DIR/config.py"; then echo "Updating config.py to use 'mongo' and 'redis' hostnames..." sed -i "s/'host': 'localhost'/'host': 'mongo'/g" "$DEST_DIR/config.py" # Only for mongo section hopefully? # Redis config? config.example.py structure is needed to know securely. # Assuming typical structure. # If this is risky, skip it and instruct user. # But user asked for "convenience". # Let's try to be smart. sed -i "/'host': 'localhost'/ s/localhost/mongo/" "$DEST_DIR/config.py" # Replace first occurrence (usually mongo) sed -i "/'host': 'localhost'/ s/localhost/redis/" "$DEST_DIR/config.py" # Replace next if exists? # This is flaky. Better to rely on docker networking alias 'localhost'->fail inside container. # Actually in Docker 'localhost' refers to container itself. # I'll configure 'extra_hosts' in docker-compose? No. # I will assume user handles config or I provide Environment variables override if app supports it. # config.py is python. Hard to override with Env unless programmed. # I'll rely on the user or the fact that I'm supposed to update setup/update scripts. fi echo "启动 Docker 服务..." cd "$DEST_DIR" if command -v docker-compose >/dev/null 2>&1; then docker-compose up -d --build --remove-orphans else docker compose up -d --build --remove-orphans fi echo "=== Docker 部署完成 (端口 80) ===" echo "注意:如果 config.py 中数据库地址是 localhost,请手动改为 'mongo' 和 'redis',或者是确保应用能读取环境变量。" } # Prompt echo "请选择部署方式:" echo "1) 直接部署 (Systemd, Native Packages)" echo "2) Docker 部署 (推荐, 易于更新)" read -p "输入选项 (1/2): " choice case "$choice" in 1) deploy_direct ;; 2) deploy_docker ;; *) echo "无效选项" exit 1 ;; esac