Debian 系统 Docusaurus 项目自动部署完整教程

159 阅读13分钟

一、项目背景

  • 目标:基于 Docusaurus 构建 Markdown 驱动的静态网站,实现内容自动化发布,提升团队知识管理与分享效率。
  • 需求:支持自动化构建与部署,保障内容持续更新,降低运维成本。

二、技术选型

  • 静态网站生成器:Docusaurus(React 生态,社区活跃,SEO 友好,易扩展)
  • 代码托管与 CI/CD:GitHub + GitHub Actions
  • 云服务器:阿里云 ECS / 腾讯云 CVM / AWS EC2(任选其一,需具备公网 IP 和 SSH 访问权限)
  • 服务器系统:Debian 12(文中的操作都是以该系统为例,其他系统的需要按需更换命令)
  • Web 服务器:Nginx(高性能、易配置)

三、系统架构图

image.png

四、实施步骤

这里从 0 到 1 开始记录,如果已经有项目了,可以跳到指定步骤。

1. 初始化 Docusaurus 项目

npx create-docusaurus@latest my-website classic --typescript
cd my-website
npm install
npm run start
  • 目录结构:docs/(文档)、src/(自定义组件)、static/(静态资源)、docusaurus.config.js(配置)

2. 代码托管

新建 GitHub 仓库,将项目代码推送至远程仓库。

git init
git remote add origin git@github.com:your-org/your-repo.git
git add .
git commit -m "init: Docusaurus site"
git push -u origin main

3. 云服务器准备

  • 购买并配置云服务器(如阿里云 ECS),开放 80/443 端口。

  • 安装 Node.js、Nginx、Git。

  • 配置 Nginx 反向代理,指向静态文件目录(如 /var/www/my-website)。

3.1 服务器环境配置

如果服务器是新系统,需要首先完成服务器的初始配置,详见文章:Debian 系统装好后必须做的两件事

如果服务器未安装 Nginx,请先安装,详见文章:Debian 系统 Nginx 完全配置指南:从安装到上线的新手教程

基础的环境搭建好了,就开始按下面的教程执行

3.1.1 创建部署用户和目录
# 创建专用的部署用户
sudo useradd -m -s /bin/bash deploy # 创建一个名为 deploy 的系统用户,专门用于部署和管理应用,避免直接使用 root
cat /etc/group | grep -E 'sudo|wheel|admin' # 检查管理组,例如我当前系统是wheel
sudo usermod -aG sudo deploy # 将 deploy 用户添加到 sudo 组,使其能通过 sudo 执行管理员命令

# 创建目录结构
sudo mkdir -p /opt/projects /opt/scripts /opt/logs
sudo chown deploy:deploy /opt/projects /opt/scripts /opt/logs # 将目录的所有者和所属组均设为 deploy 用户,确保该用户有完整读写权限

# 创建 Web 目录
sudo mkdir -p /var/www/my-website
sudo chown deploy:www-data /var/www/my-website # 不同的 linux 中 nginx 组名命名不同,可以通过 ps aux | grep nginx 查看
sudo chmod 775 /var/www/my-website # 允许 deploy 和 Nginx 共同管理文件,同时防止未授权用户修改
3.1.2 安装 Node.js
# 安装 Node.js 18.x
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs

# 验证安装
node --version
npm --version
3.1.3 安装 Git
# 安装 Git
sudo apt install -y git

# 验证安装
git -v

4. 自动化部署(CI/CD)

4.1.1 切换到部署用户并生成密钥
# 切换到部署用户
sudo su - deploy

# 生成 SSH 密钥
ssh-keygen -t ed25519 -C "deploy@debian-server"
# 或者使用 RSA(如果系统较旧)
# ssh-keygen -t rsa -b 4096 -C "deploy@debian-server"

# 设置正确的权限
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub

# 显示公钥
cat ~/.ssh/id_ed25519.pub

将获取到的公钥配置在 GitHub 的 SSH keys 中,配置完成后,在 SSH 客户端中配置

# 创建 SSH 配置文件
vim ~/.ssh/config

# 添加以下内容
Host github.com
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes

都配置好了之后,测试 GitHub 的连接:

ssh -T git@github.com

当你看到如下信息,就表示服务器和 GitHub 的连接已经成功了

Hi 你的用户名! You've successfully authenticated, but GitHub does not provide shell access.

在服务器上配置公钥,用来为 GitHub Actions 使用的,私钥配置在 Github Actions 的Actions secrets and variables中

# 生成新的SSH密钥对
ssh-keygen -t rsa -b 4096 -C "your-email@example.com" -f ~/.ssh/my-website

# 设置正确的权限
chmod 600 ~/.ssh/my-website
chmod 644 ~/.ssh/my-website.pub

