使用 GitLab CI 进行 hexo 的持续集成

其实很早之前我就意识到,把博客的源码不加掩饰的直接托管在 Github 是一件颇为危险的事,配置文件中配置的各种平台的 key 随时有可能被窃取,只是一直懒得动手,今天得闲把整个流程捋顺了,遂记录一下。

写在前面

  1. 本文使用 GitLab 提供的共享 gitlab-runner 来进行持续集成,本来手头有一台腾讯云的 1GB RAM、 CentOS 7.6 主机,在上面直接部署私有 gitlab_runner 其实能让整个过程变得更加简单一些,可惜经过测试通过 SSH 访问 gitlab.com 速度实在感人,就放弃了。
  2. 本文所用的持续集成方式是:
    共享 github-runner Docker 编译打包 -> WebHook -> 下载最新包 -> 部署
  3. 迁移源码到GitLab的步骤略过不提。

配置

新增访问令牌

在下载文件时,需要使用访问令牌,转到个人设置菜单,新增一个访问令牌:



访问令牌仅能在生成后查看一次,注意妥善保存。

配置 SSH 私钥与 known_hosts

参照官方文档,命名为 SSH_PRIVATE_KEY 和 SSH_KNOWN_HOSTS。

配置 .gitlab-ci.yml


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# This file is a template, and might need editing before it works on your project.
# Full project: https://gitlab.com/pages/hexo
image: node:10.15.3

before_script:
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
- eval $(ssh-agent -s)
- mkdir -p ~/.ssh
- rm -Rf ~/.ssh/id_rsa
- touch ~/.ssh/id_rsa
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
- rm -Rf ~/.ssh/known_hosts
- touch ~/.ssh/known_hosts
- echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
- chmod 700 ~/.ssh
- chmod 700 ~/.ssh/*
- git config --global user.email "owner@wayneshao.com"
- git config --global user.name "玮仔Wayne"

pages:
script:
- npm install hexo-cli -g
- test -e package.json && npm install
- hexo generate
- hexo deploy
artifacts:
expire_in: 3 days # <== !!!
paths:
- public
cache:
paths:
- node_modules
key: project
only:
- source

如果没有禁用共享 git-runner,那提交 .gitlab-ci.yml 之后就能看到如下的部署过程了,可能会比较慢,等待邮件通知即可。

之后就能看到生成的作业产物——打包好的 public 目录

WebHook

配置 WebHook 仓库集成

WebHook 监听

安装

打算这里我打算使用 WebHook 做 Web 监听,集成工作使用 shell 脚本来完成,先安装配置 WebHook :

1
2
3
4
cd ~
go get github.com/adnanh/webhook
cd go/bin
vi hooks.json

配置

hooks.json中按需求输入以下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
[
{
"id": "blog",
"execute-command": "/var/scripts/update-blog.sh",
"command-working-directory": "/opt/backup-blog",
"pass-arguments-to-command": [
//传入打包文件大小用于校验
{
"source": "payload",
"name": "builds.0.artifacts_file.size"
}
],
"response-message": "job done!",
//Hook条件
"trigger-rule": {
"and": [
//校验 Secret Token
{
"match": {
"type": "value",
"value": "(仓库中配置的 Secret Token)",
"parameter": {
"source": "header",
"name": "X-Gitlab-Token"
}
}
},
//校验 payload 类型
{
"match": {
"type": "value",
"value": "pipeline",
"parameter": {
"source": "payload",
"name": "object_kind"
}
}
},
//校验 payload 状态,只有成功才Hook
{
"match": {
"type": "value",
"value": "success",
"parameter": {
"source": "payload",
"name": "object_attributes.status"
}
}
}
]
}
}
]

PS:一定要有 response-message ,没有返回时 GitLab 会判定为访问失败

运行

1
nohup ~/go/bin/webhook -hooks ~/go/bin/hooks.json -verbose &

访问 :9000/hooks/blog 测试

配置 Nginx


Shell 脚本实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/bin/sh

# 获取当前时间以作为文件名/版本号
datename=$(date +%Y%m%d%H%M%S)
# 获取最后一个文件名
filename=`ls -l | tail -n 1 | awk '{print $9}'`

# 使用令牌下载最新的编译产出
wget --header "PRIVATE-TOKEN: (访问令牌)" "https://gitlab.com/api/v4/projects/(项目ID)/jobs/artifacts/(源码所在的分支)/download?job=pages" -O /opt/backup-blog/$datename.zip

echo
# 校验文件大小
if [ $(stat --format=%s /opt/backup-blog/$datename.zip) == $1 ]; then
echo "[OK]size check passed."
# 对比 Md5 校验是否为旧文件
if [ $(md5sum $datename.zip | cut -c1-32) == $(md5sum $filename | cut -c1-32) ]; then
echo "[ERROR]same with last."
rm -Rf /opt/backup-blog/$datename.zip
else
echo "[OK]last md5 equal passed."
# 解压
unzip -o /opt/backup-blog/$datename.zip

# 更新
sudo rm -Rf /opt/blog
mkdir -p /opt/blog
cp -rf ./public/* /opt/blog
rm -Rf ./public
# 重载 nginx
nginx -s reload
fi
else
echo "[ERROR]size not equal."
rm -Rf /opt/backup-blog/$datename.zip
fi

注意:

  1. 下载文件时需要在 http 请求头中设置键 PRIVATE-TOKEN 的值为访问令牌,才能请求到对应的文件。
  2. 最新产出文件的地址如图,注意修改项目 ID 和源码所在的分支名(项目 ID 可以再项目的设置中获得)。