当静态博客生成器和Git都是实机部署的时候,直接用post-recieve这个hook来调用生成器是非常方便的事情。但是当Git是用docker部署,或者是Gitlab这样不支持repository hook的解决方案的场景下,就需要用webhook来调用生成器了。本文会介绍以下内容:
- 如何写uwsgi作为响应webhook的backend
- 如何生成token以无密码clone private仓库
本文假设已经按照 archlinux部署gitlab 完成部署。
UWSGI安装
由于处理响应的是python写的程序,所以除了安装uwsgi之外还需要安装python插件:sudo pacman -S uwsgi uwsgi-plugin-python
。
安装完成之后不需要做其他操作,因为我们后续会用systemd service的方式托管,不需要用到uwsgi自己的什么“emperor”模式。
webhook设置
GitLab
解决webhook无法请求本机的问题
默认安装下gitlab是不允许webhook请求localhost的地址的,也就是不能在URL里填127.0.0.1+端口。解决这个问题有两个办法,一是简单粗暴允许所有webhook访问localhost,但是会有安全隐患,所以放在后面说;二是用让nginx监听一个server_name,把server_name填进allowlist里面。
首先来配置nginx,顺便把转发给python的部分也写上。假设我们的repo在收到push之后,会发送一个webhook给http://blogbuilder.internal:25666
,那么nginx的server配置就应该这么写:
1 | server { |
测试并重载nginx:sudo nginx -t && sudo systemctl reload nginx
。
然后需要配置dns,让dns解析器响应blogbuilder.internal
为127.0.0.1
。这个因使用的DNS软件而异,我用的mosdns就只需要在hosts插件里新增一条记录就行了。配置好之后ping blogbuilder.internal
应该有如下显示:
1 | ➜ ping blogbuilder.internal |
最后就是配置GitLab把blogbuilder.internal
放进allowlist。登录GitLab管理员账户之后,点击Configure GitLab,左侧栏点击Settings下面的Network,然后往下拉到Outbound requests:
然后要按Save changes。
给仓库添加webhook
打开一个仓库,左侧边栏最下面有个Settings,里面有个Webhooks,点击之后是这个页面:
URL填写:http://blogbuilder.internal:25666
Trigger勾选Push events,如果有多个branch可以自行设置pattern
拉到最下面,SSL verification
可以取消勾,因为我们是本机http请求,没有ssl。然后点击Add webhook按钮,没有问题的话就会添加成功了。
GitLab这边就设置完了,接下来就是编写响应的部分了。
Python
application
网上关于uwsgi的文章很多,但是都说得有点谜语人。我们用uwsgi的主要原因是为了能够让build和respond异步执行,也就是说能够尽快回应200给GitLab,让博客生成在后面慢慢摸。
那么首先我们就需要用到subprocess来启动独立进程执行博客生成器,另外还需要shlex帮忙拆分shell命令给subprocess。
1 | #!/usr/bin/env python3 |
uwsgi的入口函数是application,需要包含两个东西:1,响应码;2,返回一个list(包含生成的content)。
1 | def application(environ, start_response): |
接下来填入调用我们的外部shell script(负责build的琐碎东西)的命令:
1 | def application(environ, start_response): |
但是这样的话是不辨别webhook发送的什么内容就直接触发生成。一般来说还是要判断一下发了什么过来的,这就需要用到environ变量了:
1 | def application(environ, start_response): |
如果需要inspect一下environ包含了什么内容,可以把注释掉的那几行拿出来用——建议在真正写执行部分之前自己搞清楚environ的内容,毕竟有时候文档写的跟你真正收到的内容是不一样的。
假设我们把以下内容放在了/opt/autogenblog/application.py
里面:
1 | #!/usr/bin/env python3 |
systemd
那么现在就要写一个systemd服务来启动这个uwsgi了:
1 | [Unit] |
这些内容需要放到/etc/systemd/system/autogenblog.service
里。它的内容(Service部分)需要慢慢解释:
-
开头四行是安全防护的内容,含义参考systemd.exec(5)。
-
User用blogbuilder,如果还没有这个用户,
sudo useradd --no-user-group --create-home --system blogbuilder
-
Group用Nginx的http组
-
由于开头4行的保护,整个系统除了
/etc
和/tmp
都是不可写入的,但是我们的博客需要写入/var/www/blog
来让nginx对外serve content,所以在ReadWritePaths
指定该路径 -
由于2、3、4点,所以
/var/www/blog
的权属就应该是blogbuilder:http
,权限是755
,请用chown
和chmod
来修正这些权限。1
2
3
4
5➜ ls /var/www/blog -alh
total 36K
drwxrwxr-x 3 blogbuilder http 28K Oct 10 14:16 .
drwxr-xr-x 7 http http 4.0K Oct 4 14:38 ..
drwxr-xr-x 20 blogbuilder http 4.0K Oct 10 14:16 public -
WorkingDirectory工作目录设置成application.py所在的目录,以便
cmd = "env -i ./build.sh"
能够调用正确的build.sh -
ExecStart=
指定的socket就是我们在nginx的server里指定的uwsgi_pass的值,wsgi-file是我们的application.py的完整路径 -
KillSignal是因为如果不指定的话,uwsgi主进程会等待60秒才强制杀掉所有子进程,这就有点太久了
现在就可以启动并开机启动这个service了sudo systemctl enable --now autogenblog.service
。
build.sh
做完前面的所有东西之后,现在就只差一个uwsgi调用的build.sh需要编写了。但是因为我们的blog仓库是private的(除非你真的开诚布公,没有任何secret,否则不建议把博客源代码都设置成public仓库),所以需要生成GitLab Access Token来让脚本可以不用密码来clone。
Access Token
点开博客仓库,左边最下面的Settings,点Access Token,填Token name,role改成Maintainer(不然会被deny),scope勾上read_repository,然后点create就行。把token抄下来,不然切了页面就没办法再显示了,需要重新生成了。
token的用法是:
warning
如果不是本机直连访问,请务必使用https,否则明文传输很危险。
1 | git clone https://用户名:token@git.mutebot.net/用户/仓库名.git |
build.sh
根据以上内容就可以编写这个build脚本了。具体内容请替换成自己的实际内容:
1 |
|
把这个文件保存到application.py所在的目录,按本文的路径就是/opt/autogenblog/build.sh
。记得加上可执行权限sudo chmod +x build.sh
。
想要测试的话可以用webhook页面拉到最下面有个test,选择push的test就行。用sudo journalctl -u autogenblog --since "1m ago" -f
应该可以看到hugo命令运行的输出以及整个脚本执行的结果。再执行sudo ls -alh /var/www/blog
就能够看到更新时间应该是非常近的。
Tips and Tricks
允许webhook访问任何本地网络的资源
如果整个实例只有自己一个人用的话,直接允许webhook访问本地网络也不会有多大风险。毕竟这个选项带来的风险主要是:有些本地网络的resource并不是开放给其他用户的,但是如果开启了这个选项,其他用户就可以通过用webhook来非法访问这些resource。
登录GitLab管理员账户之后,点击Configure GitLab,左侧栏点击Settings下面的Network,然后往下拉到Outbound requests,把webhooks那个勾勾上,按保存就可以了。