aaron67's log

基于GitLab CI和Webhook的Hexo备份及部署方案

这是一套适合懒人的Hexo备份及部署解决方案

结合具体情况

  • Hexo本身适合用Git管理
  • 静态文件要部署在VPS上
  • 我习惯用Git管理代码

这套方案要满足的点包括

  • 用Git管理Hexo的源码和主题,换了电脑也要能更新博客
  • 发布文章时,VPS上的静态文件要能被优雅的更新

系好安全带,下面要开始飙车了 😝

写在前面

关于备份

Hexo是一款基于Node.js的静态博客框架,简单来说包括三部分

  1. 源码,包括项目框架、自己写的文章等
  2. 主题
  3. 部署用的静态文件,根据源码和主题自动生成
.
├── _config.yml     # [1]
├── db.json         # hexo generate时,被自动更新
├── node_modules/   # 依赖的npm包,npm install时根据package.json自动下载
├── package.json    # [1]
├── public/         # [3]
├── scaffolds/      # [1]
├── source/         # [1]
└── themes/         # [2]

所以,备份[1][2]是关键。搜索了不少文章,备份的套路五花八门,包括

  • 使用各种云存储直接备份整个文件夹
  • 将源码放在GitHub仓库的A分支,静态文件通过hexo deploy,自动更新到B分支
  • 将源码和静态文件分开存放到不同的GitHub仓库

除了用云存储直接备份的方式,使用Git仓库托管,需要注意

  • 如果要保留主题仓库原来的Git历史,方便上游更新时同步,则需要使用Git子模块。因为主题本身是以Git仓库的形式提供的,而Git仓库不能嵌套
  • 自定义主题后push修改到子模块,要求子模块仓库有写权限

因此,如果要使用子模块这种方案,在安装Hexo主题前,不同于原来的直接clone主题作者的仓库地址,要先在GitHub上fork原作者的仓库,本地clone时,clone自己fork出来的仓库,这样才对子模块有写权限

考虑到

  • Hexo中集成的各种服务(LeanCloud、Disqus、busuanzi等)的API Key,不便公开

多谢@river提醒,这点考虑是多余的

  • GitHub的private仓库收费
  • 不使用GitHub就不能fork主题
  • Next主题已经相对成熟和稳定,以后的一些简单改动,自己也能照猫画虎,所以保留原仓库的Git历史显得不那么重要
  • GitLab的private仓库免费,并且也提供了类似GitHub Pages的服务

最后决定

  • 不单独管理主题,不保留原主题的Git历史,不使用子模块方案
  • 博客整体(源码和主题)作为一个仓库,托管到GitLab

关于部署

在使用这套方案之前,原来的部署方案是这样的

  1. 本地更新博客
  2. hexo generate生成最新的public文件夹
  3. 通过FTP上传public文件夹到VPS家目录
  4. 跑一个脚本,更新Apache的DocumentRoot

部署脚本长这样

#!/bin/sh
sudo rm -rf /var/www/aaron67.cc/*
sudo cp -R public/* /var/www/aaron67.cc/.
rm -rf public

这样虽然也挺方便,但每次都要做重复的操作

最后的方案

看了不少文章,趟了不少坑,做了不少实验,最终的方案长这样

hexo-backup-deploy-solution

配置GitLab CI

基本概念(GitLab CI、Runners、Pipeline等)不多说了,网上资料一大把

在仓库目录下添加.gitlab-ci.yml

官方提供的Pages–>Hexo模板基本不用动

# This file is a template, and might need editing before it works on your project.
# Full project: https://gitlab.com/pages/hexo
image: node:4.2.2

pages:
  cache:
    paths:
    - node_modules/

  script:
  - npm install hexo-cli -g
  - npm install
  - hexo deploy
  artifacts:
    expire_in: 3 days    # <== !!! 
    paths:
    - public  # <== 每次会将生成的public文件夹当成附件,保存起来
  only:
  - master

注意添加artifactsexpire_in项,GitLab 9.0 Release之后,Pages artifacts默认会在部署后删除

GitLab CI配好后,每次push到仓库,Pipeline会自动运行,生成静态文件(public文件夹),并当成附件保存起来,随后更新GitLab Pages,完成Pages部署

下图能看到Pipeline的运行结果,每次Pipeline运行都生成了对应的artifacts

在VPS上实现Webhook服务端

这块的实现方式因人而异,只要是自己用着顺手的技术栈就好。在VPS监听Webhook收到的payload,当需要更新VPS的DocumentRoot时,通过GitLab API取回最新的artifacts,然后更新即可

我这里用的是Ruby的Sinatra,关于如何部署,可以参考这篇文章

require 'rubygems'
require 'sinatra/base'
require 'open-uri'
require 'json'

def download_gitlab_artifact(filename, url)
  data = open(url, 'PRIVATE-TOKEN' => ENV['GITLAB_PRIVATE_TOKEN']) { |f| f.read }
  File.open(filename, 'w+') do |f|
    f.binmode
    f << data
  end
end

class Webhook < Sinatra::Base
  post '/gitlab' do
    status 204 # successful request with no body content

    request.body.rewind
    payload = JSON.parse(request.body.read)

    now = Time.new.strftime("%Y%m%d%H%M%S")
    File.open("logs/#{now}.log", 'w+') do |f|
      f.puts request.env['HTTP_X_GITLAB_TOKEN']
      f.puts
      f.puts payload
    end

    return unless payload['project']['name'] == 'gitzhou.gitlab.io' &&
                  payload['object_attributes']['status'] == 'success' &&
                  payload['object_attributes']['stages'].length == 2
    return unless request.env['HTTP_X_GITLAB_TOKEN'] == ENV['X_GITLAB_TOKEN_PAGES']

    puts '--------------------- start auto-deploy ---------------------'
    puts now

    artifacts_url = "https://gitlab.com/api/v4/projects/2670720/jobs/artifacts/master/download?job=pages"
    download_gitlab_artifact('tmp/artifacts.zip', artifacts_url)

    system 'unzip tmp/artifacts.zip -d tmp/'
    system 'rm -rf /var/www/aaron67.cc/*'
    system 'cp -R tmp/public/* /var/www/aaron67.cc/.'
    system 'cp tmp/.htaccess /var/www/aaron67.cc/.'
    system 'rm -rf tmp/public tmp/artifacts.zip'

    puts '-------------------------------------------------------------'
  end
end

配置Pipeline events Webhook

Integrations下,添加一个Webhook即可

配置Slack通知

这是一个无意中发现的、锦上添花的功能…

Integrations–>Project services下,找到Slack notifications

根据提示,设置好incoming webhook URL。每当仓库有对应的event发生,Slack就可以收到对应的消息啦,省去了每次push完还得上gitlab.com看看Pipeline成没成功的步骤

最后

现在事情变得简单多了

  • 更新博客,git addgit commitgit push,剩下的,都交给自动化吧
  • 换了电脑,git clonenpm install,同样的配置、熟悉的环境又都回来了
  • 通过Slack App,随时了解仓库的events

完美,哈哈 😝

参考

如果你觉得文章不错,可以请我喝杯咖啡