Featured image of post Astro博客部署在服务器上并设置自动hook

Astro博客部署在服务器上并设置自动hook

将 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

image-20251211173329481

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

image-20251211174946037

image-20251211175116016

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

image-20251211182620483

到这里就创建了默认的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 可能需要使用个人访问令牌

  1. 访问:https://gitee.com/profile/personal_access_tokens
  2. 生成新令牌
  3. 使用令牌作为密码

或者配置 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

现在有两个脚本

  1. 快速部署(只更新文章时用):deploy-blog-fast.sh - 只拉代码 + 构建
  2. 完整部署(更新依赖时用):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

在服务器安全组开放端口

登录你的服务器控制台(阿里云/腾讯云等):

  1. 找到"安全组规则"
  2. 添加入站规则:
    • 端口: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

然后:

  1. 观察服务器日志:tail -f /var/log/astro-blog-deploy.log
  2. 应该会自动开始部署
  3. 完成后访问你的网站验证更新

常见问题

如果 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. 问题排查

常见问题解决:

  1. 403 Forbidden 错误
# 检查文件权限
sudo chown -R www-data:www-data /var/www/myblog
sudo chmod -R 755 /var/www/myblog
  1. 502 Bad Gateway 错误
# 检查端口占用
sudo netstat -tulpn | grep :80
sudo systemctl restart nginx
  1. 内存不足
# 增加 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

部署流程总结:

  1. ✅ 服务器:Ubuntu 24.04 LTS
  2. ✅ Web服务器:Nginx
  3. ✅ 运行环境:Node.js (nvm)
  4. ✅ 项目目录:/var/www/myblog
  5. ✅ SSL证书:Let’s Encrypt
  6. ✅ 自动化:Git hooks
  7. ✅ 监控:日志和 systemd