Astro博客部署在服务器上并设置自动hook
将 Astro 博客部署到云服务器上有完全的控制权。以下是详细步骤:
1. 服务器环境准备
登录服务器并更新系统
用的是Ubuntu-24.04.1-x64
ssh username@your-server-ip
sudo apt update && sudo apt upgrade -y安装必要软件
# 安装 Node.js(推荐用 nvm 避免权限问题)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
# 重新加载 bash
source ~/.bashrc
# 安装 Node.js LTS
nvm install --lts
nvm use --lts
# 验证安装
node --version
npm --version
2. 安装 Nginx(Web服务器)
sudo apt install nginx -y
sudo systemctl start nginx
sudo systemctl enable nginx3. 安装pnpm
npm install -g pnpm4. 在服务器上创建 Astro 项目
cd /var/www
pnpm create astro@latest --template uxiaohan/vhAstro-Theme astro-blog
cd astro-blog
npm install
npm run build

5. 配置 Nginx
# 创建 Nginx 配置文件
sudo vim /etc/nginx/sites-available/astro-blog基本配置示例
server {
listen 80;
listen [::]:80;
# 你的域名或服务器IP
server_name your-domain.com www.your-domain.com;
# 如果只有 IP,用:server_name your-server-ip;
root /var/www/astro-blog/dist/;
index index.html;
# 启用 gzip 压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/javascript
application/xml+rss
application/atom+xml
image/svg+xml;
location / {
try_files $uri $uri/ $uri.html =404;
}
# 缓存静态资源
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# 防止访问 .astro 等隐藏文件
location ~ /\. {
deny all;
}
}启用网站配置
# 创建符号链接
sudo ln -s /etc/nginx/sites-available/astro-blog /etc/nginx/sites-enabled/
# 测试配置
sudo nginx -t
# 重启 Nginx
sudo systemctl restart nginx
到这里就创建了默认的blog主题的,如果用了主题的就是主题的主页面,然后把md文章丢src下面对应的目录就行。
迁移Hexo文章到Astro
在原本的Hexo文件夹下运行下面的代码,需要安装依赖
pip install python-frontmatter pyyaml -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple下面这个脚本实现替换文件头的功能,可能个别文章还是需要手动替换,不过这个已经很好了。
import os
import re
import datetime
import frontmatter
import yaml
def convert_hexo_to_astro(file_path):
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 检查文件是否已经有 Astro 格式的前言
if 'id:' in content and 'recommend:' in content:
print(f"跳过已转换的文件: {file_path}")
return
# 解析原 frontmatter
post = frontmatter.loads(content)
# 生成英文 ID(基于标题)
title = post.get('title', '未命名文章')
# 移除特殊字符,保留中文字符
id_str = re.sub(r'[^\w\u4e00-\u9fff\s-]', '', str(title))
id_str = re.sub(r'\s+', '-', id_str) # 空格转横线
id_str = id_str.lower().strip('-')[:50] # 转为小写并截断
# 如果 ID 为空,使用文件名
if not id_str:
filename = os.path.basename(file_path).replace('.md', '')
id_str = re.sub(r'[^\w\s-]', '', filename)
id_str = re.sub(r'\s+', '-', id_str).lower()[:50]
# 转换日期格式
if 'date' in post:
if isinstance(post['date'], datetime.datetime):
post['date'] = post['date'].isoformat()
elif isinstance(post['date'], str):
# 尝试多种日期格式
date_formats = [
'%Y-%m-%d %H:%M:%S',
'%Y-%m-%d %H:%M',
'%Y-%m-%d',
'%Y/%m/%d %H:%M:%S',
'%Y/%m/%d'
]
for fmt in date_formats:
try:
dt = datetime.datetime.strptime(post['date'], fmt)
post['date'] = dt.isoformat()
break
except ValueError:
continue
else:
# 如果所有格式都失败,使用当前时间
post['date'] = datetime.datetime.now().isoformat()
# 转换分类格式
if 'categories' in post:
if isinstance(post['categories'], list):
# 如果是列表,转换为逗号分隔的字符串
post['categories'] = ",".join([str(c) for c in post['categories']])
elif not isinstance(post['categories'], str):
# 如果不是字符串,转换为字符串
post['categories'] = str(post['categories'])
else:
# 如果没有分类,设置为默认值
post['categories'] = "未分类"
# 确保标签是列表格式
if 'tags' in post:
if isinstance(post['tags'], str):
# 如果是字符串,尝试按逗号分割
post['tags'] = [tag.strip() for tag in post['tags'].split(',')]
elif not isinstance(post['tags'], list):
# 如果不是列表,转换为列表
post['tags'] = [str(post['tags'])]
else:
# 如果没有标签,设置为空列表
post['tags'] = []
# 添加必填字段(如果不存在)
if 'id' not in post:
post['id'] = id_str
if 'recommend' not in post:
post['recommend'] = False
if 'top' not in post:
post['top'] = False
if 'hide' not in post:
post['hide'] = False
# 确保封面图字段存在
if 'cover' not in post:
post['cover'] = ""
# 保存新格式
new_content = frontmatter.dumps(post)
# 写入文件
with open(file_path, 'w', encoding='utf-8', newline='\n') as f:
f.write(new_content)
print(f"成功转换: {file_path}")
except yaml.YAMLError as e:
print(f"YAML 解析错误 {file_path}: {e}")
# 尝试手动修复格式
manual_fix_yaml(file_path)
except Exception as e:
print(f"处理文件 {file_path} 时出错: {e}")
def manual_fix_yaml(file_path):
"""手动修复 YAML 格式错误"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 提取前言部分
parts = content.split('---', 2)
if len(parts) < 3:
print(f"无法解析文件格式: {file_path}")
return
frontmatter_text = parts[1]
body = parts[2]
# 简单的 YAML 修复
# 1. 确保键值对格式正确
lines = frontmatter_text.split('\n')
fixed_lines = []
for line in lines:
line = line.strip()
if not line:
continue
# 处理键值对
if ':' in line:
key, value = line.split(':', 1)
key = key.strip()
value = value.strip()
# 确保值有引号(如果包含特殊字符)
if value and not (value.startswith('"') and value.endswith('"')):
if any(char in value for char in '[]{}:,'):
value = f'"{value}"'
fixed_lines.append(f"{key}: {value}")
else:
# 处理列表项
if line.startswith('-'):
fixed_lines.append(f" {line}")
else:
fixed_lines.append(line)
# 重新构建前言
fixed_frontmatter = '\n'.join(fixed_lines)
new_content = f"---\n{fixed_frontmatter}\n---\n{body}"
with open(file_path, 'w', encoding='utf-8', newline='\n') as f:
f.write(new_content)
print(f"已尝试手动修复: {file_path}")
# 重新尝试转换
convert_hexo_to_astro(file_path)
except Exception as e:
print(f"手动修复失败 {file_path}: {e}")
def backup_files(directory):
"""备份原始文件"""
import shutil
import time
backup_dir = f"{directory}_backup_{int(time.time())}"
shutil.copytree(directory, backup_dir)
print(f"已创建备份: {backup_dir}")
return backup_dir
# 主程序
if __name__ == "__main__":
blog_dir = './'
# 检查目录是否存在
if not os.path.exists(blog_dir):
print(f"目录不存在: {blog_dir}")
exit(1)
# 备份原始文件
backup_path = backup_files(blog_dir)
print(f"原始文件已备份到: {backup_path}")
# 遍历目录转换所有文件
converted_count = 0
error_count = 0
for root, dirs, files in os.walk(blog_dir):
for file in files:
if file.endswith('.md'):
file_path = os.path.join(root, file)
try:
convert_hexo_to_astro(file_path)
converted_count += 1
except Exception as e:
print(f"处理文件 {file_path} 时发生严重错误: {e}")
error_count += 1
print(f"\n转换完成!")
print(f"成功转换: {converted_count} 个文件")
print(f"转换失败: {error_count} 个文件")
print(f"原始文件备份在: {backup_path}")然后运行一下下面的命令,删除某些不规范的代码块语法。
# C → c
find src/content/blog/ \( -name "*.md" -o -name "*.mdx" \) | while read -r file; do
sed -i 's/```C$/```c/g' "$file"
done
# mysql → sql
find src/content/blog/ \( -name "*.md" -o -name "*.mdx" \) | while read -r file; do
sed -i 's/```mysql$/```sql/g' "$file"
done
# url → plaintext
find src/content/blog/ \( -name "*.md" -o -name "*.mdx" \) | while read -r file; do
sed -i 's/```url$/```plaintext/g' "$file"
donemarmaid支持
添加 Mermaid 支持。导入了 mermaid,添加到 integrations 数组中。
//------其他部分
import mermaid from 'astro-mermaid';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
//------其他部分
integrations: [
swup({
theme: false,
animationClass: "vh-animation-",
containers: [".main-inner>.main-inner-content", '.vh-header>.main'],
smoothScrolling: true,
progress: true,
cache: true,
preload: true,
accessibility: true,
updateHead: true,
updateBodyClass: false,
globalInstance: true
}),
// 添加 Mermaid 支持
mermaid({
theme: 'default', // 或 'forest', 'dark', 'neutral' 等
}),
//------其他部分
Compress({ Image: false, Action: { Passed: async () => true } }),
sitemap({
// 处理末尾带 / 的 url
serialize: (item) => ({ ...item, url: item.url.endsWith('/') ? 6. 使用 Let’s Encrypt 配置 HTTPS(可选但推荐)
# 安装 Certbot
sudo apt install certbot python3-certbot-nginx -y
# 获取证书不要邮箱
sudo certbot --nginx -d your-domain.com -d www.your-domain.com --register-unsafely-without-email --agree-tos
# 设置自动续期
sudo certbot renew --dry-run7. 配置防火墙和设置专用用户
# 如果使用 UFW
sudo ufw allow 'Nginx Full'
sudo ufw allow OpenSSH
sudo ufw enable
# 打开hook的端口
sudo ufw allow 9000/tcp
sudo ufw status第1步:创建专用用户
# 创建系统用户 blogdeploy(不创建家目录,不允许登录)
sudo adduser --system --group --no-create-home blogdeploy
# 验证用户创建成功
id blogdeploy
# 应该显示:uid=xxx(blogdeploy) gid=xxx(blogdeploy) groups=xxx(blogdeploy)第2步:设置目录权限
# 将项目目录所有权给 blogdeploy
sudo chown -R blogdeploy:blogdeploy /var/www/astro-blog
# 或者如果 pnpm 装在其他地方
which pnpm # 先查看 pnpm 位置
# 给 blogdeploy 用户访问 pnpm 的权限
sudo chown -R blogdeploy:blogdeploy /root/.local/share/pnpm
# dist 目录特殊处理:让 www-data(Nginx用户)有读取权限
sudo chown -R blogdeploy:www-data /var/www/astro-blog/dist
sudo chmod -R 755 /var/www/astro-blog/dist第3步:配置 blogdeploy 用户的 Git
# 直接在项目里配置(只对这个仓库生效)
cd /var/www/astro-blog
sudo -u blogdeploy git config user.name "Blog Deploy"
sudo -u blogdeploy git config user.email "deploy@ruajingjing.top"
sudo -u blogdeploy git config --add safe.directory /var/www/astro-blog
# 验证
sudo -u blogdeploy git config --list
# 切换到 blogdeploy 用户(临时)
sudo -u blogdeploy bash
# 测试能否访问仓库
cd /var/www/astro-blog
git status
git pull origin astro-blog
# 退出 blogdeploy 用户
exit第4步:确保 blogdeploy 能使用 pnpm
echo "安装 nvm..."
# 通过 sudo -u 执行安装脚本
sudo -u blogdeploy bash << 'EOF'
# 下载并安装 nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
# 设置 nvm 环境变量
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# 安装 Node.js LTS
echo "安装 Node.js LTS 版本..."
nvm install --lts
nvm use --lts
# 验证安装
echo "Node.js 版本:"
node --version
echo "npm 版本:"
npm --version
EOF
# 4. 重新安装 pnpm(使用正确的方法)
echo "重新安装 pnpm..."
sudo -u blogdeploy bash << 'EOF'
# 加载 nvm
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# 确保使用正确的 Node.js 版本
nvm use --lts
# 卸载可能存在的问题 pnpm 安装
npm uninstall -g pnpm 2>/dev/null || true
# 清除 npm 缓存
npm cache clean --force
# 使用正确方法安装 pnpm
npm install -g pnpm --prefix=/home/blogdeploy/.npm-packages
# 创建 pnpm 专用配置
mkdir -p ~/.pnpm
cat > ~/.pnpm/config.json << 'PNPM_CONFIG'
{
"store-dir": "/home/blogdeploy/.pnpm-store",
"global-dir": "/home/blogdeploy/.npm-packages/lib/node_modules"
}
PNPM_CONFIG
# 设置 PATH(不修改 .bashrc,使用环境变量)
export PATH="/home/blogdeploy/.npm-packages/bin:$PATH"
# 验证安装
echo "pnpm 版本:"
pnpm --version 2>/dev/null || echo "pnpm 未正确安装"
echo "pnpm 存储路径:"
pnpm store path 2>/dev/null || echo "无法获取存储路径"
EOF
# 5. 创建安全的部署环境配置
echo "创建部署环境配置..."
sudo -u blogdeploy bash << 'EOF'
# 创建部署脚本使用的环境文件
cat > ~/.blogdeploy.env << 'DEPLOY_ENV'
export NVM_DIR="/home/blogdeploy/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
export PATH="/home/blogdeploy/.npm-packages/bin:$PATH"
DEPLOY_ENV
echo "已创建部署环境配置文件"
EOF
echo "=== 环境验证 ==="
sudo -u blogdeploy bash << 'EOF'
echo "1. 用户: \$USER"
echo "2. 家目录: \$HOME"
echo ""
echo "3. Node.js 版本:"
node --version 2>/dev/null || echo "未安装"
echo ""
echo "4. npm 版本:"
npm --version 2>/dev/null || echo "未安装"
echo ""
echo "5. pnpm 版本:"
pnpm --version 2>/dev/null || echo "未安装"
echo ""
echo "6. pnpm 存储路径:"
pnpm store path 2>/dev/null || echo "无法获取"
echo ""
echo "7. 当前 PATH:"
echo \$PATH
EOF
第5步:测试完整部署流程
# 以 blogdeploy 用户身份测试部署
sudo -u blogdeploy bash -c "cd /var/www/astro-blog && git pull origin astro-blog && pnpm install && pnpm build"
cd /var/www/astro-blog
sudo -u blogdeploy git remote set-url origin https://gitee.com/xxx/my-blog.git
sudo -u blogdeploy git pull origin astro-blog
# 检查是否成功
ls -la /var/www/astro-blog/dist第6步:验证权限
# 检查关键目录权限
ls -la /var/www/astro-blog
# 应该显示 blogdeploy blogdeploy
ls -la /var/www/astro-blog/dist
# 应该显示 blogdeploy www-data 或 www-data www-data
# 检查 Nginx 能否读取
sudo -u www-data cat /var/www/astro-blog/dist/index.html
# 应该能正常显示内容常见问题排查
如果遇到权限错误
# 重新设置权限
sudo chown -R blogdeploy:blogdeploy /var/www/astro-blog
sudo chown -R blogdeploy:www-data /var/www/astro-blog/dist
sudo chmod -R 755 /var/www/astro-blog如果 Git 报错
# 设置 Git 安全目录
sudo -u blogdeploy git config --global --add safe.directory /var/www/astro-blog如果 pnpm 找不到
# 检查 pnpm 路径
which pnpm
# 如果在 /root/.local/share/pnpm,需要创建链接或重装8. 切换git分支迁移
1. 初始化并关联远程仓库
cd /var/www/astro-blog
# 如果还没初始化 git
git init
# 添加 Gitee 远程仓库
git remote add origin https://xxxx.git
# 查看远程仓库
git remote -v2. 创建新分支(推荐叫 astro)
# 创建并切换到新分支
git checkout -b astro3. 添加 .gitignore
创建 .gitignore 文件,避免提交不必要的文件:
nano .gitignore粘贴以下内容:
# 依赖
node_modules/
.pnpm-store/
# 构建产物
dist/
.astro/
# 环境变量
.env
.env.local
.env.production
# 日志
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
# 操作系统
.DS_Store
Thumbs.db
# 编辑器
.vscode/
.idea/
*.swp
*.swo
*~
# 临时文件
*.log
.cache/4. 提交代码
# 添加所有文件
git add .
# 查看状态
git status
# 提交
git commit -m "feat: 迁移到 Astro 博客框架
- 使用 vhAstro-Theme 主题
- 配置网站信息和备案
- 添加 TOC 目录功能
- 迁移所有文章内容"5. 推送到 Gitee
# 首次推送(会创建远程分支)
git push -u origin astro
# 如果需要输入用户名密码,使用 Gitee 账号6. 如果推送失败(需要先拉取)
# 拉取远程仓库
git pull origin master --allow-unrelated-histories
# 然后再推送
git push -u origin astro后续更新流程
以后更新博客:
# 添加修改
git add .
# 提交
git commit -m "update: 更新内容描述"
# 推送
git push如果想保留 Hexo 分支
# 查看所有分支
git branch -a
# master 分支保留 Hexo 内容
# astro 分支是新的 Astro 博客配置 Git 用户信息(如果还没配置)
git config --global user.name "你的名字"
git config --global user.email "your-email@example.com"常见问题
如果提示认证失败
Gitee 可能需要使用个人访问令牌:
- 访问:https://gitee.com/profile/personal_access_tokens
- 生成新令牌
- 使用令牌作为密码
或者配置 SSH:
# 生成 SSH 密钥
ssh-keygen -t rsa -C "your-email@example.com"
# 查看公钥
cat ~/.ssh/id_rsa.pub
# 复制公钥,添加到 Gitee SSH 设置然后改用 SSH 地址:
git remote set-url origin git@gitee.com:xxx.git验证步骤
执行每步后检查:
# 1. 查看当前分支
git branch
# 2. 查看远程仓库
git remote -v
# 3. 查看提交历史
git log --oneline
# 4. 查看状态
git status9. 使用 Git 自动化部署(推荐)
完美!✅ 现在继续配置 Webhook。
第1步:安装 webhook 工具
sudo apt update
sudo apt install webhook -y
# 验证安装
webhook --version第2步:生成强密钥
# 生成一个强密钥用于 Webhook 验证
openssl rand -hex 32
# 记下这个密钥,例如:
# a8f5e2c9d3b7f1e4a6c8d9e2f3a1b5c7d4e6f8a1b2c3d4e5f6a7b8c9d0e1f2a3把生成的密钥保存好,待会要用。
第3步:创建部署脚本
# 创建脚本目录
sudo mkdir -p /var/www/deploy-scripts
# 创建部署脚本
sudo vim /var/www/deploy-scripts/deploy-blog.sh粘贴以下内容:
#!/bin/bash
# 日志文件
LOG_FILE="/var/log/astro-blog-deploy.log"
REPO_DIR="/var/www/astro-blog"
# 记录开始
echo "========================================" >> $LOG_FILE
echo "部署开始: $(date)" >> $LOG_FILE
echo "触发者: Gitee Webhook" >> $LOG_FILE
# 切换到项目目录
cd $REPO_DIR || {
echo "ERROR: 无法进入项目目录" >> $LOG_FILE
exit 1
}
# 验证 Git 仓库完整性
echo "验证仓库完整性..." >> $LOG_FILE
if ! git fsck --no-progress >> $LOG_FILE 2>&1; then
echo "ERROR: Git 仓库损坏,终止部署" >> $LOG_FILE
exit 1
fi
# 拉取代码
echo "拉取代码..." >> $LOG_FILE
git fetch origin astro-blog >> $LOG_FILE 2>&1
git reset --hard origin/astro-blog >> $LOG_FILE 2>&1
# 清理旧依赖
echo "清理依赖..." >> $LOG_FILE
rm -rf node_modules >> $LOG_FILE 2>&1
# 安装依赖
echo "安装依赖..." >> $LOG_FILE
pnpm install --frozen-lockfile >> $LOG_FILE 2>&1
# 审计依赖漏洞
echo "审计依赖..." >> $LOG_FILE
pnpm audit --audit-level=high >> $LOG_FILE 2>&1 || {
echo "WARNING: 发现高危漏洞" >> $LOG_FILE
}
# 构建
echo "构建项目..." >> $LOG_FILE
pnpm build >> $LOG_FILE 2>&1
# 验证构建产物
if [ ! -d "dist" ]; then
echo "ERROR: 构建失败,dist目录不存在" >> $LOG_FILE
exit 1
fi
# 设置正确的权限
# chown -R blogdeploy:www-data dist/
# chmod -R 755 dist/
echo "部署成功: $(date)" >> $LOG_FILE
echo "========================================" >> $LOG_FILE保存后设置权限:
# 给脚本执行权限
sudo chmod +x /var/www/deploy-scripts/deploy-blog.sh
# 设置所有者为 blogdeploy
sudo chown blogdeploy:blogdeploy /var/www/deploy-scripts/deploy-blog.sh
# 创建日志文件
sudo touch /var/log/astro-blog-deploy.log
sudo chown blogdeploy:blogdeploy /var/log/astro-blog-deploy.log第4步:测试部署脚本
# 以 blogdeploy 用户身份测试脚本
sudo -u blogdeploy /var/www/deploy-scripts/deploy-blog.sh
# 查看日志
tail -30 /var/log/astro-blog-deploy.log如果成功,会看到 “部署成功” 的消息。
如果解决构建错误
(Astro 想访问 /nonexistent/.config,但 blogdeploy 没有家目录)。
# 给 blogdeploy 创建配置目录
sudo mkdir -p /home/blogdeploy/.config
sudo chown -R blogdeploy:blogdeploy /home/blogdeploy
sudo usermod -d /home/blogdeploy blogdeploy
# 或者禁用 Astro 遥测
cd /var/www/astro-blog
sudo -u blogdeploy npx astro telemetry disable
# 再次测试部署
sudo -u blogdeploy /var/www/deploy-scripts/deploy-blog.sh测试成功后继续!
为 blogdeploy 用户安装独立的 pnpm:
bash
# 1. 为 blogdeploy 创建 home 目录(如果还没有)
sudo mkdir -p /home/blogdeploy
sudo chown blogdeploy:blogdeploy /home/blogdeploy
# 2. 切换到 blogdeploy 用户并安装 pnpm
sudo -u blogdeploy bash << 'EOF'
cd ~
curl -fsSL https://get.pnpm.io/install.sh | sh -
source ~/.bashrc
pnpm --version
EOF
# 3. 验证 pnpm 可用
sudo -u blogdeploy bash -c 'export PNPM_HOME="/home/blogdeploy/.local/share/pnpm"; export PATH="$PNPM_HOME:$PATH"; pnpm --version'
# 4. 更新部署脚本(使用上面方案2的脚本内容)
# 5. 修复 Git 警告
sudo chown -R blogdeploy:blogdeploy /home/blogdeploy/.config
# 6. 测试脚本
sudo -u blogdeploy /var/www/deploy-scripts/deploy-blog-fast.sh
# 7. 查看日志
tail -30 /var/log/astro-blog-deploy.log
# 8. 重启 webhook 服务
sudo systemctl restart webhook执行完这些步骤后,再推送一次代码测试。这次应该就能成功构建了!🚀
优化脚本(跳过依赖安装)
创建一个快速部署脚本:
sudo vim /var/www/deploy-scripts/deploy-blog-fast.sh#!/bin/bash
# 设置完整的环境变量
export HOME=/home/blogdeploy
export NVM_DIR="$HOME/.nvm"
export PATH="$HOME/.npm-global/bin:$PATH"
# 加载 nvm 环境
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# 日志文件
LOG_FILE="/var/log/astro-blog-deploy.log"
REPO_DIR="/var/www/astro-blog"
# 记录开始
{
echo "========================================"
echo "快速部署开始: $(date)"
echo "用户: $(whoami)"
echo "HOME: $HOME"
echo "PATH: $PATH"
echo "NVM_DIR: $NVM_DIR"
echo "Node版本: $(node --version 2>&1)"
echo "npm版本: $(npm --version 2>&1)"
echo "PNPM位置: $(which pnpm 2>&1)"
echo "PNPM版本: $(pnpm --version 2>&1)"
} >> $LOG_FILE 2>&1
# 切换到项目目录
cd $REPO_DIR || {
echo "ERROR: 无法进入项目目录 $REPO_DIR" >> $LOG_FILE
exit 1
}
# 拉取代码
echo "拉取代码..." >> $LOG_FILE
git fetch origin astro-blog >> $LOG_FILE 2>&1
# 检查是否有更新
LOCAL=$(git rev-parse HEAD 2>/dev/null)
REMOTE=$(git rev-parse origin/astro-blog 2>/dev/null)
if [ "$LOCAL" = "$REMOTE" ] && [ -n "$LOCAL" ]; then
echo "没有更新,跳过部署" >> $LOG_FILE
exit 0
fi
git reset --hard origin/astro-blog >> $LOG_FILE 2>&1
# 清理旧的构建文件
echo "清理旧构建..." >> $LOG_FILE
rm -rf dist >> $LOG_FILE 2>&1
echo "构建项目..." >> $LOG_FILE
pnpm run build 2>&1 | tee -a $LOG_FILE
# 验证构建产物
if [ ! -d "dist" ]; then
echo "ERROR: 构建失败,dist 目录未生成" >> $LOG_FILE
echo "尝试使用完整构建命令..." >> $LOG_FILE
# 尝试使用 npx
npx astro build 2>&1 | tee -a $LOG_FILE
fi
# 再次验证
if [ ! -d "dist" ]; then
echo "ERROR: 构建失败" >> $LOG_FILE
exit 1
fi
echo "构建成功!" >> $LOG_FILE
echo "构建文件:" >> $LOG_FILE
ls -la dist/ 2>&1 | head -10 >> $LOG_FILE
echo "快速部署成功: $(date)" >> $LOG_FILE
echo "========================================" >> $LOG_FILE设置权限:
sudo chmod +x /var/www/deploy-scripts/deploy-blog-fast.sh
sudo chown blogdeploy:blogdeploy /var/www/deploy-scripts/deploy-blog-fast.sh第5步:配置 Webhook
这里用的gitee的hook,每个代码仓库的hook规则可能不一样,需要翻看文档
# 创建 webhook 配置文件
sudo vim /etc/webhook.conf粘贴(记得把密钥改成你自己的):
[
{
"id": "deploy-blog",
"execute-command": "/var/www/deploy-scripts/deploy-blog-fast.sh",
"command-working-directory": "/var/www/astro-blog",
"response-message": "正在部署博客,请稍候...",
"trigger-rule": {
"and": [
{
"match": {
"type": "value",
"value": "你的密码",
"parameter": {
"source": "header",
"name": "X-Gitee-Token"
}
}
},
{
"match": {
"type": "value",
"value": "refs/heads/astro-blog",
"parameter": {
"source": "payload",
"name": "ref"
}
}
}
]
}
}
]第6步:创建 Webhook systemd 服务
sudo vim /etc/systemd/system/webhook.service使用全局安装就不需要指定变量了
[Unit]
Description=Webhook Service for Blog Auto Deploy
After=network.target
[Service]
Type=simple
User=blogdeploy
Group=blogdeploy
Environment="HOME=/home/blogdeploy"
ExecStart=/usr/bin/webhook -hooks /etc/webhook.conf -verbose -port 9000
Restart=always
RestartSec=10
# 安全限制
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/www/astro-blog /var/log /home/blogdeploy
[Install]
WantedBy=multi-user.target第7步:启动 Webhook 服务
# 重载 systemd
sudo systemctl daemon-reload
# 启动服务
sudo systemctl start webhook
# 设置开机自启
sudo systemctl enable webhook
# 查看状态
sudo systemctl status webhook
# 查看日志
sudo journalctl -u webhook -f应该看到类似:
● webhook.service - Webhook Service for Blog Auto Deploy
Loaded: loaded
Active: active (running)继续下一步!🚀
修改 Webhook 配置使用快速脚本
sudo vim /etc/webhook.conf把 "execute-command" 改为:
{
"id": "deploy-blog",
"execute-command": "/var/www/deploy-scripts/deploy-blog-fast.sh",
...
}重启 Webhook 服务
sudo systemctl restart webhook
sudo systemctl status webhook现在有两个脚本
- 快速部署(只更新文章时用):
deploy-blog-fast.sh- 只拉代码 + 构建 - 完整部署(更新依赖时用):
deploy-blog.sh- 重装依赖 + 构建
Webhook 用快速的,手动完整部署时:
sudo -u blogdeploy /var/www/deploy-scripts/deploy-blog.sh测试快速脚本
sudo -u blogdeploy /var/www/deploy-scripts/deploy-blog-fast.sh
tail -20 /var/log/astro-blog-deploy.log应该几十秒就完成了!继续配置 Gitee Webhook 吧?🚀
关于 Git 警告
顺便修复 Git 的权限警告:
# 创建配置目录
sudo mkdir -p /home/blogdeploy/.config/git
# 创建空文件
sudo touch /home/blogdeploy/.config/git/ignore
sudo touch /home/blogdeploy/.config/git/attributes
# 设置权限
sudo chown -R blogdeploy:blogdeploy /home/blogdeploy/.config
sudo chmod -R 755 /home/blogdeploy/.config
# 确保目录存在并有正确权限
sudo mkdir -p /home/blogdeploy/.config/git
sudo touch /home/blogdeploy/.config/git/ignore
sudo touch /home/blogdeploy/.config/git/attributes
sudo chown -R blogdeploy:blogdeploy /home/blogdeploy
sudo chmod -R 755 /home/blogdeploy配置 Gitee Webhook
在服务器安全组开放端口
登录你的服务器控制台(阿里云/腾讯云等):
- 找到”安全组规则”
- 添加入站规则:
- 端口:
9000 - 协议:
TCP - 来源:
0.0.0.0/0(或只允许 Gitee IP)
- 端口:
第1步:访问 Gitee Webhook 设置
打开浏览器,访问:
https://gitee.com/bugatti100Peagle/my-blog/hooks第2步:添加 WebHook
点击页面上的 “添加 WebHook” 或 “管理 WebHook” → “添加”
第3步:填写配置
填写以下信息:
http://你的服务器IP:9000/hooks/deploy-blog例如:http://123.45.67.89:9000/hooks/deploy-blog
密码令牌
粘贴你之前生成的密钥(openssl rand -hex 32 那个)
如果忘了,重新生成一个:
openssl rand -hex 32就是/etc/webhook.conf 里的 secret 值
事件选择
- ✅ 勾选
Push - ⬜ 其他不勾选
其他选项
- SSL 验证:关闭(因为用的是 HTTP)
- 激活 WebHook:✅ 勾选
第4步:保存并测试
点击 “添加” 或 “确定”
然后点击刚创建的 Webhook 右侧的 “测试” 按钮
验证是否成功
方法1:查看 Gitee 界面
测试后,Gitee 会显示:
- ✅ 成功:状态码 200,响应 “正在部署博客,请稍候…”
- ❌ 失败:显示错误信息
方法2:查看服务器日志
# 查看 webhook 日志
sudo journalctl -u webhook -f
# 查看部署日志
tail -f /var/log/astro-blog-deploy.log如果成功,你会看到类似:
[webhook] 2025/12/13 xx:xx:xx [deploy-blog] 200 | deploy-blog got matched
[webhook] 2025/12/13 xx:xx:xx [deploy-blog] executing /var/www/deploy-scripts/deploy-blog-fast.sh第5步:真实测试
在本地修改一篇文章,然后:
git add .
git commit -m "test: 测试自动部署"
git push origin astro-blog然后:
- 观察服务器日志:
tail -f /var/log/astro-blog-deploy.log - 应该会自动开始部署
- 完成后访问你的网站验证更新
常见问题
如果 Gitee 提示连接失败
# 确认 webhook 服务正在运行
sudo systemctl status webhook
# 确认端口开放
sudo netstat -tlnp | grep 9000
# 测试本地访问
curl http://localhost:9000/hooks/deploy-blog如果 Gitee 提示 403 或验证失败
检查密钥是否一致:
# 查看配置文件中的密钥
sudo cat /etc/webhook.conf | grep secret确保和 Gitee 填的一样。
修复 dist 目录权限
# 1. 确保 blogdeploy 对整个项目目录有写权限
sudo chown -R blogdeploy:blogdeploy /var/www/astro-blog
# 2. 特别是 dist 目录
sudo rm -rf /var/www/astro-blog/dist
sudo mkdir -p /var/www/astro-blog/dist
sudo chown -R blogdeploy:blogdeploy /var/www/astro-blog/dist
# 3. 确保 node_modules 也有正确权限
sudo chown -R blogdeploy:blogdeploy /var/www/astro-blog/node_modules
# 4. 测试构建
sudo -u blogdeploy bash /var/www/deploy-scripts/deploy-blog-fast.sh
# 5. 查看日志
tail -50 /var/log/astro-blog-deploy.log安全增强(可选)
如果还想更安全,可以加上 IP 白名单:
{
"trigger-rule": {
"and": [
{
"match": {
"type": "value",
"value": "你的密码",
"parameter": {
"source": "header",
"name": "X-Gitee-Token"
}
}
},
{
"match": {
"type": "value",
"value": "refs/heads/astro-blog",
"parameter": {
"source": "payload",
"name": "ref"
}
}
},
{
"match": {
"type": "ip-whitelist",
"ip-range": "180.97.0.0/16,116.211.0.0/16"
}
}
]
}
}10. 监控和维护
查看 Nginx 日志
# 实时查看访问日志
sudo tail -f /var/log/nginx/access.log
# 查看错误日志
sudo tail -f /var/log/nginx/error.log创建服务状态检查脚本
#!/bin/bash
# check-status.sh
echo "=== 系统状态检查 ==="
echo "Nginx 状态:"
sudo systemctl status nginx --no-pager
echo -e "\n磁盘使用:"
df -h
echo -e "\n内存使用:"
free -h
echo -e "\n最近访问日志:"
sudo tail -20 /var/log/nginx/access.log12. 常用命令总结
# 查看 Nginx 状态
sudo systemctl status nginx
# 重启 Nginx
sudo systemctl restart nginx
# 重载配置
sudo systemctl reload nginx
# 查看 Nginx 配置语法
sudo nginx -t
# 查看网站访问
sudo tail -f /var/log/nginx/access.log13. 备份脚本
#!/bin/bash
# backup.sh
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/home/username/backups"
SOURCE_DIR="/var/www/myblog"
# 创建备份目录
mkdir -p $BACKUP_DIR
# 备份网站文件
tar -czf $BACKUP_DIR/myblog_$DATE.tar.gz $SOURCE_DIR
# 备份数据库(如果有)
# mysqldump -u username -p dbname > $BACKUP_DIR/db_$DATE.sql
# 保留最近7天备份
find $BACKUP_DIR -name "myblog_*.tar.gz" -mtime +7 -delete
echo "备份完成: $BACKUP_DIR/myblog_$DATE.tar.gz"14. 问题排查
常见问题解决:
- 403 Forbidden 错误
# 检查文件权限
sudo chown -R www-data:www-data /var/www/myblog
sudo chmod -R 755 /var/www/myblog- 502 Bad Gateway 错误
# 检查端口占用
sudo netstat -tulpn | grep :80
sudo systemctl restart nginx- 内存不足
# 增加 swap
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab部署流程总结:
- ✅ 服务器:Ubuntu 24.04 LTS
- ✅ Web服务器:Nginx
- ✅ 运行环境:Node.js (nvm)
- ✅ 项目目录:
/var/www/myblog - ✅ SSL证书:Let’s Encrypt
- ✅ 自动化:Git hooks
- ✅ 监控:日志和 systemd