将 Astro 博客部署到云服务器上有完全的控制权。以下是详细步骤:
1. 服务器环境准备
登录服务器并更新系统
用的是Ubuntu-24.04.1-x64
ssh username@your-server-ip
c
安装必要软件
# 安装 Node.js(推荐用 nvm 避免权限问题)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
# 掉线的话用镜像
curl -o- https://gh-proxy.org/https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
# 重新加载 bash
source ~/.bashrc
# 安装 Node.js LTS
# 如果安装慢用镜像
export NVM_NODEJS_ORG_MIRROR=https://npmmirror.com/mirrors/node
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 nginx
3. 安装pnpm
npm install -g pnpm
4. 在服务器上创建 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文件夹下运行下面的代码,需要安装依赖
1pip install python-frontmatter pyyaml -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple
下面这个脚本实现替换文件头的功能,可能个别文章还是需要手动替换,不过这个已经很好了。
1import os
2import re
3import datetime
4import frontmatter
5import yaml
6
7def convert_hexo_to_astro(file_path):
8 try:
9 with open(file_path, 'r', encoding='utf-8') as f:
10 content = f.read()
11
12 # 检查文件是否已经有 Astro 格式的前言
13 if 'id:' in content and 'recommend:' in content:
14 print(f"跳过已转换的文件: {file_path}")
15 return
16
17 # 解析原 frontmatter
18 post = frontmatter.loads(content)
19
20 # 生成英文 ID(基于标题)
21 title = post.get('title', '未命名文章')
22 # 移除特殊字符,保留中文字符
23 id_str = re.sub(r'[^\w\u4e00-\u9fff\s-]', '', str(title))
24 id_str = re.sub(r'\s+', '-', id_str) # 空格转横线
25 id_str = id_str.lower().strip('-')[:50] # 转为小写并截断
26
27 # 如果 ID 为空,使用文件名
28 if not id_str:
29 filename = os.path.basename(file_path).replace('.md', '')
30 id_str = re.sub(r'[^\w\s-]', '', filename)
31 id_str = re.sub(r'\s+', '-', id_str).lower()[:50]
32
33 # 转换日期格式
34 if 'date' in post:
35 if isinstance(post['date'], datetime.datetime):
36 post['date'] = post['date'].isoformat()
37 elif isinstance(post['date'], str):
38 # 尝试多种日期格式
39 date_formats = [
40 '%Y-%m-%d %H:%M:%S',
41 '%Y-%m-%d %H:%M',
42 '%Y-%m-%d',
43 '%Y/%m/%d %H:%M:%S',
44 '%Y/%m/%d'
45 ]
46 for fmt in date_formats:
47 try:
48 dt = datetime.datetime.strptime(post['date'], fmt)
49 post['date'] = dt.isoformat()
50 break
51 except ValueError:
52 continue
53 else:
54 # 如果所有格式都失败,使用当前时间
55 post['date'] = datetime.datetime.now().isoformat()
56
57 # 转换分类格式
58 if 'categories' in post:
59 if isinstance(post['categories'], list):
60 # 如果是列表,转换为逗号分隔的字符串
61 post['categories'] = ",".join([str(c) for c in post['categories']])
62 elif not isinstance(post['categories'], str):
63 # 如果不是字符串,转换为字符串
64 post['categories'] = str(post['categories'])
65 else:
66 # 如果没有分类,设置为默认值
67 post['categories'] = "未分类"
68
69 # 确保标签是列表格式
70 if 'tags' in post:
71 if isinstance(post['tags'], str):
72 # 如果是字符串,尝试按逗号分割
73 post['tags'] = [tag.strip() for tag in post['tags'].split(',')]
74 elif not isinstance(post['tags'], list):
75 # 如果不是列表,转换为列表
76 post['tags'] = [str(post['tags'])]
77 else:
78 # 如果没有标签,设置为空列表
79 post['tags'] = []
80
81 # 添加必填字段(如果不存在)
82 if 'id' not in post:
83 post['id'] = id_str
84
85 if 'recommend' not in post:
86 post['recommend'] = False
87
88 if 'top' not in post:
89 post['top'] = False
90
91 if 'hide' not in post:
92 post['hide'] = False
93
94 # 确保封面图字段存在
95 if 'cover' not in post:
96 post['cover'] = ""
97
98 # 保存新格式
99 new_content = frontmatter.dumps(post)
100
101 # 写入文件
102 with open(file_path, 'w', encoding='utf-8', newline='\n') as f:
103 f.write(new_content)
104
105 print(f"成功转换: {file_path}")
106
107 except yaml.YAMLError as e:
108 print(f"YAML 解析错误 {file_path}: {e}")
109 # 尝试手动修复格式
110 manual_fix_yaml(file_path)
111 except Exception as e:
112 print(f"处理文件 {file_path} 时出错: {e}")
113
114def manual_fix_yaml(file_path):
115 """手动修复 YAML 格式错误"""
116 try:
117 with open(file_path, 'r', encoding='utf-8') as f:
118 content = f.read()
119
120 # 提取前言部分
121 parts = content.split('---', 2)
122 if len(parts) < 3:
123 print(f"无法解析文件格式: {file_path}")
124 return
125
126 frontmatter_text = parts[1]
127 body = parts[2]
128
129 # 简单的 YAML 修复
130 # 1. 确保键值对格式正确
131 lines = frontmatter_text.split('\n')
132 fixed_lines = []
133
134 for line in lines:
135 line = line.strip()
136 if not line:
137 continue
138
139 # 处理键值对
140 if ':' in line:
141 key, value = line.split(':', 1)
142 key = key.strip()
143 value = value.strip()
144
145 # 确保值有引号(如果包含特殊字符)
146 if value and not (value.startswith('"') and value.endswith('"')):
147 if any(char in value for char in '[]{}:,'):
148 value = f'"{value}"'
149
150 fixed_lines.append(f"{key}: {value}")
151 else:
152 # 处理列表项
153 if line.startswith('-'):
154 fixed_lines.append(f" {line}")
155 else:
156 fixed_lines.append(line)
157
158 # 重新构建前言
159 fixed_frontmatter = '\n'.join(fixed_lines)
160 new_content = f"---\n{fixed_frontmatter}\n---\n{body}"
161
162 with open(file_path, 'w', encoding='utf-8', newline='\n') as f:
163 f.write(new_content)
164
165 print(f"已尝试手动修复: {file_path}")
166
167 # 重新尝试转换
168 convert_hexo_to_astro(file_path)
169
170 except Exception as e:
171 print(f"手动修复失败 {file_path}: {e}")
172
173def backup_files(directory):
174 """备份原始文件"""
175 import shutil
176 import time
177
178 backup_dir = f"{directory}_backup_{int(time.time())}"
179 shutil.copytree(directory, backup_dir)
180 print(f"已创建备份: {backup_dir}")
181 return backup_dir
182
183# 主程序
184if __name__ == "__main__":
185 blog_dir = './'
186
187 # 检查目录是否存在
188 if not os.path.exists(blog_dir):
189 print(f"目录不存在: {blog_dir}")
190 exit(1)
191
192 # 备份原始文件
193 backup_path = backup_files(blog_dir)
194 print(f"原始文件已备份到: {backup_path}")
195
196 # 遍历目录转换所有文件
197 converted_count = 0
198 error_count = 0
199
200 for root, dirs, files in os.walk(blog_dir):
201 for file in files:
202 if file.endswith('.md'):
203 file_path = os.path.join(root, file)
204 try:
205 convert_hexo_to_astro(file_path)
206 converted_count += 1
207 except Exception as e:
208 print(f"处理文件 {file_path} 时发生严重错误: {e}")
209 error_count += 1
210
211 print(f"\n转换完成!")
212 print(f"成功转换: {converted_count} 个文件")
213 print(f"转换失败: {error_count} 个文件")
214 print(f"原始文件备份在: {backup_path}")
然后运行一下下面的命令,删除某些不规范的代码块语法。
1# C → c
2find src/content/blog/ \( -name "*.md" -o -name "*.mdx" \) | while read -r file; do
3 sed -i 's/```C$/```c/g' "$file"
4done
5
6# mysql → sql
7find src/content/blog/ \( -name "*.md" -o -name "*.mdx" \) | while read -r file; do
8 sed -i 's/```mysql$/```sql/g' "$file"
9done
10
11# url → plaintext
12find src/content/blog/ \( -name "*.md" -o -name "*.mdx" \) | while read -r file; do
13 sed -i 's/```url$/```plaintext/g' "$file"
14done
marmaid支持
添加 Mermaid 支持。导入了 mermaid,添加到 integrations 数组中。
1//------其他部分
2
3import mermaid from 'astro-mermaid';
4import { fileURLToPath } from 'url';
5const __dirname = path.dirname(fileURLToPath(import.meta.url));
6//------其他部分
7 integrations: [
8 swup({
9 theme: false,
10 animationClass: "vh-animation-",
11 containers: [".main-inner>.main-inner-content", '.vh-header>.main'],
12 smoothScrolling: true,
13 progress: true,
14 cache: true,
15 preload: true,
16 accessibility: true,
17 updateHead: true,
18 updateBodyClass: false,
19 globalInstance: true
20 }),
21
22 // 添加 Mermaid 支持
23 mermaid({
24 theme: 'default', // 或 'forest', 'dark', 'neutral' 等
25 }),
26
27 //------其他部分
28 Compress({ Image: false, Action: { Passed: async () => true } }),
29 sitemap({
30 // 处理末尾带 / 的 url
31 serialize: (item) => ({ ...item, url: item.url.endsWith('/') ?
6. 使用 Let’s Encrypt 配置 HTTPS(可选但推荐)
1# 安装 Certbot
2sudo apt install certbot python3-certbot-nginx -y
3
4# 获取证书不要邮箱
5sudo certbot --nginx -d your-domain.com -d www.your-domain.com --register-unsafely-without-email --agree-tos
6
7# 设置自动续期
8sudo certbot renew --dry-run
7. 配置防火墙和设置专用用户
1# 如果使用 UFW
2sudo ufw allow 'Nginx Full'
3sudo ufw allow OpenSSH
4sudo ufw enable
5# 打开hook的端口
6sudo ufw allow 9000/tcp
7sudo ufw status
第1步:创建专用用户
1# 创建系统用户 blogdeploy(不创建家目录,不允许登录)
2sudo adduser --system --group blogdeploy
3
4# 验证用户创建成功
5id blogdeploy
6# 应该显示:uid=xxx(blogdeploy) gid=xxx(blogdeploy) groups=xxx(blogdeploy)
第2步:设置目录权限
1# 将项目目录所有权给 blogdeploy
2sudo chown -R blogdeploy:blogdeploy /var/www/astro-blog
3
4# 或者如果 pnpm 装在其他地方
5which pnpm # 先查看 pnpm 位置
6# 给 blogdeploy 用户访问 pnpm 的权限
7sudo chown -R blogdeploy:blogdeploy /root/.local/share/pnpm
8
9
10# dist 目录特殊处理:让 www-data(Nginx用户)有读取权限
11sudo chown -R blogdeploy:www-data /var/www/astro-blog/dist
12sudo chmod -R 755 /var/www/astro-blog/dist
第3步:配置 blogdeploy 用户的 Git
1# 直接在项目里配置(只对这个仓库生效)
2cd /var/www/astro-blog
3
4sudo -u blogdeploy git config user.name "Blog Deploy"
5sudo -u blogdeploy git config user.email "deploy@ruajingjing.top"
6sudo -u blogdeploy git config --add safe.directory /var/www/astro-blog
7
8# 验证
9sudo -u blogdeploy git config --list
10
11# 切换到 blogdeploy 用户(临时)
12sudo -u blogdeploy bash
13
14# 测试能否访问仓库
15cd /var/www/astro-blog
16git status
17git pull origin astro-blog
18
19# 退出 blogdeploy 用户
20exit
第4步:确保 blogdeploy 能使用 pnpm
1echo "安装 nvm..."
2# 通过 sudo -u 执行安装脚本
3sudo -u blogdeploy bash << 'EOF'
4# 下载并安装 nvm
5curl -o- https://gh-proxy.org/https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
6
7# 设置 nvm 环境变量
8export NVM_DIR="$HOME/.nvm"
9[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
10
11# 安装 Node.js LTS
12echo "安装 Node.js LTS 版本..."
13nvm install --lts
14nvm use --lts
15
16# 验证安装
17echo "Node.js 版本:"
18node --version
19echo "npm 版本:"
20npm --version
21EOF
22
23# 4. 重新安装 pnpm(使用正确的方法)
24echo "重新安装 pnpm..."
25sudo -u blogdeploy bash << 'EOF'
26# 加载 nvm
27export NVM_DIR="$HOME/.nvm"
28[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
29
30# 确保使用正确的 Node.js 版本
31nvm use --lts
32
33# 卸载可能存在的问题 pnpm 安装
34npm uninstall -g pnpm 2>/dev/null || true
35
36# 清除 npm 缓存
37npm cache clean --force
38
39# 使用正确方法安装 pnpm
40npm install -g pnpm --prefix=/home/blogdeploy/.npm-packages
41
42# 创建 pnpm 专用配置
43mkdir -p ~/.pnpm
44cat > ~/.pnpm/config.json << 'PNPM_CONFIG'
45{
46 "store-dir": "/home/blogdeploy/.pnpm-store",
47 "global-dir": "/home/blogdeploy/.npm-packages/lib/node_modules"
48}
49PNPM_CONFIG
50
51# 设置 PATH(不修改 .bashrc,使用环境变量)
52export PATH="/home/blogdeploy/.npm-packages/bin:$PATH"
53
54# 验证安装
55echo "pnpm 版本:"
56pnpm --version 2>/dev/null || echo "pnpm 未正确安装"
57
58echo "pnpm 存储路径:"
59pnpm store path 2>/dev/null || echo "无法获取存储路径"
60EOF
61
62# 5. 创建安全的部署环境配置
63echo "创建部署环境配置..."
64sudo -u blogdeploy bash << 'EOF'
65# 创建部署脚本使用的环境文件
66cat > ~/.blogdeploy.env << 'DEPLOY_ENV'
67export NVM_DIR="/home/blogdeploy/.nvm"
68[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
69export PATH="/home/blogdeploy/.npm-packages/bin:$PATH"
70DEPLOY_ENV
71
72echo "已创建部署环境配置文件"
73EOF
74
75echo "=== 环境验证 ==="
76
77sudo -u blogdeploy bash << 'EOF'
78echo "1. 用户: \"$USER
79echo "2. 家目录: \"$HOME
80echo ""
81
82echo "3. Node.js 版本:"
83node --version 2>/dev/null || echo "未安装"
84echo ""
85
86echo "4. npm 版本:"
87npm --version 2>/dev/null || echo "未安装"
88echo ""
89
90echo "5. pnpm 版本:"
91pnpm --version 2>/dev/null || echo "未安装"
92echo ""
93
94echo "6. pnpm 存储路径:"
95pnpm store path 2>/dev/null || echo "无法获取"
96echo ""
97
98echo "7. 当前 PATH:"
99echo \$PATH
100EOF
如果遇到安装错误
1# 检查用户当前的主目录
2sudo grep blogdeploy /etc/passwd
3
4# 如果显示 /nonexistent,需要修改
5sudo usermod -d /home/blogdeploy blogdeploy
6
7# 创建主目录
8sudo mkdir -p /home/blogdeploy
9sudo chown -R blogdeploy:blogdeploy /home/blogdeploy
10
11# 重新尝试安装
12sudo -u blogdeploy bash -c "curl -o- https://gh-proxy.com/https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash"
解决方案:
方案一:永久配置环境变量
# 1. 以 root 用户为 blogdeploy 配置环境
cat >> /home/blogdeploy/.bashrc << 'EOF'
# nvm 配置
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
# pnpm 配置
export PNPM_HOME="$HOME/.local/share/pnpm"
export PATH="$PNPM_HOME:$PATH"
# 自定义 npm 全局包路径
export PATH="$HOME/.npm-packages/bin:$PATH"
EOF
# 2. 同样添加到 .profile
cat >> /home/blogdeploy/.profile << 'EOF'
# nvm 配置
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# pnpm 配置
export PNPM_HOME="$HOME/.local/share/pnpm"
case ":$PATH:" in
*":$PNPM_HOME:"*) ;;
*) export PATH="$PNPM_HOME:$PATH" ;;
esac
EOF
# 3. 设置权限
chown blogdeploy:blogdeploy /home/blogdeploy/.bashrc /home/blogdeploy/.profile
方案二:安装 pnpm 到正确位置
1# 1. 切换到 blogdeploy 用户配置
2sudo -u blogdeploy bash << 'EOF'
3# 加载 nvm
4export NVM_DIR="$HOME/.nvm"
5[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
6
7# 安装 pnpm(使用 npm 安装到全局)
8npm install -g pnpm
9
10# 验证 pnpm 安装
11pnpm --version
12
13# 配置 pnpm 存储路径
14pnpm config set store-dir ~/.pnpm-store
15EOF
16
17# 2. 现在应该可以正常使用了
18sudo -u blogdeploy bash -c "source ~/.bashrc && cd /var/www/astro-blog && pnpm build"
第5步:测试完整部署流程
1# 以 blogdeploy 用户身份测试部署
2sudo -u blogdeploy bash -c "cd /var/www/astro-blog && git pull origin astro-blog && pnpm install && pnpm build"
3
4cd /var/www/astro-blog
5sudo -u blogdeploy git remote set-url origin https://gitee.com/xxx/my-blog.git
6sudo -u blogdeploy git pull origin astro-blog
7
8# 检查是否成功
9ls -la /var/www/astro-blog/dist
第6步:验证权限
1# 检查关键目录权限
2ls -la /var/www/astro-blog
3# 应该显示 blogdeploy blogdeploy
4
5ls -la /var/www/astro-blog/dist
6# 应该显示 blogdeploy www-data 或 www-data www-data
7
8# 检查 Nginx 能否读取
9sudo -u www-data cat /var/www/astro-blog/dist/index.html
10# 应该能正常显示内容
常见问题排查
如果遇到权限错误
1# 重新设置权限
2sudo chown -R blogdeploy:blogdeploy /var/www/astro-blog
3sudo chown -R blogdeploy:www-data /var/www/astro-blog/dist
4sudo chmod -R 755 /var/www/astro-blog
如果 Git 报错
1# 设置 Git 安全目录
2sudo -u blogdeploy git config --global --add safe.directory /var/www/astro-blog
如果 pnpm 找不到
1# 检查 pnpm 路径
2which pnpm
3
4# 如果在 /root/.local/share/pnpm,需要创建链接或重装
8. 切换git分支迁移
1. 初始化并关联远程仓库
1cd /var/www/astro-blog
2
3# 如果还没初始化 git
4git init
5
6# 添加 Gitee 远程仓库
7git remote add origin https://xxxx.git
8
9# 查看远程仓库
10git remote -v
2. 创建新分支(推荐叫 astro)
1# 创建并切换到新分支
2git checkout -b astro
3. 添加 .gitignore
创建 .gitignore 文件,避免提交不必要的文件:
1nano .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. 提交代码
1# 添加所有文件
2git add .
3
4# 查看状态
5git status
6
7# 提交
8git commit -m "feat: 迁移到 Astro 博客框架
9
10- 使用 vhAstro-Theme 主题
11- 配置网站信息和备案
12- 添加 TOC 目录功能
13- 迁移所有文章内容"
5. 推送到 Gitee
1# 首次推送(会创建远程分支)
2git push -u origin astro
3
4# 如果需要输入用户名密码,使用 Gitee 账号
6. 如果推送失败(需要先拉取)
1# 拉取远程仓库
2git pull origin master --allow-unrelated-histories
3
4# 然后再推送
5git push -u origin astro
后续更新流程
以后更新博客:
1# 添加修改
2git add .
3
4# 提交
5git commit -m "update: 更新内容描述"
6
7# 推送
8git push
如果想保留 Hexo 分支
1# 查看所有分支
2git branch -a
3
4# master 分支保留 Hexo 内容
5# astro 分支是新的 Astro 博客
配置 Git 用户信息(如果还没配置)
1git config --global user.name "你的名字"
2git config --global user.email "your-email@example.com"
常见问题
如果提示认证失败
Gitee 可能需要使用个人访问令牌:
- 访问:https://gitee.com/profile/personal_access_tokens
- 生成新令牌
- 使用令牌作为密码
或者配置 SSH:
1# 生成 SSH 密钥
2ssh-keygen -t rsa -C "your-email@example.com"
3
4# 查看公钥
5cat ~/.ssh/id_rsa.pub
6
7# 复制公钥,添加到 Gitee SSH 设置
然后改用 SSH 地址:
1git remote set-url origin git@gitee.com:xxx.git
验证步骤
执行每步后检查:
1# 1. 查看当前分支
2git branch
3
4# 2. 查看远程仓库
5git remote -v
6
7# 3. 查看提交历史
8git log --oneline
9
10# 4. 查看状态
11git status
9. 使用 Git 自动化部署(推荐)
完美!✅ 现在继续配置 Webhook。
第1步:安装 webhook 工具
1sudo apt update
2sudo apt install webhook -y
3
4# 验证安装
5webhook --version
第2步:生成强密钥
1# 生成一个强密钥用于 Webhook 验证
2openssl rand -hex 32
3
4# 记下这个密钥,例如:
5# a8f5e2c9d3b7f1e4a6c8d9e2f3a1b5c7d4e6f8a1b2c3d4e5f6a7b8c9d0e1f2a3
把生成的密钥保存好,待会要用。
第3步:创建部署脚本
1# 创建脚本目录
2sudo mkdir -p /var/www/deploy-scripts
3
4# 创建部署脚本
5sudo vim /var/www/deploy-scripts/deploy-blog.sh
粘贴以下内容:
1#!/bin/bash
2
3# 日志文件
4LOG_FILE="/var/log/astro-blog-deploy.log"
5REPO_DIR="/var/www/astro-blog"
6
7# 记录开始
8echo "========================================" >> $LOG_FILE
9echo "部署开始: $(date)" >> $LOG_FILE
10echo "触发者: Gitee Webhook" >> $LOG_FILE
11
12# 切换到项目目录
13cd $REPO_DIR || {
14 echo "ERROR: 无法进入项目目录" >> $LOG_FILE
15 exit 1
16}
17
18# 验证 Git 仓库完整性
19echo "验证仓库完整性..." >> $LOG_FILE
20if ! git fsck --no-progress >> $LOG_FILE 2>&1; then
21 echo "ERROR: Git 仓库损坏,终止部署" >> $LOG_FILE
22 exit 1
23fi
24
25# 拉取代码
26echo "拉取代码..." >> $LOG_FILE
27git fetch origin astro-blog >> $LOG_FILE 2>&1
28git reset --hard origin/astro-blog >> $LOG_FILE 2>&1
29
30# 清理旧依赖
31echo "清理依赖..." >> $LOG_FILE
32rm -rf node_modules >> $LOG_FILE 2>&1
33
34# 安装依赖
35echo "安装依赖..." >> $LOG_FILE
36pnpm install --frozen-lockfile >> $LOG_FILE 2>&1
37
38# 审计依赖漏洞
39echo "审计依赖..." >> $LOG_FILE
40pnpm audit --audit-level=high >> $LOG_FILE 2>&1 || {
41 echo "WARNING: 发现高危漏洞" >> $LOG_FILE
42}
43
44# 构建
45echo "构建项目..." >> $LOG_FILE
46pnpm build >> $LOG_FILE 2>&1
47
48# 验证构建产物
49if [ ! -d "dist" ]; then
50 echo "ERROR: 构建失败,dist目录不存在" >> $LOG_FILE
51 exit 1
52fi
53
54# 设置正确的权限
55# chown -R blogdeploy:www-data dist/
56# chmod -R 755 dist/
57
58echo "部署成功: $(date)" >> $LOG_FILE
59echo "========================================" >> $LOG_FILE
保存后设置权限:
1# 给脚本执行权限
2sudo chmod +x /var/www/deploy-scripts/deploy-blog.sh
3
4# 设置所有者为 blogdeploy
5sudo chown blogdeploy:blogdeploy /var/www/deploy-scripts/deploy-blog.sh
6
7# 创建日志文件
8sudo touch /var/log/astro-blog-deploy.log
9sudo chown blogdeploy:blogdeploy /var/log/astro-blog-deploy.log
第4步:测试部署脚本
1# 以 blogdeploy 用户身份测试脚本
2sudo -u blogdeploy /var/www/deploy-scripts/deploy-blog.sh
3
4# 查看日志
5tail -30 /var/log/astro-blog-deploy.log
如果成功,会看到 “部署成功” 的消息。
如果解决构建错误
(Astro 想访问 /nonexistent/.config,但 blogdeploy 没有家目录)。
1# 给 blogdeploy 创建配置目录
2sudo mkdir -p /home/blogdeploy/.config
3sudo chown -R blogdeploy:blogdeploy /home/blogdeploy
4sudo usermod -d /home/blogdeploy blogdeploy
5
6# 或者禁用 Astro 遥测
7cd /var/www/astro-blog
8sudo -u blogdeploy npx astro telemetry disable
9
10# 再次测试部署
11sudo -u blogdeploy /var/www/deploy-scripts/deploy-blog.sh
测试成功后继续!
为 blogdeploy 用户安装独立的 pnpm:
bash
1# 1. 为 blogdeploy 创建 home 目录(如果还没有)
2sudo mkdir -p /home/blogdeploy
3sudo chown blogdeploy:blogdeploy /home/blogdeploy
4
5# 2. 切换到 blogdeploy 用户并安装 pnpm
6sudo -u blogdeploy bash << 'EOF'
7cd ~
8curl -fsSL https://get.pnpm.io/install.sh | sh -
9source ~/.bashrc
10pnpm --version
11EOF
12
13# 3. 验证 pnpm 可用
14sudo -u blogdeploy bash -c 'export PNPM_HOME="/home/blogdeploy/.local/share/pnpm"; export PATH="$PNPM_HOME:$PATH"; pnpm --version'
15
16# 4. 更新部署脚本(使用上面方案2的脚本内容)
17
18# 5. 修复 Git 警告
19sudo chown -R blogdeploy:blogdeploy /home/blogdeploy/.config
20
21# 6. 测试脚本
22sudo -u blogdeploy /var/www/deploy-scripts/deploy-blog-fast.sh
23
24# 7. 查看日志
25tail -30 /var/log/astro-blog-deploy.log
26
27# 8. 重启 webhook 服务
28sudo systemctl restart webhook
执行完这些步骤后,再推送一次代码测试。这次应该就能成功构建了!🚀
优化脚本(跳过依赖安装)
创建一个快速部署脚本:
1sudo vim /var/www/deploy-scripts/deploy-blog-fast.sh
1#!/bin/bash
2
3# 设置完整的环境变量
4export HOME=/home/blogdeploy
5export NVM_DIR="$HOME/.nvm"
6export PATH="$HOME/.npm-global/bin:$PATH"
7
8# 加载 nvm 环境
9[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
10
11# 日志文件
12LOG_FILE="/var/log/astro-blog-deploy.log"
13REPO_DIR="/var/www/astro-blog"
14
15# 记录开始
16{
17echo "========================================"
18echo "快速部署开始: $(date)"
19echo "用户: $(whoami)"
20echo "HOME: $HOME"
21echo "PATH: $PATH"
22echo "NVM_DIR: $NVM_DIR"
23echo "Node版本: $(node --version 2>&1)"
24echo "npm版本: $(npm --version 2>&1)"
25echo "PNPM位置: $(which pnpm 2>&1)"
26echo "PNPM版本: $(pnpm --version 2>&1)"
27} >> $LOG_FILE 2>&1
28
29# 切换到项目目录
30cd $REPO_DIR || {
31 echo "ERROR: 无法进入项目目录 $REPO_DIR" >> $LOG_FILE
32 exit 1
33}
34
35# 拉取代码
36echo "拉取代码..." >> $LOG_FILE
37git fetch origin astro-blog >> $LOG_FILE 2>&1
38
39# 检查是否有更新
40LOCAL=$(git rev-parse HEAD 2>/dev/null)
41REMOTE=$(git rev-parse origin/astro-blog 2>/dev/null)
42
43if [ "$LOCAL" = "$REMOTE" ] && [ -n "$LOCAL" ]; then
44 echo "没有更新,跳过部署" >> $LOG_FILE
45 exit 0
46fi
47
48git reset --hard origin/astro-blog >> $LOG_FILE 2>&1
49
50# 清理旧的构建文件
51echo "清理旧构建..." >> $LOG_FILE
52rm -rf dist >> $LOG_FILE 2>&1
53
54echo "构建项目..." >> $LOG_FILE
55pnpm run build 2>&1 | tee -a $LOG_FILE
56
57# 验证构建产物
58if [ ! -d "dist" ]; then
59 echo "ERROR: 构建失败,dist 目录未生成" >> $LOG_FILE
60 echo "尝试使用完整构建命令..." >> $LOG_FILE
61 # 尝试使用 npx
62 npx astro build 2>&1 | tee -a $LOG_FILE
63fi
64
65# 再次验证
66if [ ! -d "dist" ]; then
67 echo "ERROR: 构建失败" >> $LOG_FILE
68 exit 1
69fi
70
71echo "构建成功!" >> $LOG_FILE
72echo "构建文件:" >> $LOG_FILE
73ls -la dist/ 2>&1 | head -10 >> $LOG_FILE
74
75echo "快速部署成功: $(date)" >> $LOG_FILE
76echo "========================================" >> $LOG_FILE
设置权限:
1sudo chmod +x /var/www/deploy-scripts/deploy-blog-fast.sh
2sudo chown blogdeploy:blogdeploy /var/www/deploy-scripts/deploy-blog-fast.sh
第5步:配置 Webhook
这里用的gitee的hook,每个代码仓库的hook规则可能不一样,需要翻看文档
1# 创建 webhook 配置文件
2sudo vim /etc/webhook.conf
粘贴(记得把密钥改成你自己的):
1[
2 {
3 "id": "deploy-blog",
4 "execute-command": "/var/www/deploy-scripts/deploy-blog-fast.sh",
5 "command-working-directory": "/var/www/astro-blog",
6 "response-message": "正在部署博客,请稍候...",
7 "trigger-rule": {
8 "and": [
9 {
10 "match": {
11 "type": "value",
12 "value": "你的密码",
13 "parameter": {
14 "source": "header",
15 "name": "X-Gitee-Token"
16 }
17 }
18 },
19 {
20 "match": {
21 "type": "value",
22 "value": "refs/heads/astro-blog",
23 "parameter": {
24 "source": "payload",
25 "name": "ref"
26 }
27 }
28 }
29 ]
30 }
31 }
32]
第6步:创建 Webhook systemd 服务
1sudo vim /etc/systemd/system/webhook.service
使用全局安装就不需要指定变量了
1[Unit]
2Description=Webhook Service for Blog Auto Deploy
3After=network.target
4
5[Service]
6Type=simple
7User=blogdeploy
8Group=blogdeploy
9Environment="HOME=/home/blogdeploy"
10ExecStart=/usr/bin/webhook -hooks /etc/webhook.conf -verbose -port 9000
11Restart=always
12RestartSec=10
13
14# 安全限制
15NoNewPrivileges=true
16PrivateTmp=true
17ProtectSystem=strict
18ProtectHome=true
19ReadWritePaths=/var/www/astro-blog /var/log /home/blogdeploy
20
21[Install]
22WantedBy=multi-user.target
第7步:启动 Webhook 服务
1# 重载 systemd
2sudo systemctl daemon-reload
3
4# 启动服务
5sudo systemctl start webhook
6
7# 设置开机自启
8sudo systemctl enable webhook
9
10# 查看状态
11sudo systemctl status webhook
12
13# 查看日志
14sudo journalctl -u webhook -f
应该看到类似:
● webhook.service - Webhook Service for Blog Auto Deploy
Loaded: loaded
Active: active (running)
继续下一步!🚀
修改 Webhook 配置使用快速脚本
1sudo vim /etc/webhook.conf
把 "execute-command" 改为:
1{
2 "id": "deploy-blog",
3 "execute-command": "/var/www/deploy-scripts/deploy-blog-fast.sh",
4 ...
5}
重启 Webhook 服务
1sudo systemctl restart webhook
2sudo systemctl status webhook
现在有两个脚本
- 快速部署(只更新文章时用):
deploy-blog-fast.sh- 只拉代码 + 构建 - 完整部署(更新依赖时用):
deploy-blog.sh- 重装依赖 + 构建
Webhook 用快速的,手动完整部署时:
1sudo -u blogdeploy /var/www/deploy-scripts/deploy-blog.sh
测试快速脚本
1sudo -u blogdeploy /var/www/deploy-scripts/deploy-blog-fast.sh
2tail -20 /var/log/astro-blog-deploy.log
应该几十秒就完成了!继续配置 Gitee Webhook 吧?🚀
关于 Git 警告
顺便修复 Git 的权限警告:
1# 创建配置目录
2sudo mkdir -p /home/blogdeploy/.config/git
3
4# 创建空文件
5sudo touch /home/blogdeploy/.config/git/ignore
6sudo touch /home/blogdeploy/.config/git/attributes
7
8# 设置权限
9sudo chown -R blogdeploy:blogdeploy /home/blogdeploy/.config
10sudo chmod -R 755 /home/blogdeploy/.config
11# 确保目录存在并有正确权限
12sudo mkdir -p /home/blogdeploy/.config/git
13sudo touch /home/blogdeploy/.config/git/ignore
14sudo touch /home/blogdeploy/.config/git/attributes
15sudo chown -R blogdeploy:blogdeploy /home/blogdeploy
16sudo 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 那个)
如果忘了,重新生成一个:
1openssl rand -hex 32
就是/etc/webhook.conf 里的 secret 值
事件选择
- ✅ 勾选
Push - ⬜ 其他不勾选
其他选项
- SSL 验证:关闭(因为用的是 HTTP)
- 激活 WebHook:✅ 勾选
第4步:保存并测试
点击 “添加” 或 “确定”
然后点击刚创建的 Webhook 右侧的 “测试” 按钮
验证是否成功
方法1:查看 Gitee 界面
测试后,Gitee 会显示:
- ✅ 成功:状态码 200,响应 “正在部署博客,请稍候…”
- ❌ 失败:显示错误信息
方法2:查看服务器日志
1# 查看 webhook 日志
2sudo journalctl -u webhook -f
3
4# 查看部署日志
5tail -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步:真实测试
在本地修改一篇文章,然后:
1git add .
2git commit -m "test: 测试自动部署"
3git push origin astro-blog
然后:
- 观察服务器日志:
tail -f /var/log/astro-blog-deploy.log - 应该会自动开始部署
- 完成后访问你的网站验证更新
常见问题
如果 Gitee 提示连接失败
1# 确认 webhook 服务正在运行
2sudo systemctl status webhook
3
4# 确认端口开放
5sudo netstat -tlnp | grep 9000
6
7# 测试本地访问
8curl http://localhost:9000/hooks/deploy-blog
9
10curl -X POST http://localhost:9000/hooks/deploy-blog \
11 -H "X-Gitee-Token: 你的密码" \
12 -H "Content-Type: application/json" \
13 -d '{"ref": "refs/heads/master"}'
如果 Gitee 提示 403 或验证失败
检查密钥是否一致:
1# 查看配置文件中的密钥
2sudo cat /etc/webhook.conf | grep secret
确保和 Gitee 填的一样。
私钥权限
私钥权限太宽松了,SSH 拒绝使用。修复一下:
1sudo chmod 600 /home/blogdeploy/.ssh/id_rsa
2sudo chmod 644 /home/blogdeploy/.ssh/id_rsa.pub
3sudo chmod 700 /home/blogdeploy/.ssh
4sudo chown -R blogdeploy:blogdeploy /home/blogdeploy/.ssh
然后再 push 测试。
修复 dist 目录权限
1# 1. 确保 blogdeploy 对整个项目目录有写权限
2sudo chown -R blogdeploy:blogdeploy /var/www/astro-blog
3
4# 2. 特别是 dist 目录
5sudo rm -rf /var/www/astro-blog/dist
6sudo mkdir -p /var/www/astro-blog/dist
7sudo chown -R blogdeploy:blogdeploy /var/www/astro-blog/dist
8
9# 3. 确保 node_modules 也有正确权限
10sudo chown -R blogdeploy:blogdeploy /var/www/astro-blog/node_modules
11
12# 4. 测试构建
13sudo -u blogdeploy bash /var/www/deploy-scripts/deploy-blog-fast.sh
14
15# 5. 查看日志
16tail -50 /var/log/astro-blog-deploy.log
安全增强(可选)
如果还想更安全,可以加上 IP 白名单:
1{
2 "trigger-rule": {
3 "and": [
4 {
5 "match": {
6 "type": "value",
7 "value": "你的密码",
8 "parameter": {
9 "source": "header",
10 "name": "X-Gitee-Token"
11 }
12 }
13 },
14 {
15 "match": {
16 "type": "value",
17 "value": "refs/heads/astro-blog",
18 "parameter": {
19 "source": "payload",
20 "name": "ref"
21 }
22 }
23 },
24 {
25 "match": {
26 "type": "ip-whitelist",
27 "ip-range": "180.97.0.0/16,116.211.0.0/16"
28 }
29 }
30 ]
31 }
32}
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.log
12. 常用命令总结
# 查看 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.log
13. 备份脚本
#!/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