# 将公钥添加到 authorized_keys
echo "你的公钥内容" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

# 验证文件权限
ls -la ~/.ssh/
4.1.3 克隆项目
# 确保在部署用户下操作
sudo su - deploy

# 克隆项目
cd /opt/projects
git clone git@github.com:your-username/my-website.git
cd my-website

# 安装依赖
npm install

# 测试构建
npm run build

# 检查构建结果
ls -la build/
4.1.2 创建部署脚本

创建 /opt/scripts/my-website_deploy.sh:

#!/bin/bash

# ========================================
# Website 项目部署脚本 v2.0
# 适配 GitHub Actions 自动化部署
# ========================================

# 严格模式和错误处理
set -euo pipefail

# ========================================
# 配置变量(支持环境变量覆盖)
# ========================================
readonly SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_VERSION="2.0"
readonly PROJECT_NAME="my-website"

# 路径配置 - 支持从环境变量获取
readonly PROJECT_DIR="/opt/projects/${PROJECT_NAME}"
readonly WEB_DIR="/var/www/${PROJECT_NAME}"
readonly BUILD_SOURCE="${BUILD_SOURCE:-${PROJECT_DIR}/build}"
readonly LOG_DIR="/opt/logs"
readonly LOG_FILE="${LOG_DIR}/${PROJECT_NAME}-deploy.log"
readonly BACKUP_DIR="/opt/backups/${PROJECT_NAME}"

# 部署信息 - 从环境变量获取
readonly TIMESTAMP="${DEPLOY_TIMESTAMP:-$(date '+%Y%m%d_%H%M%S')}"
readonly RUN_NUMBER="${DEPLOY_RUN_NUMBER:-manual}"
readonly COMMIT_SHA="${DEPLOY_COMMIT:-unknown}"
readonly DEPLOY_USER="${DEPLOY_ACTOR:-$(whoami)}"
readonly BRANCH="${DEPLOY_BRANCH:-unknown}"

# 清理和保留设置
readonly MAX_BACKUP_DAYS=7
readonly MAX_LOG_DAYS=30

# 用户和权限
readonly WEB_USER="$(whoami)"
readonly WEB_GROUP="www-data"
readonly FILE_PERMISSIONS="644"
readonly DIR_PERMISSIONS="755"

# 颜色定义
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly PURPLE='\033[0;35m'
readonly CYAN='\033[0;36m'
readonly WHITE='\033[1;37m'
readonly NC='\033[0m'

# ========================================
# 工具函数
# ========================================

# 日志函数
log() {
    local level="$1"
    local message="$2"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    local color=""
    local prefix=""
    
    case "$level" in
        "INFO")    color="$BLUE";   prefix="ℹ️ " ;;
        "SUCCESS") color="$GREEN";  prefix="✅" ;;
        "WARN")    color="$YELLOW"; prefix="⚠️ " ;;
        "ERROR")   color="$RED";    prefix="❌" ;;
        "DEBUG")   color="$PURPLE"; prefix="🔍" ;;
        "STEP")    color="$CYAN";   prefix="🔧" ;;
        *)         color="$NC";     prefix="📝" ;;
    esac
    
    local log_line="[$timestamp] [$level] $message"
    local display_line="${color}[$timestamp] ${prefix} $message${NC}"
    
    # 输出到终端
    echo -e "$display_line"
    
    # 写入日志文件(创建目录如果不存在)
    if mkdir -p "$(dirname "$LOG_FILE")" 2>/dev/null; then
        echo "$log_line" >> "$LOG_FILE"
    fi
}

# 错误处理函数
error_handler() {
    local line_number="$1"
    local error_code="$2"
    log "ERROR" "脚本在第 $line_number 行失败 (退出码: $error_code)"
    log "ERROR" "部署流程异常终止"
    exit "$error_code"
}

# 设置错误陷阱
trap 'error_handler ${LINENO} $?' ERR

# 检查用户权限(如果以root运行给出警告但继续)
check_user_permissions() {
    if [ "$EUID" -eq 0 ]; then
        log "WARN" "以 root 用户运行,建议使用普通用户"
    fi
    log "SUCCESS" "用户权限检查完成"
}

# 创建必要目录
create_directories() {
    log "STEP" "创建必要的目录结构..."
    
    local dirs=("$LOG_DIR" "$BACKUP_DIR")
    
    for dir in "${dirs[@]}"; do
        if [ ! -d "$dir" ]; then
            if mkdir -p "$dir" 2>/dev/null; then
                log "INFO" "创建目录: $dir"
            else
                log "WARN" "无法创建目录: $dir,尝试使用sudo..."
                sudo mkdir -p "$dir"
                sudo chown "$WEB_USER:$WEB_GROUP" "$dir" 2>/dev/null || true
            fi
        fi
    done
    
    log "SUCCESS" "目录结构创建完成"
}

