我在优化重构我的个人网页的架构。之前的个人网页架构是通过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里触发)。
这个脚本做的事情是:
- 在执行某一步错误之后不再向后执行
- 进入工作目录下,拉取新的提交
- 使用jekyll编译静态网页
- 使用链接/复制的方式同步构建目录和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 ""