前言

之前我的博客部署到 VPS 使用的是 Jenkins + Docker 的方案(具体在这篇文章)。Jenkins 占用资源较大,实测长期运行不重启的话,内存能达到近 1GB,所以想放弃使用。但希望保留既能部署到 GitHub Pages,又能部署到服务器的功能。

近期借助 AI 工具,多次尝试,成功实现了基于 GitHub Actions + Docker + Webhook 的自动化部署方案。以下是完整的搭建记录,已经过验证,按照这个流程可以成功搭建服务。

在开始之前需要准备两个仓库,以我的为例:

  • 私有仓库DEKVIW/ZDBS(博客源码私有仓库)
  • 公开仓库DEKVIW/DEKVIW.github.io(GitHub Pages 公开仓库)

在保持现有结构基础上,添加 Docker 化的服务器自动部署:

1
2
3
本地hexo博客 → ZDBS(私有源码) → GitHub Actions → DEKVIW.github.io(GitHub Pages)
↓ (webhook通知)
Webhook服务器 → git pull更新 → Docker Nginx服务

第一步:服务器环境准备

安装必要依赖(Debian)

根据环境需要选择安装,已经具备的环境就不需要重复安装。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 更新系统
apt update && apt upgrade -y

# 安装Docker和Docker Compose
curl -fsSL https://get.docker.com | bash
curl -L "https://github.com/docker/compose/releases/download/v2.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

# 启动Docker服务
systemctl start docker
systemctl enable docker

# 安装Node.js和PM2(用于webhook服务器)
curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
apt-get install -y nodejs
npm install -g pm2

# 验证安装
docker --version
docker-compose --version
node --version

创建项目目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 创建项目目录结构
mkdir -p /root/data/hexo-webhook/{logs/nginx,scripts,configs,nginx/conf.d,nginx/html}

# 创建多站点目录结构(static,unhxi,mdnice为作者个性化目录,可根据需要调整)
mkdir -p /root/data/hexo-webhook/nginx/html/{hexo,static,unhxi,mdnice}

# 克隆博客到hexo子目录
cd /root/data/hexo-webhook/nginx/html/hexo
git clone https://github.com/DEKVIW/DEKVIW.github.io.git .

# 创建 .gitignore 文件(保护作者个性化目录,可根据需要调整)
cat > .gitignore << 'EOF'
cizhi/
EOF

目录结构说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/root/data/hexo-webhook/
├── logs/ # 所有日志文件
├── scripts/ # webhook和监控脚本
├── configs/ # PM2和其他配置
├── docker-compose.yml # Docker Compose配置
└── nginx/ # Nginx Docker相关
├── conf.d/ # 站点配置文件
├── html/ # 多站点静态文件目录
│ ├── hexo/ # 博客站点(Git管理)
│ │ ├── .git/ # Git仓库
│ │ ├── index.html # 博客文件
│ │ ├── posts/ # 博客文件
│ │ ├── css/ # 博客文件
│ │ ├── js/ # 博客文件
│ │ └── .gitignore # Git忽略配置
│ ├── static/ # 静态资源目录(作者个性化)
│ ├── mdnice/ # mdnice文件目录(作者个性化)
│ └── unhxi/ # 文件下载目录(作者个性化)
└── nginx.conf # Nginx主配置文件

第二步:Nginx 配置

创建 Nginx 主配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
cat > /root/data/hexo-webhook/nginx/nginx.conf << 'EOF'
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;

# 文件描述符限制
worker_rlimit_nofile 65535;

events {
multi_accept on;
worker_connections 2048;
}

http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
server_tokens off;

# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';

access_log /var/log/nginx/access.log main;

# 请求限制
limit_req_zone $binary_remote_addr zone=example_zone:50m rate=35r/s;
limit_req zone=example_zone burst=100 nodelay;

# 连接限制
limit_conn_zone $binary_remote_addr zone=addr:20m;
limit_conn addr 25;

# 带宽限制
limit_rate_after 50m;
limit_rate 20m;

# 文件缓存
open_file_cache max=2000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;

# 缓存配置
fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:20m max_size=1g inactive=30m;
proxy_cache_path /var/cache/nginx/proxy levels=1:2 keys_zone=my_proxy_cache:20m max_size=1g inactive=30m;

