通过Gitlab的WebHooks实现网站内容同步

背景

我自己有一个用Hexo搭建的网站——https://4ading.com/,用来发布自己写的文章。文章使用阿里云的代码托管服务(以下简称CODE,使用Gitlab搭建)存储,并在ECS上进行克隆,通过Nginx进行访问。

网络图

开始的时候,我发布文章的流程如下:

  1. 在电脑上编辑文章。
  2. 要发布的时候,执行hexo g -d,通过Git发布到CODE上。
  3. 使用SSH登录服务器,在服务器上的网站根目录下执行git pull,完成同步。

在此之前,我把连接配置名、密钥什么的都设置好了。新版的Windows 10自带SSH客户端,因此基本上不需要下载其他的SSH客户端。

不过,上面的流程要输入好几行代码,中间等待的时间还是比较长的。后来,我将第二步和第三步用一行PowerShell命令行搞定了:

hexo g -d; ssh 之前设置好的连接配置名 "cd 服务器上的网站根目录 && git pull"

不过,这行代码太长,我一般都是在终端里面上翻历史命令,找到这一条再执行的。结果,几周前重装系统后,这条记录就没了,我还因为命令中的分隔符纠结了半天。

于是,我便想:如果能够在执行hexo g -d之后自动更新服务器上的文件,就好了。

WebHooks介绍

我以前听说,GitHub上面可以通过Travis CI这样的持续集成工具,在推送时进行测试、构建、部署等工作。

后来,我发现,Gitlab中,有一个叫WebHooks的东西,如果加以利用,可以自建属于自己的持续集成工具(其他的代码托管平台也有类似的功能,包括上面提到的GitHub)。

简单来说,WebHooks就是:当项目进行了某项操作(如提交),代码托管平台就会调用给定的URL,发送一条POST请求。至于URL那边的服务器,接收到请求之后,就可以为所欲为了——这篇文章中提到的同步存储库什么的,只是冰山一角,如果你会写一些脚本,理论上可以做到发邮件、在社交平台/即时通信平台上发送消息、甚至如前面提到的那样,自动进行测试、构建、部署。当然,和一般的请求一样,服务器也会给一个返回。不过这个返回的意义并不是非常大,相比之下,服务器内的处理流程才是最重要的。

WebHooks示意图

设置WebHooks的方法

在CODE(其他基于Gitlab的托管平台的操作流程应该也差不多)中,设置WebHooks的方式很简单:

在项目的设置中,点击“WebHooks”选项,填入接收请求的URL,选择触发事件,点击下面的“增加WEBHOOKS”,即可成功添加一条。可以看到,在“触发”的选项中,有一个“推送事件”,当推送内容至仓库后,即触发。

增加WebHooks

这样一来,我们可以写实现如下操作的代码:当接收到请求后,即在服务器中对网站根目录进行git pull操作。

其实,这种要求非常低。如果你装了宝塔面板,只要装一下“宝塔WebHook”的插件,在插件里面设置好如下的shell脚本就可以了。

cd 网站根目录
git pull

点“查看密钥”,就可以看到插件给你的URL,用这个URL做WebHook的链接就行了。

宝塔WebHook给出的URL

仅在提交特定分支时调用URL

但是,我的这个仓库有两个分支:masterhexomaster分支存储生成的网页文件,hexo分支存储网站的源文件和设置。我只想让服务器在master分支被提交时更新网站内容,而hexo分支更新与否与网站无关。

Gitlab中并没有关于“提交某个特定的分支”的触发事件,但是,在调用URL的时候,会发送特定的请求头和请求体。

帮助文档中给出了比较详细的事例。比如推送事件的请求头如下:

X-Gitlab-Event: Push Hook

请求体为JSON格式的内容,如下:

{
  "object_kind": "push",
  "before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
  "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
  "ref": "refs/heads/master",
  "user_id": 4,
  "user_name": "John Smith",
  "user_email": "john@example.com",
  "project_id": 15,
  "repository": {
    "name": "Diaspora",
    "url": "git@example.com:mike/diasporadiaspora.git",
    "description": "",
    "homepage": "http://example.com/mike/diaspora",
    "git_http_url":"http://example.com/mike/diaspora.git",
    "git_ssh_url":"git@example.com:mike/diaspora.git",
    "visibility_level":0
  },
  "commits": [
    {
      "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
      "message": "Update Catalan translation to e38cb41.",
      "timestamp": "2011-12-12T14:27:31+02:00",
      "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327",
      "author": {
        "name": "Jordi Mallach",
        "email": "jordi@softcatala.org"
      }
      "added": ["CHANGELOG"],
      "modified": ["app/controller/application.rb"],
      "removed": []
    },
    {
      "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
      "message": "fixed readme",
      "timestamp": "2012-01-03T23:36:29+02:00",
      "url": "http://example.com/mike/diaspora/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
      "author": {
        "name": "GitLab dev user",
        "email": "gitlabdev@dv6700.(none)"
      },
      "added": ["CHANGELOG"],
      "modified": ["app/controller/application.rb"],
      "removed": []
    }
  ],
  "total_commits_count": 4

}

可以看到,请求体里面的"ref"字段中包含了推送的分支,只要做到这个操作就可以了:在服务器收到请求后,读取请求体,判断该字段中是否表示master分支,如果是,则更新网站根目录的内容。

当然,为了安全,还要验证请求头是否正确。

