0%

使用 Gitea + Git Hook 实现 Hexo 博客源码托管与持续集成

目录

目录-使用 Gitea + Git Hook 实现 Hexo 博客源码托管与持续集成

0 前言

在此前的文章《在 Ubuntu 18.04 中搭建 GitLab 环境》中,笔者尝试了在 2 核 + 4G 的阿里云 ECS 上搭建 GitLab,无奈由于 GitLab 太占性能,最后只能卸载放弃。本文中,将使用更加轻量的开源解决方案 Gitea 来完成私有 Git 托管服务的搭建,并通过搭建好的 Gitea 管理 Hexo 博客工程的源码。

同时,还将通过 Git Hook 将 Hexo 博客的构建过程转移到线上,本地只需专注于创作与推送。借助 Gitea 仓库的在线编辑提交,还可实现 Hexo 博客的跨平台异地更新。

在实践本文内容前,读者应具备以下前提条件:

  • 一台云服务器
  • 域名 + ICP 备案 + DNS 解析
  • SSL 证书

关于前提条件,可以参考此前的文章《将 Hexo 搭建的博客站点从 GitHub Pages 迁移到腾讯云 VPS》

1 安装 Gitea

1.1 安装前置依赖

1
2
sudo apt update
sudo apt install sqlite3 git nginx

配置 Nginx 服务自启动:

1
sudo systemctl enable nginx

1.2 创建工作用户

1
2
3
4
5
6
7
8
sudo adduser \
--system \
--shell /bin/bash \
--gecos 'Git Version Control' \
--group \
--disabled-password \
--home /home/git \
git

上面的命令将分别创建一个名为 git 的新用户和组,并将其主目录设置为 /home/git

1.3 下载 Gitea 二进制文件,并添加可执行权限

1
2
3
cd /usr/local/bin
sudo wget -O gitea https://dl.gitea.io/gitea/1.17.0/gitea-1.17.0-linux-amd64
sudo chmod +x /usr/local/bin/gitea

1.4 创建 Gitea 工作目录,并修改用户组及权限

1
2
3
4
5
6
sudo mkdir -p /var/lib/gitea/{custom,data,log}
sudo chown -R git:git /var/lib/gitea/
sudo chmod -R 750 /var/lib/gitea/
sudo mkdir /etc/gitea
sudo chown root:git /etc/gitea
sudo chmod 770 /etc/gitea

1.5 配置 Gitea 服务自启动

编辑 gitea 的 service 文件 /etc/systemd/system/gitea.service

1
sudo vim /etc/systemd/system/gitea.service

将下面的内容粘贴进去(来源于这里,不同版本间可能略有区别):

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
[Unit]
Description=Gitea (Git with a cup of tea)
After=syslog.target
After=network.target
###
# Don't forget to add the database service dependencies
###
#
#Wants=mysql.service
#After=mysql.service
#
#Wants=mariadb.service
#After=mariadb.service
#
#Wants=postgresql.service
#After=postgresql.service
#
#Wants=memcached.service
#After=memcached.service
#
#Wants=redis.service
#After=redis.service
#
###
# If using socket activation for main http/s
###
#
#After=gitea.main.socket
#Requires=gitea.main.socket
#
###
# (You can also provide gitea an http fallback and/or ssh socket too)
#
# An example of /etc/systemd/system/gitea.main.socket
###
##
## [Unit]
## Description=Gitea Web Socket
## PartOf=gitea.service
##
## [Socket]
## Service=gitea.service
## ListenStream=<some_port>
## NoDelay=true
##
## [Install]
## WantedBy=sockets.target
##
###