# GZIP 压缩
gzip on;
gzip_static on;
gzip_proxied any;
gzip_vary on;
gzip_comp_level 4;
gzip_buffers 8 256k;
gzip_min_length 50;
gzip_types application/atom+xml application/javascript application/json application/vnd.api+json application/rss+xml
application/vnd.ms-fontobject application/x-font-opentype application/x-font-truetype
application/x-font-ttf application/x-javascript application/xhtml+xml application/xml
font/eot font/opentype font/otf font/truetype image/svg+xml image/vnd.microsoft.icon
image/x-icon image/x-win-bitmap text/css text/richtext text/plain text/x-script
text/x-component text/x-java-source text/x-markdown text/javascript text/xml
application/x-perl application/x-httpd-cgi multipart/bag multipart/mixed application/wasm;

# 超时配置
client_body_timeout 60s;
client_header_timeout 60s;
send_timeout 60s;
keepalive_timeout 120s;
keepalive_requests 8000;

# fastcgi 设置
fastcgi_cache_key "$scheme$request_method$host$request_uri$http_accept_encoding";
fastcgi_cache_methods GET HEAD;
fastcgi_cache_bypass $http_cookie;
fastcgi_no_cache $http_cookie;
fastcgi_cache_valid 200 301 302 120m;
fastcgi_cache_valid 404 10m;
fastcgi_cache_valid 500 502 503 504 0;
fastcgi_cache_lock on;
fastcgi_cache_lock_timeout 5s;
fastcgi_cache_background_update on;
fastcgi_buffering on;
fastcgi_buffer_size 128k;
fastcgi_buffers 16 1024k;
fastcgi_busy_buffers_size 8m;
fastcgi_keep_conn on;
fastcgi_intercept_errors on;

# proxy 设置
proxy_cache_key "$scheme$request_method$host$request_uri$http_accept_encoding";
proxy_cache_methods GET HEAD;
proxy_cache_bypass $http_cookie;
proxy_no_cache $http_cookie;
proxy_cache_valid 200 301 302 120m;
proxy_cache_valid 404 10m;
proxy_cache_valid 500 502 503 504 0;
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;
proxy_cache_background_update on;
proxy_buffering on;
proxy_buffer_size 128k;
proxy_buffers 16 1024k;
proxy_busy_buffers_size 8m;
proxy_intercept_errors on;

# 安全头配置
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy "no-referrer";
add_header Permissions-Policy "geolocation=(), microphone=()";
add_header Vary "Accept-Encoding" always;

