一、项目背景
- 目标:基于 Docusaurus 构建 Markdown 驱动的静态网站,实现内容自动化发布,提升团队知识管理与分享效率。
- 需求:支持自动化构建与部署,保障内容持续更新,降低运维成本。
二、技术选型
- 静态网站生成器:Docusaurus(React 生态,社区活跃,SEO 友好,易扩展)
- 代码托管与 CI/CD:GitHub + GitHub Actions
- 云服务器:阿里云 ECS / 腾讯云 CVM / AWS EC2(任选其一,需具备公网 IP 和 SSH 访问权限)
- 服务器系统:Debian 12(文中的操作都是以该系统为例,其他系统的需要按需更换命令)
- Web 服务器:Nginx(高性能、易配置)
三、系统架构图
四、实施步骤
这里从 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_KEY:cat ~/.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 + 云服务器自动化部署,具备如下优势:
-
自动化程度高,降低人力运维成本
-
支持内容持续集成与快速发布
-
架构简单,易于维护和扩展