流程图

但是,“宝塔WebHook”插件太简单了,做不到这个。它的传值在查询字符串中,读不了请求头和请求体。这样一来,我就只能写服务器脚本来实现这个功能了。

你可以使用你熟悉的语言和框架写服务器脚本,只要能够收到请求并作出正确的反应就行了。我接触JS和Python比较多,不过Python中的Flask框架足够轻量(说实话貌似有更轻量的),开发起来也容易得多。于是,我便使用了Python和Flask框架来写这个脚本。

使用Python和Flask框架实现功能

安装好Flask,然后写入如下的代码。

基本骨架是Flask提供的Hello World示例,我翻了很多资料,东拼西凑出来可以运行的代码。不过确实非常简单。

一些必要的注释我放在代码里面了。

# flask.request是用来处理请求的
from flask import Flask, request
# 代码中涉及到日志
import logging
# 切换目录用
import os
# 执行外部脚本用
import subprocess
app = Flask(__name__)

# 这样写,URL就是http://地址:端口号/hook
# WebHooks用的是POST请求
@app.route('/hook', methods=['POST'])
def git_hook():
    # 请求头要对应;请求头有很多字段,所以找请求头中是否有这个字符串
    if str(request.headers).find('X-Gitlab-Event: Push Hook')!=-1:
        # 找请求体的"ref"字段是否有"master"的字样
        if str(request.json['ref']).find('/master')!=-1:
            # 切换目录
            os.chdir('网站根目录')
            # 使用subprocess.check_output执行shell脚本,脚本内被空格隔开的部分用列表装住
            # 如果出错,会抛出subprocess.CalledProcessError错误,所以要用try...except接住错误
            # subprocess.CalledProcessError.returncode是错误时的返回值
            # subprocess.CalledProcessError.output是shell脚本在控制台上输出的字符
            try:
                output = subprocess.check_output(['git','pull'])
            except subprocess.CalledProcessError as e:
                app.logger.error('git err at %s', e.returncode)
                app.logger.error('git err output:\n %s', e.output)
                return 'git error'
            app.logger.info('git pull master success')
            return 'sucess'
        else:
            # 非master分支的提交
            app.logger.info('not master')
            # 返回不需要写那么详细
            return 'sucess'
    else:
        # 请求头错误
        app.logger.info('head error')
        return 'error'

在服务器上部署代码。我用的是宝塔的Python项目管理器,因此具体的脚本我就不清楚了,反正我用了gunicorn。

之后,用Nginx进行端口的反向代理,并应用SSL。具体设置方法看网上的文档,因为我也是用宝塔的默认文档魔改的。当然,如果你对安全不那么在意的话,你也可以不用设置Nginx。这时,监听主机要设置为'0.0.0.0'

此后就可以到CODE(Gitlab)去设置WebHooks了。如果你用了反代什么的,就根据反代的参数设置URL。如果你开了SSL,就可以点下面的“开启SSL证书验证”了。

增加完成后,要多测试。页面下方给出了测试按钮,会发送一条测试的请求。到主机的服务器上看日志、看返回结果什么的,看看执行的怎么样。

WebHooks测试

不仅要用这个测试按钮,还要在本地实际进行提交,进行测试。我上面的代码就测试了不下十次才写出点样子。

不仅仅是网站,还可以是整个项目

实际上,一开始,这个仓库只有一个分支。之所以开另一个分支,是因为我希望能够在不同的地方写文章。

和WordPress这样的博客系统不同,Hexo在线上是静态的,源文件都在本地。我此前希望自己写在线编辑器,但是因为我懒、菜,就一直没有写。后来我试了hexo-adminhexo-myadmin这样的在线编辑器,但是前者太丑了,后者在手机端上几乎没法用。最后我找了半天,选择了hexo-hey,因为它的页面适配手机端比较好。但是,作者不更新了,手机端上也有一些问题。目前我还没有找到满意的在线编辑器。

hexo-hey的效果

要使用在线编辑器,就要把整个工作目录放上去,而不仅仅是生成的文件。我在知乎上找到了教程,将生成文件和工作目录放到不同的分支下。然后,我按照组件、用pm2执行,再用Nginx做反向代理,以便使用SSL证书,终于搞好了。

这时,如果在手机端上修改了文章的草稿(由于我修改了一些node-modules,加上我写文章并不快,所以我目前不期望在手机端上发布文章),就要:在手机端上使用SSH登录到服务器,再在工作目录下执行以下命令,以将更改上传到CODE:

git add .
git commit -m "提交信息"
git push

然后,回去用电脑编辑之前,也要从CODE上下载更改。更要命的是,编辑好了之后,还要上传更改,然后在服务器上下载更改,以便手机端修改。

简而言之,每次编辑前后,都要拉取、提交修改。电脑端上倒无所谓,但是手机端上执行这一系列流程挺困难的。而且,虽然关于git的所有流程都在电脑上进行,但是还是挺麻烦的,而且容易忘记拉取,一旦忘了就尴尬了。

在我写完上面的脚本之后,我想到了这个情况,便想到了一个方法:当推送hexo分支后,同时在服务器上拉取该分支。这样一来,就能省下一半的工作量。

修改起来也简单,只要在上面的代码中的# 非master分支的提交处,照着写一下hexo分支提交之后的处理方法,再测试一下,就行了。

至此,我的服务器的功能图如下:

服务器功能图
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容