# 引入 conf.d 目录中的站点配置
include /etc/nginx/conf.d/*.conf;
}
EOF

创建站点配置文件

配置名字dekviw-blog.conf可以自己随便改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
cat > /root/data/hexo-webhook/nginx/conf.d/dekviw-blog.conf << 'EOF'
server {
listen 80;
server_name localhost;

# 应用全局限制
limit_req zone=example_zone burst=100 nodelay;
limit_conn addr 25;

# 自定义的404页面
error_page 404 /404.html;
location = /404.html {
root /usr/share/nginx/html;
internal;
}

# 处理 /static 路径
location /static/ {
root /usr/share/nginx/html;
expires 1d;
add_header Cache-Control "public";

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

# 处理 /unhxi 路径
location /unhxi/ {
root /usr/share/nginx/html;
autoindex on;
autoindex_exact_size off;
autoindex_localtime on;
expires 1d;

# 下载文件缓存
location ~* \.(zip|rar|7z|tar|gz|bz2|pdf|doc|docx|xls|xlsx|ppt|pptx)$ {
expires 7d;
add_header Cache-Control "public";
}
}

# 根路径及其他子路径配置
location / {
root /usr/share/nginx/html/hexo;
index index.html;
try_files $uri $uri/ =404;

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

# HTML 文件缓存
location ~* \.html$ {
expires 1h;
add_header Cache-Control "public, must-revalidate";
}
}

# 健康检查
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}

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

# 隐藏版本信息
location ~ /\.ht {
deny all;
}
}
EOF

创建 DockerCompose 配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cat > /root/data/hexo-webhook/docker-compose.yml << 'EOF'
version: '3.8'

services:
nginx:
image: nginx:alpine
container_name: hexo-nginx
ports:
- "8080:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./nginx/html:/usr/share/nginx/html:ro
- ./logs/nginx:/var/log/nginx
environment:
- TZ=Asia/Shanghai
restart: unless-stopped
networks:
- hexo-network

networks:
hexo-network:
driver: bridge
EOF

第三步:Webhook 服务器

创建配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cat > /root/data/hexo-webhook/scripts/config.js << 'EOF'
module.exports = {
// Webhook 密钥(请修改为您的安全密钥)
WEBHOOK_SECRET: process.env.WEBHOOK_SECRET || 'your-super-secret-key-here-12345',

// 服务器端口
PORT: process.env.PORT || 3000,

// 路径配置
NGINX_HTML_PATH: '/root/data/hexo-webhook/nginx/html/hexo',

// 日志配置
LOG_LEVEL: process.env.LOG_LEVEL || 'info'
};
EOF

生成安全密钥并写入配置文件

1
SECRET_KEY=$(openssl rand -hex 32) && sed -i "s/'your-super-secret-key-here-12345'/'$SECRET_KEY'/g" /root/data/hexo-webhook/scripts/config.js && echo "=== 生成的密钥(复制到 GitHub Secrets)===" && echo "$SECRET_KEY" && echo "=== 密钥已写入配置文件 ==="

创建 Webhook 服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
cat > /root/data/hexo-webhook/scripts/webhook-server.js << 'EOF'
const express = require('express');
const crypto = require('crypto');
const { exec } = require('child_process');
const fs = require('fs');
const path = require('path');

// 加载配置
const config = require('./config');

const app = express();
const PORT = config.PORT;
const SECRET = config.WEBHOOK_SECRET;
const NGINX_HTML_PATH = config.NGINX_HTML_PATH;

app.use(express.json());

// 验证 webhook 签名
function verifySignature(req, res, next) {
const signature = req.headers['x-hub-signature-256'];
if (!signature) {
return res.status(401).json({ error: 'Missing signature' });
}

const bodyString = JSON.stringify(req.body);
const expectedSignature = 'sha256=' + crypto
.createHmac('sha256', SECRET)
.update(bodyString)
.digest('hex');

if (signature !== expectedSignature) {
return res.status(401).json({ error: 'Invalid signature' });
}

next();
}



// 部署博客
function deployBlog() {
return new Promise((resolve, reject) => {
console.log('开始部署博客...');

// 切换到博客目录
process.chdir(NGINX_HTML_PATH);

// 执行 Git 操作
const commands = [
'git fetch origin',
'git reset --hard origin/main',
'git clean -fd'
];

let currentCommand = 0;

function executeNext() {
if (currentCommand >= commands.length) {
console.log('部署完成');

// 重新创建 .gitignore 文件以保护个性化目录
try {
const gitignoreContent = `# 作者个性化目录(可根据需要调整)
cizhi/
upimg/
rmbg/
qrc/
fyh/
bbd/
bzsm/
nav/
wzq/

# 确保 .gitignore 文件本身不被忽略
!.gitignore
`;

fs.writeFileSync('.gitignore', gitignoreContent);
console.log('.gitignore 文件已重新创建,个性化目录得到保护');
} catch (error) {
console.warn('创建 .gitignore 文件失败:', error.message);
// 不阻止部署流程,只记录警告
}

resolve();
return;
}

const command = commands[currentCommand];
console.log(`执行命令: ${command}`);

exec(command, (error, stdout, stderr) => {
if (error) {
console.error(`命令执行失败: ${command}`, error);
reject(error);
return;
}

if (stdout) console.log(stdout);
if (stderr) console.log(stderr);

currentCommand++;
executeNext();
});
}

executeNext();
});
}



// Webhook 端点
app.post('/webhook', verifySignature, async (req, res) => {
try {
console.log('收到 webhook 请求:', req.body);

// 部署博客
await deployBlog();

// 验证部署是否成功
if (!fs.existsSync(path.join(NGINX_HTML_PATH, 'index.html'))) {
throw new Error('部署后未找到 index.html');
}

res.json({
success: true,
message: '部署成功'
});

} catch (error) {
console.error('Webhook 处理错误:', error);
res.status(500).json({
error: '部署失败',
details: error.message
});
}
});

// 健康检查端点
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
config: {
port: PORT,
nginxPath: NGINX_HTML_PATH
}
});
});

// 启动服务器
app.listen(PORT, () => {
console.log(`Webhook 服务器运行在端口 ${PORT}`);
console.log(`博客路径: ${NGINX_HTML_PATH}`);
console.log(`配置加载完成`);
});
EOF

创建 package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 创建 package.json
cat > /root/data/hexo-webhook/scripts/package.json << 'EOF'
{
"name": "hexo-webhook-server",
"version": "1.0.0",
"description": "Webhook server for Hexo blog deployment",
"main": "webhook-server.js",
"scripts": {
"start": "node webhook-server.js",
"dev": "nodemon webhook-server.js",
"config": "node -e \"console.log(require('./config'))\""
},
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}
EOF

安装依赖并启动服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 安装依赖
cd /root/data/hexo-webhook/scripts
npm install

# 验证配置文件
npm run config

# 使用 PM2 启动 webhook 服务器
pm2 start webhook-server.js --name "hexo-webhook" --log /root/data/hexo-webhook/logs/webhook.log

# 设置 PM2 开机自启
pm2 startup
pm2 save

# 查看服务状态
pm2 status
pm2 logs hexo-webhook

第四步:GitHub Actions 工作流配置

  1. 在\BlogRoot.github 文件夹下新建 workflows 文件夹
  2. 在 workflows 文件夹下新建 autodeploy.yml 文件
  3. 将以下代码复制到 autodeploy.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
name: DEKVIW博客自动部署

on:
push:
branches: [main]
release:
types: [published]

jobs:
deploy:
runs-on: ubuntu-latest

steps:
- name: 检查源码
uses: actions/checkout@v4
with:
ref: main
submodules: recursive

- name: 设置Node.js环境
uses: actions/setup-node@v4
with:
node-version: "18"
cache: "npm"

- name: 设置时区
run: |
export TZ='Asia/Shanghai'
echo "TZ=Asia/Shanghai" >> $GITHUB_ENV

- name: 缓存依赖
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.OS }}-node-

- name: 安装依赖
run: |
npm install hexo-cli -g
npm install

- name: 构建静态文件
run: |
hexo clean
hexo generate

- name: 生成时间戳
run: |
echo "COMMIT_TIME=$(date +'%Z %Y-%m-%d %A %H:%M:%S')" >> $GITHUB_ENV

- name: 部署到GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
personal_token: ${{ secrets.GITHUBTOKEN }}
external_repository: DEKVIW/DEKVIW.github.io
publish_branch: main
publish_dir: ./public
commit_message: ${{ github.event.head_commit.message }} [${{ env.COMMIT_TIME }}]
user_name: ${{ secrets.GITHUBUSERNAME }}
user_email: ${{ secrets.GITHUBEMAIL }}

- name: 通知服务器更新
run: |
# 创建webhook负载
WEBHOOK_PAYLOAD='{"repository":{"name":"DEKVIW.github.io","full_name":"DEKVIW/DEKVIW.github.io"},"ref":"refs/heads/main","head_commit":{"message":"${{ github.event.head_commit.message }}","timestamp":"${{ github.event.head_commit.timestamp }}","id":"${{ github.sha }}"},"pusher":{"name":"${{ github.actor }}"}}'

# 计算签名
SIGNATURE=$(echo -n "$WEBHOOK_PAYLOAD" | openssl dgst -sha256 -hmac "${{ secrets.WEBHOOK_SECRET }}" | cut -d' ' -f2)

# 发送webhook
curl -X POST \
-H "Content-Type: application/json" \
-H "X-Hub-Signature-256: sha256=$SIGNATURE" \
-H "User-Agent: GitHub-Actions-DEKVIW" \
-d "$WEBHOOK_PAYLOAD" \
${{ secrets.WEBHOOK_URL }} \
--max-time 30 \
--retry 2 \
--connect-timeout 10
continue-on-error: true

重要说明:

  • DEKVIW 替换为你的 GitHub 用户名
  • DEKVIW.github.io 替换为你的 GitHub Pages 仓库名
  • 确保 ZDBS 仓库中已安装 Hexo 相关依赖

第五步:GitHub Secrets 配置

Secret 名称 用途 说明
GITHUBTOKEN GitHub Personal Access Token GitHub 仓库访问权限 需要 repo 权限
GITHUBUSERNAME GitHub 用户名 Git 提交信息 GitHub 用户名
GITHUBEMAIL GitHub 邮箱 Git 提交信息 GitHub 邮箱
WEBHOOK_SECRET 服务器生成的密钥 验证 webhook 签名 与服务器端 config.js 保持一致
WEBHOOK_URL http://服务器ip:3000/webhook webhook 请求地址 替换为服务器 IP

获取 GitHub Personal Access Token:

  1. 登录 GitHub,点击右上角头像 → Settings
  2. 左侧菜单选择 “Developer settings” → “Personal access tokens” → “Tokens (classic)”
  3. 点击 “Generate new token” → “Generate new token (classic)”
  4. 设置名称(如:Hexo Blog),选择权限:repo(完整仓库权限)
  5. 点击 “Generate token”,复制生成的 token

配置步骤:

  1. 进入 ZDBS 仓库
  2. 点击 Settings → Secrets and variables → Actions
  3. 点击 “New repository secret”
  4. 添加上述表格的 Secrets

第六步:初始化部署

启动服务

1
2
3
4
5
6
7
8
9
10
11
# 启动 Docker Compose
cd /root/data/hexo-webhook
docker-compose up -d

# 检查服务状态
docker-compose ps
docker-compose logs nginx

# 检查 webhook 服务
pm2 status
pm2 logs hexo-webhook

验证部署

1
2
3
4
5
6
7
8
# 检查目录结构
tree /root/data/hexo-webhook/nginx/html/ -L 2

# 测试 webhook 端点
curl http://192.227.206.48:3000/health

# 测试 Nginx 服务
curl http://192.227.206.48:8080/health

第七步:测试完整流程

本地开发工作流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1. 进入本地博客目录
cd /path/to/your/ZDBS

# 2. 创建或修改文章
hexo new "新文章标题"
# 编辑 source/_posts/新文章标题.md

# 3. 生成静态文件
hexo generate

# 4. 本地预览(可选)
hexo server

# 5. 提交到私有仓库
git add .
git commit -m "添加新文章:新文章标题"
git push origin main

自动化部署流程

  1. GitHub Actions 自动执行

    • 构建 Hexo 静态文件
    • 部署到 GitHub Pages 仓库
    • 发送 webhook 通知到服务器
  2. 服务器自动更新

    • Webhook 服务器接收通知
    • 自动拉取最新代码
    • Nginx 服务更新内容
  3. 验证部署结果

    • GitHub Pages:https://your-username.github.io
    • 服务器:http://服务器ip:8080

常见问题排查

1
2
3
4
5
6
7
8
9
# 检查 SSH 连接
ssh -T git@github.com

# 检查 Git 配置
git config --list

# 检查 Hexo 配置
hexo version
npm list hexo-deployer-git

常用维护命令

注意:以下命令均在项目根目录 /root/data/hexo-webhook 下执行

1
cd /root/data/hexo-webhook

查看服务状态

1
2
3
4
5
6
7
8
# 查看 Docker 容器状态
docker-compose ps

# 查看 PM2 进程状态
pm2 status

# 查看 webhook 服务状态
pm2 show hexo-webhook

查看日志

1
2
3
4
5
6
7
8
9
10
11
# 查看 Nginx 容器日志
docker-compose logs nginx

# 查看 webhook 服务日志
pm2 logs hexo-webhook

# 查看 webhook 错误日志
pm2 logs hexo-webhook --err

# 实时查看日志(按 Ctrl+C 退出)
pm2 logs hexo-webhook --lines 50

重启服务

1
2
3
4
5
6
7
8
# 重启 Nginx 容器
docker-compose restart nginx

# 重启 webhook 服务
pm2 restart hexo-webhook

# 重启所有服务
docker-compose restart && pm2 restart hexo-webhook

排查问题

1
2
3
4
5
6
7
8
9
10
11
# 测试 webhook 端点
curl http://服务器ip:3000/health

# 测试 Nginx 服务
curl http://服务器ip:8080/health

# 检查博客文件是否更新
ls -la nginx/html/hexo/

# 手动触发部署(测试用)
cd nginx/html/hexo && git pull origin main

更新配置

1
2
3
4
5
6
7
8
# 重新加载 Nginx 配置
docker-compose exec nginx nginx -s reload

# 更新 webhook 配置后重启
pm2 restart hexo-webhook

# 查看当前配置
cd scripts && npm run config