[Service]
# Modify these two values and uncomment them if you have
# repos with lots of files and get an HTTP error 500 because
# of that
###
#LimitMEMLOCK=infinity
#LimitNOFILE=65535
RestartSec=2s
Type=simple
User=git
Group=git
WorkingDirectory=/var/lib/gitea/
# If using Unix socket: tells systemd to create the /run/gitea folder, which will contain the gitea.sock file
# (manually creating /run/gitea doesn't work, because it would not persist across reboots)
#RuntimeDirectory=gitea
ExecStart=/usr/local/bin/gitea web --config /etc/gitea/app.ini
Restart=always
Environment=USER=git HOME=/home/git GITEA_WORK_DIR=/var/lib/gitea
# If you install Git to directory prefix other than default PATH (which happens
# for example if you install other versions of Git side-to-side with
# distribution version), uncomment below line and add that prefix to PATH
# Don't forget to place git-lfs binary on the PATH below if you want to enable
# Git LFS support
#Environment=PATH=/path/to/git/bin:/bin:/sbin:/usr/bin:/usr/sbin
# If you want to bind Gitea to a port below 1024, uncomment
# the two values below, or use socket activation to pass Gitea its ports as above
###
#CapabilityBoundingSet=CAP_NET_BIND_SERVICE
#AmbientCapabilities=CAP_NET_BIND_SERVICE
###
# In some cases, when using CapabilityBoundingSet and AmbientCapabilities option, you may want to
# set the following value to false to allow capabilities to be applied on gitea process. The following
# value if set to true sandboxes gitea service and prevent any processes from running with privileges
# in the host user namespace.
###
#PrivateUsers=false
###

[Install]
WantedBy=multi-user.target

配置 Gitea 服务自启动:

1
2
sudo systemctl daemon-reload
sudo systemctl enable --now gitea

查看 Gitea 服务运行状态:

1
sudo systemctl status gitea

gitea service 成功运行

重启 Gitea:

1
sudo systemctl restart gitea

1.6 初始化安装

若服务器的 3000 端口已经暴露到外部访问,那么通过在浏览器中访问 IP:3000 我们会来到 Gitea 的初始化安装页面,参考下述图片进行配置(大多数内容是我们在上文中编辑 Gitea 的 service 文件时写入的):

gitea 安装初始配置-数据库设置与一般设置

gitea 安装初始配置-可选设置

所有的配置最终会写入 /etc/gitea/app.ini 中,完成初始化安装后,我们随时可以通过编辑该文件修改配置。

1.7 配置 Nginx 反向代理

本节的内容将实现通过自有域名访问 Gitea 服务。

首先,参考以下内容编辑 /etc/nginx/nginx.conf 文件:

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
user www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
worker_connections 768;
}