# 验证构建文件
validate_build_files() {
    log "STEP" "验证构建文件..."
    
    if [ ! -d "$BUILD_SOURCE" ]; then
        log "ERROR" "构建源目录不存在: $BUILD_SOURCE"
        log "INFO" "请确保GitHub Actions正确上传了构建文件"
        exit 1
    fi
    
    if [ ! -f "$BUILD_SOURCE/index.html" ]; then
        log "ERROR" "主页文件不存在: $BUILD_SOURCE/index.html"
        log "INFO" "构建可能不完整或路径配置错误"
        exit 1
    fi
    
    local file_count=$(find "$BUILD_SOURCE" -type f | wc -l)
    local dir_size=$(du -sh "$BUILD_SOURCE" | cut -f1)
    
    if [ "$file_count" -lt 5 ]; then
        log "WARN" "构建文件数量较少 ($file_count 个),可能构建不完整"
    fi
    
    log "INFO" "构建文件统计: $file_count 个文件,总大小: $dir_size"
    log "SUCCESS" "构建文件验证通过"
}

# 检查Web目录权限
check_web_directory() {
    log "STEP" "检查Web目录权限..."
    
    # 创建Web目录如果不存在
    if [ ! -d "$WEB_DIR" ]; then
        log "INFO" "Web目录不存在,尝试创建..."
        if mkdir -p "$WEB_DIR" 2>/dev/null; then
            log "SUCCESS" "Web目录创建成功: $WEB_DIR"
        else
            log "WARN" "无法创建Web目录,尝试使用sudo..."
            sudo mkdir -p "$WEB_DIR"
            sudo chown "$WEB_USER:$WEB_GROUP" "$WEB_DIR"
            log "SUCCESS" "Web目录创建成功 (使用sudo): $WEB_DIR"
        fi
    fi
    
    # 检查写权限
    if [ ! -w "$WEB_DIR" ]; then
        log "WARN" "没有Web目录写权限,尝试修复权限..."
        if sudo chown "$WEB_USER:$WEB_GROUP" "$WEB_DIR" 2>/dev/null; then
            sudo chmod 775 "$WEB_DIR"
            log "SUCCESS" "Web目录权限修复完成"
        else
            log "ERROR" "无法修复Web目录权限: $WEB_DIR"
            log "INFO" "请手动运行: sudo chown $WEB_USER:$WEB_GROUP $WEB_DIR"
            exit 1
        fi
    fi
    
    log "SUCCESS" "Web目录权限检查通过"
}

