我在优化重构我的个人网页的架构。之前的个人网页架构是通过git管理Markdown然后通过jekyll和jekyll自带的webrick进行编译和webserver的模式。这种模式有几个问题,一个是在电脑的vscode完成博客编写之后,push到裸仓库还需要再在工作路径下pull一下。第二个是笔记种的一列Liquid语法可能写出问题,在这种构建+服务器耦合的模式不好解决。

为了解决这些问题,核心是利用git的hook构建一个CI/CD的工作流,然后使用Nginx管理静态的网页。在新的架构下,我使用git管理我的个人主页的代码仓库,里面是.md格式的笔记、jekyll的配置文件和一些css/js的格式代码。在git收到push以后使用hook进行拉取,使用jekyll进行编译,然后把静态网页交给了nginx代理。

接下来我将进一步的阐述具体的做法和细节。

首先需要做的是在ECS上建立一个Git裸仓库,然后在ECS本地的工作目录和主机的工作目录里通过git remote set-url命令设置远程仓库为ECS上的裸仓库,参见这篇笔记

然后是设置nginx作为静态网页的web server,修改nginx的配置文件如下:

vi /etc/nginx/sites-avaliable
server {
    listen 80;
    server_name www.honest-zh.me honest-zh.me;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name www.honest-zh.me honest-zh.me;

    # 静态文件目录
    root /var/www/honest-zh.me;
    index index.html index.htm;

    # SSL 配置
    ssl_certificate /etc/letsencrypt/live/www.honest-zh.me/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/www.honest-zh.me/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    # 安全头部
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # ACME 验证
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/html;
        allow all;
    }

    # 主要静态文件处理
    location / {
        try_files $uri $uri/ $uri.html =404;
    }

    # 处理 Jekyll 的 _site 结构
    location ~ ^/(assets|images|js|css|fonts)/ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    # 视频文件特殊处理
    location /assets/video/ {
        alias /home/honest/blog/honestblog/assets/video/;
        add_header Accept-Ranges bytes;
        expires 30d;
    }

    # 保护目录
    location ~ ^/(admin|草稿|树洞) {
        auth_basic "Restricted Area";
        auth_basic_user_file /etc/nginx/.htpasswd;
        try_files $uri $uri/ =404;
    }

    # 禁止访问隐藏文件
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }

     # 错误页面
    error_page 404 /404.html;
    error_page 500 502 503 504 /50x.html;

    # 日志配置
    access_log /var/log/nginx/blog.access.log;
    error_log /var/log/nginx/blog.error.log;
}

Nginx代理的是/var/www/honestblog下的网页,只需要把生成好的静态网页放在这个目录下即可。

最后是测试Git的post-recieve的hook调用一个脚本实现这个自动的CI/CD。先测试Git的post-recieve hook触发是否正常。如果不正常需要确定权限。

#!/bin/bash
set -euo pipefail

export PATH=/usr/local/bin:/usr/bin:/bin
export HOME=/home/git

echo "HOOK TRIGGERED at $(date) by $(whoami)" >> /tmp/vsftpd_git_hook_test.log
id >> /tmp/vsftpd_git_hook_test.log

/home/blog/blog_deploy.sh >> /tmp/vsftpd_git_hook_test.log 2>&1

echo "Blog Deployed" >> /tmp/vsftpd_git_hook_test.log

这里我直接在post-recieve里直接执行一个脚本 blog_deploy.sh。(可以先以git用户执行这个脚本验证脚本有没有问题,再放到hook里触发)。

这个脚本做的事情是:

  1. 在执行某一步错误之后不再向后执行
  2. 进入工作目录下,拉取新的提交
  3. 使用jekyll编译静态网页
  4. 使用链接/复制的方式同步构建目录和nginx代理的静态资源目录(需要做隔离)

注意这个脚本应该让git拥有执行权限!同时脚本中做的所有shell操作需要让对应的位置具有合适的权限,需要用chmod和chown。

#!/bin/bash
set -euo pipefail

echo " 检测到代码推送,开始部署..."

# 记录日志
exec >> /home/blog/blog_deploy.log 2>&1
echo "=== 部署开始: $(date) ==="

# 1. 更新工作目录
echo "更新工作目录..."
cd /home/blog/honestblog||exit 1

# git hook触发的脚本,他会认为GIT_DIR是GIT_DIR=/home/git/blog/honestblog.git,
# 尽管当前目录在/home/blog/honestblog,他也不会自动寻找其下的.git
git --git-dir=/home/blog/honestblog/.git \
    --work-tree=/home/blog/honestblog \
    fetch origin
git --git-dir=/home/blog/honestblog/.git \
    --work-tree=/home/blog/honestblog \
    reset --hard origin/master


# 1.5 检查依赖
bundle check || {
  echo "依赖不满足,构建中止"
  exit 1
}

# 2. 增量构建
echo "增量构建静态网页..."
bundle exec jekyll build --incremental

# 3.链接
ln -sfn "/home/blog/honestblog/_site" "/var/www/honest-zh.me"

echo "✅ 部署完成: $(date)"
echo "网站地址: https://www.honest-zh.me"
echo ""