http {
include mime.types;
default_type application/octet-stream;

sendfile on;

# for gitea
server {
listen 82;
server_name gitea.shipengx.com;
# http 访问强制定向到 https 访问
return 301 https://gitea.shipengx.com$request_uri;
}

# 加载 ssl 配置文件
include /etc/nginx/ssl/*.conf;
}

Nginx 默认监听 80 端口,但笔者服务器的 80 端口和 81 端口都已经分配给了其它服务,所以这里选择监听 82 端口。

然后,创建 /etc/nginx/ssl 目录并将自有域名的 SSL 证书上传到该目录。

其次,创建 SSL 配置文件 /etc/nginx/ssl/ssl.conf,并参考以下内容对其进行编辑:

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
# for gitea
server {
# SSL 访问端口号为 443
listen 443;
# 填写绑定证书的域名
server_name gitea.shipengx.com;
ssl on;
# 证书文件名称
ssl_certificate ssl/gitea.shipengx.com_bundle.crt;
# 私钥文件名称
ssl_certificate_key ssl/gitea.shipengx.com.key;
ssl_session_timeout 5m;
# 请按照以下协议配置
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
# 请按照以下套件配置,配置加密套件,写法遵循 openssl 标准
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://localhost:3000/;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
}
}

从两个配置文件的内容我们不难发现,对 IP:82gitea.shipengx.com 的访问都将被重定向到 443 端口,对 443 端口的访问又会被代理到 localhost:3000,最终实现通过 82 端口和域名访问云服务器的 Gitea 服务。

最后,重启 Nginx 服务,并在浏览器中输入域名我们便可访问自建 Gitea:

1
sudo systemctl restart nginx.service

通过自定义域名访问 gitea

登录 gitea

1.8 为数据安全再加一道锁:配置 Nginx 登录验证

虽然我们可以设置 Gitea 的登录账户及密码,但为了安全,我们还可以再加一道 Nginx 登录验证,实现只有通过该验证才可以访问到 Gitea 登录界面的效果。

首先,安装 apache2-utils 工具:

1
sudo apt install apache2-utils

然后,创建 Gitea 登录账户并为其设置密码:

1
sudo htpasswd -c /etc/nginx/.htpasswd new_user_name

回车后会要求为新建用户设置密码。

其次,编辑 /etc/nginx/ssl/ssl.conf 文件,在 location / 项中添加 auth_basic 指令打开身份验证,并添加 auth_basic_user_file 指令指定我们上文中创建的登录凭证文件 /etc/nginx/.htpasswd

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
# for gitea
server {
# SSL 访问端口号为 443
listen 443;
# 填写绑定证书的域名
server_name gitea.shipengx.com;
ssl on;
# 证书文件名称
ssl_certificate ssl/gitea.shipengx.com_bundle.crt;
# 私钥文件名称
ssl_certificate_key ssl/gitea.shipengx.com.key;
ssl_session_timeout 5m;
# 请按照以下协议配置
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
# 请按照以下套件配置,配置加密套件,写法遵循 openssl 标准
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
ssl_prefer_server_ciphers on;
location / {
auth_basic "Please enter your account password";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://localhost:3000/;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
}
}

最后,重启 Nginx 服务:

1
sudo systemctl restart nginx.service

此时通过IP:82 或域名访问 Gitea 时首先会来到 Nginx 的登录验证页面,只有输入 /etc/nginx/.htpasswd 中存在的用户名及密码才可通过验证:

Nginx 要求输入用户名和密码

但还有个问题,此前我们配置了 3000 端口作为 Gitea 服务的外部访问端口,因此通过访问 IP:3000 可以直接绕过 Nginx 的登录验证:

通过 3000 端口访问 gitea 可以绕过 Nginx 登录验证

所以为确保安全,我们需要在云服务器的安全策略设置中拒绝外部对 3000 端口的访问请求:

云服务器安全策略设置为拒绝外部对 3000 端口的访问请求

1.9 导入本地裸仓

如果服务器中已经有了此前建立的裸仓,我们可以将其直接导入到 Gitea 中进行管理。

首先,将已有的裸仓拷贝到 Gitea 的仓库存储目录下,并将其所有权变更为上文中所建立的 git:git 用户:

1
2
cp -r ~/blog_test.git /var/lib/gitea/data/gitea-repositories/shipeng/
chown -R git:git /var/lib/gitea/data/gitea-repositories/shipeng/blog_test.git

然后,依次执行下述图片中的步骤即可:

在管理员面板导入已有的裸仓(1)

在管理员面板导入已有的裸仓(2)

导入到 Gitea 中的裸仓与原裸仓将不再有任何关联。

2 为服务器安装 Node.js 与 Hexo

实现 Hexo 博客的线上管理与持续集成前,我们需要先为服务器配置好 Node.js 与 Hexo 环境,为确保稳定可靠,请尽量使用本文中对应的版本。

2.1 安装 Node.js

下载二进制文件(本文中使用 v12.14.1 版本)并上传到服务器的 /usr/local 目录下,然后解压:

1
tar -zxvf node-v12.14.1-linux-x64.tar.gz

nodenpm 添加可执行权限并创建软链接:

1
2
3
4
sudo chmod +x /usr/local/node-v12.14.1-linux-x64/bin/node
sudo chmod +x /usr/local/node-v12.14.1-linux-x64/bin/npm
sudo ln -s /usr/local/node-v12.14.1-linux-x64/bin/node /usr/local/bin/node
sudo ln -s /usr/local/node-v12.14.1-linux-x64/bin/npm /usr/local/bin/npm

2.2 安装 Hexo

通过 npm 安装 Hexo(本文中使用 4.2.0 版本):

1
sudo npm install -g hexo@4.2.0

为 Hexo 可执行程序创建软链接:

1
sudo ln -s /usr/local/node-v12.14.1-linux-x64/lib/node_modules/hexo/bin/hexo /usr/local/bin/hexo

3 创建 Hexo 博客仓库并配置 Git Hook 钩子

完成上述步骤后,我们已经可以开始进行 Hexo 博客仓库的创建,并为其配置 Git Hook 钩子,并最终实现从线下创作到线上更新的自动化流程:

1
线下创作并推送提交 → 触发远程 Git Hook → 线上构建部署 → 站点更新

通过将博客的构建过程移到线上,不仅节约了本地的运算资源,而且可以让我们更专注于本地创作。

3.1 创建 Hexo 博客仓库

直接通过 Web 界面完成 Hexo 博客仓库的创建,并通过 https 克隆链接或 ssh 克隆链接将仓库克隆到本地,然后将本地仓库初始化(hexo init)为 Hexo 工程或将已有的 Hexo 工程拷贝到本地仓库。我们应该为仓库添加一个 .gitignore 文件,并添加一些不应被 git 管理的忽略项:

1
2
3
*.log
public/
.deploy*/

最好再创建一个部署脚本,方便后面线上构建时直接被 Git Hook 钩子脚本调用,下面是笔者的部署脚本 deploy.sh,该脚本被调用时会构建出博客的 HTML 静态页面,并将构建日志同时输出到终端和文件:

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
#!/bin/bash

file_log=deploy.log
file_fifo=run.fifo
rm -f "$file_log" "$file_fifo"
mkfifo $file_fifo
cat $file_fifo | tee -a $file_log &
exec 3>&1
exec 4>&2
exec 1>$file_fifo
exec 2>&1

gbError="\033[1;31m[ERROR]\033[0m"
gbWarning="\033[1;33m[WARNING]\033[0m"
gbInfo="\033[1;32m[INFO]\033[0m"
gbGood="\033[1;32m[GOOD]\033[0m"

current_time=$(date "+%Y-%m-%d %H:%M:%S")
echo -e "\n <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< $current_time >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> \n"
echo -e "$gbInfo Start to build and deploy hexo blog...\n"

hexo clean
hexo douban -b
hexo g
# hexo deploy

if [ $? -eq 0 ]; then
echo -e "\n$gbGood Successfully built and deployed hexo blog!"
else
echo -e "\n$gbError Faild to deploy hexo blog, please check it!"
fi

printf "\015"
exec 1>&3
exec 2>&4
rm -f "$file_fifo"

3.2 配置 Git Hook 钩子

创建 /var/lib/gitea/data/gitea-repositories/shipeng/blog.git/hooks/post-receive.d/post-receive 钩子脚本,输入下述内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash

gbError="\033[1;31m[ERROR]\033[0m"
gbWarning="\033[1;33m[WARNING]\033[0m"
gbInfo="\033[1;32m[INFO]\033[0m"
gbGood="\033[1;32m[GOOD]\033[0m"

echo -e "\n$gbInfo Hexo blog post-receive git hook is running...\n"

bare_repo=/var/lib/gitea/data/gitea-repositories/shipeng/blog.git
cloned_repo=/var/lib/gitea/data/gitea-repositories/shipeng/blog
blog_dir=/var/www/blog

rm -rf $cloned_repo
git clone $bare_repo $cloned_repo

cd $cloned_repo
chmod +x deploy.sh
./deploy.sh

if [ $? -eq 0 ]; then
rm -rf ${blog_dir}/*
cp -rf public/* ${blog_dir}/
fi

其中,blog_dir 指代的是博客站点的 Nginx 代理目录,需要提前将其所有权设定为上文中所创建的 git:git 用户:

1
sudo chown -R git:git /var/www/blog

Git Hook 的具体工作原理是,每发生一个推送提交的行为,会首先调用 Gitea 创建的钩子脚本 /var/lib/gitea/data/gitea-repositories/shipeng/blog.git/hooks/post-receive,该钩子脚本会调用上文中我们自定义的钩子脚本,自定义钩子脚本执行时,会将博客裸仓克隆出一个实体仓,并进入到实体仓中执行部署脚本 deploy.sh,若博客构建成功,则会将实体仓中生成的 public 目录下的博客站点内容拷贝到博客站点的 Nginx 代理目录,最终实现从本地创作到博客更新的自动化。

4 后记

生命不息,折腾不止!

参考

  1. 在 Ubuntu20.04 上怎样安装 Gitea
  2. 在 Ubuntu20.04 上怎样添加和删除用户
  3. GIT 仓库、源码管理服务器 gitea 的安装、htttps 访问
  4. gitea 安装
  5. Gitea 官方文档
  6. gitea
  7. 如何在 Ubuntu 上使用 Nginx 设置密码验证

Thank you for your donate!