# 备份当前部署
backup_current_deployment() {
    if [ -d "$WEB_DIR" ] && [ "$(ls -A "$WEB_DIR" 2>/dev/null)" ]; then
        local backup_path="$BACKUP_DIR/backup_$TIMESTAMP"
        log "STEP" "备份当前部署到: $backup_path"
        
        mkdir -p "$backup_path"
        
        # 使用 rsync 进行更可靠的备份(如果可用)
        if command -v rsync >/dev/null 2>&1; then
            rsync -av "$WEB_DIR/" "$backup_path/"
        else
            cp -r "$WEB_DIR"/* "$backup_path/" 2>/dev/null || true
        fi
        
        local backup_size=$(du -sh "$backup_path" | cut -f1)
        log "SUCCESS" "备份完成,大小: $backup_size"
    else
        log "INFO" "无需备份,Web目录为空或不存在"
    fi
}

# 部署新文件
deploy_new_files() {
    log "STEP" "部署新文件到 Web 目录..."
    
    # 清理旧文件(更安全的方式)
    if [ -d "$WEB_DIR" ] && [ "$(ls -A "$WEB_DIR" 2>/dev/null)" ]; then
        log "INFO" "清理旧的Web文件..."
        find "$WEB_DIR" -mindepth 1 -delete 2>/dev/null || {
            log "WARN" "清理失败,尝试使用sudo..."
            sudo find "$WEB_DIR" -mindepth 1 -delete
        }
    fi
    
    # 复制新文件
    log "INFO" "复制新文件到Web目录..."
    if command -v rsync >/dev/null 2>&1; then
        rsync -av --delete "$BUILD_SOURCE/" "$WEB_DIR/"
    else
        cp -r "$BUILD_SOURCE"/* "$WEB_DIR/"
    fi
    
    # 设置权限
    log "INFO" "设置文件权限..."
    if chown -R "$WEB_USER:$WEB_GROUP" "$WEB_DIR" 2>/dev/null; then
        find "$WEB_DIR" -type d -exec chmod "$DIR_PERMISSIONS" {} \;
        find "$WEB_DIR" -type f -exec chmod "$FILE_PERMISSIONS" {} \;
    else
        log "WARN" "设置权限失败,尝试使用sudo..."
        sudo chown -R "$WEB_USER:$WEB_GROUP" "$WEB_DIR"
        sudo find "$WEB_DIR" -type d -exec chmod "$DIR_PERMISSIONS" {} \;
        sudo find "$WEB_DIR" -type f -exec chmod "$FILE_PERMISSIONS" {} \;
    fi
    
    local deployed_files=$(find "$WEB_DIR" -type f | wc -l)
    local deployed_size=$(du -sh "$WEB_DIR" | cut -f1)
    
    log "SUCCESS" "文件部署完成: $deployed_files 个文件,总大小: $deployed_size"
}

# 配置和重启服务
restart_web_services() {
    log "STEP" "配置并重启Web服务..."
    
    # 测试 Nginx 配置
    if command -v nginx >/dev/null 2>&1; then
        if nginx -t >/dev/null 2>&1 || sudo nginx -t >/dev/null 2>&1; then
            log "SUCCESS" "Nginx 配置测试通过"
            
            # 重新加载 Nginx
            log "INFO" "重新加载 Nginx 配置..."
            if systemctl reload nginx 2>/dev/null || sudo systemctl reload nginx 2>/dev/null; then
                log "SUCCESS" "Nginx 重载完成"
            else
                log "WARN" "Nginx 重载失败,请手动重载"
            fi
        else
            log "WARN" "Nginx 配置测试失败,跳过重载"
        fi
    else
        log "INFO" "Nginx 未安装或不可用,跳过服务重启"
    fi
}

# 清理操作
perform_cleanup() {
    log "STEP" "执行清理操作..."
    
    # 清理旧备份
    if [ -d "$BACKUP_DIR" ]; then
        local deleted_backups=$(find "$BACKUP_DIR" -name "backup_*" -type d -mtime +$MAX_BACKUP_DAYS -delete -print 2>/dev/null | wc -l)
        if [ "$deleted_backups" -gt 0 ]; then
            log "INFO" "清理了 $deleted_backups 个旧备份"
        fi
    fi
    
    # 清理旧日志
    if [ -f "$LOG_FILE" ]; then
        find "$(dirname "$LOG_FILE")" -name "*.log" -type f -mtime +$MAX_LOG_DAYS -delete 2>/dev/null || true
    fi
    
    log "SUCCESS" "清理操作完成"
}

# 生成部署报告
generate_deployment_report() {
    log "STEP" "生成部署报告..."
    
    local report_file="${LOG_DIR}/${PROJECT_NAME}-deployment-report-${TIMESTAMP}.txt"
    
    cat > "$report_file" << EOF
========================================
Website 项目部署报告
========================================
时间: $(date '+%Y-%m-%d %H:%M:%S')
脚本版本: $SCRIPT_VERSION
部署ID: $TIMESTAMP

GitHub Actions 信息:
- 构建号: $RUN_NUMBER
- 提交: $COMMIT_SHA
- 分支: $BRANCH
- 触发者: $DEPLOY_USER

环境信息:
- 服务器: $(hostname)
- 系统: $(uname -a)
- 执行用户: $(whoami)

路径信息:
- 项目目录: $PROJECT_DIR
- Web目录: $WEB_DIR
- 构建源: $BUILD_SOURCE

部署统计:
- 部署文件数: $(find "$WEB_DIR" -type f 2>/dev/null | wc -l)
- 目录大小: $(du -sh "$WEB_DIR" 2>/dev/null | cut -f1)
- 备份位置: $BACKUP_DIR/backup_$TIMESTAMP

服务状态:
- Nginx: $(systemctl is-active nginx 2>/dev/null || echo "未知")

========================================
EOF
    
    log "SUCCESS" "部署报告已生成: $report_file"
}

# ========================================
# 主要部署流程
# ========================================

print_banner() {
    echo -e "${WHITE}"
    echo "========================================"
    echo "🚀 Website 项目部署系统"
    echo "📝 脚本版本: $SCRIPT_VERSION"
    echo "⏰ 开始时间: $(date '+%Y-%m-%d %H:%M:%S')"
    echo "🆔 部署ID: $TIMESTAMP"
    if [ "$RUN_NUMBER" != "manual" ]; then
        echo "🔢 构建号: $RUN_NUMBER"
        echo "🔗 提交: ${COMMIT_SHA:0:8}"
        echo "👤 触发者: $DEPLOY_USER"
        echo "🌿 分支: $BRANCH"
    fi
    echo "========================================"
    echo -e "${NC}"
}

main() {
    print_banner
    
    log "INFO" "开始 Website 项目部署流程..."
    
    # 预检查阶段
    check_user_permissions
    create_directories
    validate_build_files
    check_web_directory
    
    # 部署阶段
    backup_current_deployment
    deploy_new_files
    restart_web_services
    
    # 后处理阶段
    perform_cleanup
    generate_deployment_report
    
    echo -e "${GREEN}"
    echo "========================================"
    echo "🎉 部署成功完成!"
    echo "🌐 网站目录: $WEB_DIR"
    echo "📊 部署统计: $(find "$WEB_DIR" -type f | wc -l) 个文件"
    echo "💾 占用空间: $(du -sh "$WEB_DIR" | cut -f1)"
    echo "⏰ 完成时间: $(date '+%Y-%m-%d %H:%M:%S')"
    echo "========================================"
    echo -e "${NC}"
    
    log "SUCCESS" "🎉 Website 项目部署流程全部完成!"
}

# 信号处理
trap 'log "WARN" "收到中断信号,正在清理..."; exit 130' INT TERM

# 执行主函数
main "$@"

设置脚本权限:

sudo chmod +x /opt/scripts/my-website_deploy.sh
sudo chown deploy:deploy /opt/scripts/my-website_deploy.sh
4.1.3 配置 Nginx

创建 /etc/nginx/sites-available/my-website:

server {
    listen 80;
    listen [::]:80;  # IPv6 支持
    server_name your-domain.com www.your-domain.com;
    root /var/www/my-website;
    index index.html;

    # Debian 推荐的日志路径
    access_log /var/log/nginx/my-website.access.log;
    error_log /var/log/nginx/my-website.error.log;

    # 主要路由处理
    location / {
        try_files $uri $uri/ @fallback;
    }

    # Docusaurus 客户端路由处理
    location @fallback {
        rewrite ^.*$ /index.html last;
    }

    # 静态资源优化
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|webp)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        add_header Vary "Accept-Encoding";
        access_log off;
    }

    # 安全头部
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # Debian 特有的安全配置
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }

    location ~ ~$ {
        deny all;
        access_log off;
        log_not_found off;
    }

    # 禁止访问敏感文件
    location ~* \.(env|git|svn|htaccess|htpasswd)$ {
        deny all;
        access_log off;
        log_not_found off;
    }
}

启用站点:

# 启用站点配置
sudo ln -s /etc/nginx/sites-available/my-website /etc/nginx/sites-enabled/

# 移除默认站点(可选)
sudo rm -f /etc/nginx/sites-enabled/default

# 测试配置
sudo nginx -t

# 重新加载
sudo systemctl reload nginx
4.1.4 配置 deploy 权限
# 在 Debian 中使用 visudo
sudo visudo

# 添加以下行
deploy ALL=(ALL) NOPASSWD: /opt/scripts/my-website_deploy.sh, /usr/sbin/nginx, /bin/systemctl reload nginx, /bin/systemctl status nginx, /bin/cp, /bin/rm, /bin/chown, /bin/chmod, /bin/mkdir, /usr/bin/find
4.1.5 GitHub Actions 配置
  • 在 GitHub 仓库 Settings > Secrets and variables > Actions 添加如下 Secret:

    • HOST:云服务器公网 IP

    • USERNAME:云服务器用户名(如 deploy/root

    • PORT(可选):SSH 端口(默认 22)

    • SSH_PRIVATE_KEYcat ~/.ssh/id_ed25519 获取

在项目根目录新建 .github/workflows/deploy.yml

name: Deploy Web to Cloud Server

on:
  push:
    branches: [main]
  workflow_dispatch:

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    timeout-minutes: 30  # 增加整体超时时间
    
    steps:
      - name: 🚀 Checkout Repository
        uses: actions/checkout@v4
        timeout-minutes: 3

      - name: 📦 Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 18
          cache: 'npm'
        timeout-minutes: 5

      - name: 📥 Install Dependencies
        run: |
          echo "开始安装依赖..."
          npm ci --silent
          echo "✅ 依赖安装完成"
        timeout-minutes: 8

      - name: 🔨 Build Project
        run: |
          echo "开始构建项目..."
          npm run build
          echo "✅ 构建完成,验证文件..."
          ls -la build/
          echo "📊 文件数量: $(find build -type f | wc -l)"
          echo "📦 构建大小: $(du -sh build | cut -f1)"
        timeout-minutes: 5

      - name: 🧪 Test SSH Connection
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          timeout: 30s
          command_timeout: 2m
          script: |
            echo "🧪 测试SSH连接..."
            echo "✅ SSH连接成功"
            echo "📍 服务器: $(hostname)"
            echo "👤 用户: $(whoami)"
            echo "🕐 时间: $(date)"

      - name: 📤 Upload Build Files to Server
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          timeout: 5m
          source: "build/*"
          target: "/home/${{ secrets.USERNAME }}/my-website-build-${{ github.run_number }}"
          strip_components: 1

      - name: 🔍 Verify Upload
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          timeout: 1m
          command_timeout: 2m
          script: |
            BUILD_DIR="/home/${{ secrets.USERNAME }}/my-website-build-${{ github.run_number }}"
            echo "🔍 验证文件上传..."
            if [ -d "$BUILD_DIR" ]; then
              echo "✅ 构建目录存在: $BUILD_DIR"
              echo "📊 文件数量: $(find $BUILD_DIR -type f | wc -l)"
              echo "📦 目录大小: $(du -sh $BUILD_DIR | cut -f1)"
              if [ -f "$BUILD_DIR/index.html" ]; then
                echo "✅ 主页文件存在"
              else
                echo "❌ 主页文件缺失"
                exit 1
              fi
            else
              echo "❌ 构建目录不存在"
              exit 1
            fi

      - name: 🛠️ Prepare Deployment Environment
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          timeout: 2m
          command_timeout: 3m
          script: |
            echo "🛠️ 准备部署环境..."
            
            # 创建必要目录
            mkdir -p /home/${{ secrets.USERNAME }}/logs
            mkdir -p /home/${{ secrets.USERNAME }}/backups
            
            # 检查Web目录权限
            WEB_DIR="/var/www/my-website"
            if [ ! -d "$WEB_DIR" ]; then
              echo "⚠️ Web目录不存在,需要创建"
              echo "请在服务器上运行: sudo mkdir -p $WEB_DIR && sudo chown ${{ secrets.USERNAME }}:www-data $WEB_DIR"
              exit 1
            fi
            
            if [ ! -w "$WEB_DIR" ]; then
              echo "⚠️ 没有Web目录写权限"
              echo "请在服务器上运行: sudo chown ${{ secrets.USERNAME }}:www-data $WEB_DIR"
              exit 1
            fi
            
            echo "✅ 部署环境检查通过"

      - name: 🚀 Execute Deployment Script
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          timeout: 5m          # SSH连接超时
          command_timeout: 15m  # 命令执行超时,给部署脚本充足时间
          script: |
            # 设置环境变量供部署脚本使用
            export BUILD_SOURCE="/home/${{ secrets.USERNAME }}/my-website-build-${{ github.run_number }}"
            export DEPLOY_TIMESTAMP="$(date '+%Y%m%d_%H%M%S')"
            export DEPLOY_RUN_NUMBER="${{ github.run_number }}"
            export DEPLOY_COMMIT="${{ github.sha }}"
            export DEPLOY_ACTOR="${{ github.actor }}"
            export DEPLOY_BRANCH="${{ github.ref_name }}"
            
            echo "🚀 开始执行部署脚本..."
            echo "================================================"
            echo "📂 构建源: $BUILD_SOURCE"
            echo "🕐 部署时间: $DEPLOY_TIMESTAMP"
            echo "🔢 构建号: $DEPLOY_RUN_NUMBER"
            echo "🔗 提交: $DEPLOY_COMMIT"
            echo "👤 触发者: $DEPLOY_ACTOR"
            echo "🌿 分支: $DEPLOY_BRANCH"
            echo "================================================"
            
            # 检查部署脚本是否存在
            DEPLOY_SCRIPT="/opt/scripts/my-website_deploy.sh"
            if [ ! -f "$DEPLOY_SCRIPT" ]; then
              echo "❌ 部署脚本不存在: $DEPLOY_SCRIPT"
              echo "请确保在服务器上创建了该脚本"
              exit 1
            fi
            
            # 检查脚本权限
            if [ ! -x "$DEPLOY_SCRIPT" ]; then
              echo "⚠️ 部署脚本没有执行权限,尝试修复..."
              chmod +x "$DEPLOY_SCRIPT" || {
                echo "❌ 无法修改脚本权限,尝试使用sudo..."
                sudo chmod +x "$DEPLOY_SCRIPT" || {
                  echo "❌ 无法修改脚本权限"
                  exit 1
                }
              }
              echo "✅ 脚本权限修复完成"
            fi
            
            echo "✅ 部署脚本检查通过"
            echo "🔧 开始执行部署脚本..."
            echo "================================================"
            
            # 根据脚本需要的权限,尝试不同的执行方式
            if [ -w "/var/www/my-website" ] 2>/dev/null; then
              # 如果有Web目录写权限,直接执行
              echo "ℹ️ 使用当前用户权限执行脚本..."
              "$DEPLOY_SCRIPT"
            else
              # 如果没有写权限,尝试使用sudo
              echo "ℹ️ 使用sudo权限执行脚本..."
              sudo "$DEPLOY_SCRIPT"
            fi
            
            SCRIPT_EXIT_CODE=$?
            echo "================================================"
            
            if [ $SCRIPT_EXIT_CODE -eq 0 ]; then
              echo "✅ 部署脚本执行成功"
            else
              echo "❌ 部署脚本执行失败,退出码: $SCRIPT_EXIT_CODE"
              exit $SCRIPT_EXIT_CODE
            fi

      - name: 🔄 Restart Web Service (Optional)
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          timeout: 1m
          command_timeout: 2m
          script: |
            echo "🔄 重启Web服务..."
            
            # 尝试重载nginx(需要sudo权限)
            if sudo -n nginx -t >/dev/null 2>&1; then
              sudo -n systemctl reload nginx && echo "✅ Nginx重载成功" || echo "⚠️ Nginx重载失败"
            else
              echo "ℹ️ 跳过Nginx重载(需要sudo权限)"
            fi
        continue-on-error: true  # 即使失败也继续

      - name: 🔍 Post-Deployment Verification
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          timeout: 2m
          command_timeout: 5m
          script: |
            echo "🔍 执行部署后验证..."
            
            # 检查Web目录
            WEB_DIR="/var/www/my-website"
            echo "📁 检查Web目录: $WEB_DIR"
            
            if [ -d "$WEB_DIR" ]; then
              FILE_COUNT=$(find "$WEB_DIR" -type f 2>/dev/null | wc -l)
              if [ "$FILE_COUNT" -gt 0 ]; then
                DIR_SIZE=$(du -sh "$WEB_DIR" 2>/dev/null | cut -f1)
                echo "✅ Web目录包含 $FILE_COUNT 个文件,大小: $DIR_SIZE"
                
                # 检查关键文件
                if [ -f "$WEB_DIR/index.html" ]; then
                  echo "✅ 主页文件存在"
                else
                  echo "⚠️ 主页文件不存在"
                fi
                
                # 显示部分文件列表
                echo "📄 主要文件列表:"
                ls -la "$WEB_DIR/" | head -10
              else
                echo "❌ Web目录为空"
                echo "🔍 检查部署脚本日志..."
                if [ -f "/opt/logs/my-website-deploy.log" ]; then
                  echo "📋 最近的部署日志:"
                  tail -20 /opt/logs/my-website-deploy.log
                fi
                exit 1
              fi
            else
              echo "❌ Web目录不存在: $WEB_DIR"
              exit 1
            fi
            
            echo ""
            echo "🌐 执行网站健康检查..."
            sleep 3  # 等待服务稳定
            
            # 检查多个端点
            ENDPOINTS=("http://localhost" "http://127.0.0.1" "http://localhost:80")
            SUCCESS=false
            
            for endpoint in "${ENDPOINTS[@]}"; do
              echo "🔗 测试: $endpoint"
              RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 15 "$endpoint" 2>/dev/null || echo "000")
              
              case "$RESPONSE" in
                "200")
                  echo "✅ $endpoint 响应正常 (HTTP $RESPONSE)"
                  SUCCESS=true
                  break
                  ;;
                "000")
                  echo "⚠️ $endpoint 连接失败"
                  ;;
                *)
                  echo "⚠️ $endpoint 响应异常 (HTTP $RESPONSE)"
                  ;;
              esac
            done
            
            if [ "$SUCCESS" = "true" ]; then
              echo ""
              echo "🎉 部署验证成功!"
              echo "✅ 文件部署完成"
              echo "✅ 网站响应正常"
            else
              echo ""
              echo "⚠️ 网站响应异常,但文件部署可能仍然成功"
              echo "🔍 可能的原因:"
              echo "  - Nginx配置问题"
              echo "  - 服务未启动"
              echo "  - 防火墙设置"
              echo ""
              echo "🔧 请检查以下内容:"
              echo "  - sudo systemctl status nginx"
              echo "  - sudo nginx -t"
              echo "  - sudo systemctl reload nginx"
              
              # 不因为健康检查失败而整体失败,因为文件可能已经部署成功
              echo ""
              echo "ℹ️ 文件部署已完成,请手动检查网站状态"
            fi

      - name: 🧹 Cleanup
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          timeout: 1m
          command_timeout: 2m
          script: |
            echo "🧹 清理临时文件..."
            
            # 清理构建文件
            BUILD_DIR="/home/${{ secrets.USERNAME }}/my-website-build-${{ github.run_number }}"
            if [ -d "$BUILD_DIR" ]; then
              rm -rf "$BUILD_DIR"
              echo "✅ 清理构建文件: $BUILD_DIR"
            fi
            
            # 清理旧备份(保留最近5个)
            BACKUP_DIR="/home/${{ secrets.USERNAME }}/backups"
            if [ -d "$BACKUP_DIR" ]; then
              find "$BACKUP_DIR" -name "backup_*" -type d -mtime +5 -exec rm -rf {} \; 2>/dev/null || true
              echo "✅ 清理旧备份完成"
            fi
            
            echo "🎉 部署流程全部完成!"
        if: always()  # 总是执行清理

      - name: 📊 Deployment Summary
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          timeout: 30s
          command_timeout: 1m
          script: |
            echo "📊 部署摘要"
            echo "=================="
            echo "🕐 完成时间: $(date '+%Y-%m-%d %H:%M:%S')"
            echo "🔗 提交: ${{ github.sha }}"
            echo "👤 触发者: ${{ github.actor }}"
            echo "🌿 分支: ${{ github.ref_name }}"
            echo "🏗️ 构建号: ${{ github.run_number }}"
            
            WEB_DIR="/var/www/my-website"
            if [ -d "$WEB_DIR" ]; then
              echo "📁 Web目录: ✅"
              echo "📄 文件数: $(find "$WEB_DIR" -type f | wc -l)"
              echo "💾 大小: $(du -sh "$WEB_DIR" | cut -f1)"
            else
              echo "📁 Web目录: ❌"
            fi
        if: always()

五、系统监控

创建 /opt/scripts/system-monitor.sh:

#!/bin/bash

# Debian 系统监控脚本
LOG_FILE="/opt/logs/system-monitor.log"

log_with_timestamp() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a $LOG_FILE
}

log_with_timestamp "=== Debian 系统状态检查开始 ==="

# 检查系统负载
load_avg=$(uptime | awk -F'load average:' '{print $2}' | cut -d',' -f1 | xargs)
log_with_timestamp "系统负载: $load_avg"

# 检查内存使用
memory_usage=$(free | grep Mem | awk '{printf "%.1f", $3/$2 * 100.0}')
log_with_timestamp "内存使用率: ${memory_usage}%"

# 检查磁盘空间
disk_usage=$(df /var/www | awk 'NR==2 {print $5}' | cut -d'%' -f1)
log_with_timestamp "磁盘使用率: ${disk_usage}%"

# 检查 Nginx 状态
if systemctl is-active --quiet nginx; then
    log_with_timestamp "✅ Nginx 服务运行正常"
else
    log_with_timestamp "❌ Nginx 服务异常"
fi

# 检查网站响应
response=$(curl -s -o /dev/null -w "%{http_code}" http://localhost 2>/dev/null || echo "000")
if [ "$response" = "200" ]; then
    log_with_timestamp "✅ 网站正常访问"
else
    log_with_timestamp "❌ 网站响应异常: $response"
fi

# 检查 Node.js 进程
node_processes=$(pgrep -c node || echo "0")
log_with_timestamp "Node.js 进程数: $node_processes"

# 检查最近的部署日志
if [ -f "/opt/logs/deploy.log" ]; then
    last_deploy=$(tail -1 /opt/logs/deploy.log 2>/dev/null || echo "无部署日志")
    log_with_timestamp "最近部署: $last_deploy"
fi

log_with_timestamp "=== 系统状态检查完成 ==="

设置定时任务

# 编辑 crontab
crontab -e

# 添加监控任务
# 每小时检查一次系统状态
0 * * * * /opt/scripts/system-monitor.sh

# 每天凌晨清理日志
0 0 * * * find /opt/logs -name "*.log" -size +100M -exec truncate -s 10M {} \;

# 每周自动更新系统(可选)
0 2 * * 0 sudo apt update && sudo apt upgrade -y

六、系统优化配置(可选)

6.1 优化 Nginx 配置

# 优化 Nginx 配置
sudo nano /etc/nginx/nginx.conf

# 在 http 块中添加 Debian 优化配置
http {
    # 基础优化
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    server_tokens off;

    # Gzip 压缩
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types
        text/plain
        text/css
        text/xml
        text/javascript
        application/json
        application/javascript
        application/xml+rss
        application/atom+xml
        image/svg+xml;
}
配置项作用推荐场景
sendfile on使用内核零拷贝技术传输静态文件,减少 CPU 占用。所有环境(尤其是高流量站点)
tcp_nopush on配合 sendfile,确保数据包满再发送(减少网络碎片)。大文件下载或高延迟网络
tcp_nodelay on禁用 Nagle 算法,降低小文件传输延迟。实时性要求高的场景(如 API、WebSocket)
keepalive_timeout 65保持 TCP 连接 65 秒,减少重复握手开销。高并发短连接场景(如浏览器访问)
server_tokens off隐藏 Nginx 版本号,提升安全性。所有生产环境必选
gzip on压缩文本内容,减少传输体积(提升加载速度)。文本为主的网站(HTML/CSS/JS)

七、写在最后

本方案采用 Docusaurus + GitHub Actions + 云服务器自动化部署,具备如下优势:

  • 自动化程度高,降低人力运维成本

  • 支持内容持续集成与快速发布

  • 架构简单,易于维护和扩展