使用 Travis CI 自动更新 GitHub Pages

每次更改完 NexT 文档 都要手动部署到 GitHub Pages,重复的次数多了就显得很麻烦,出错的几率也会变大。文档源码放置在 master 分支,最终部署文件在 gh-pages 分支。当在 master 分支更改某些内容之后,通过运行 gulp dist 来生成最终部署的 HTML 文件到 dist 目录,随后再进入 dist 目录初始化 git 仓库、添加文件、提交文件,最后将提交强制推送到远程 gh-pages 分支(因当心我会误将最终部署的 HTML 文件提交到 master 分支导致源码丢失,我在 GitHub 上把 master 分支给锁定了)。除此之外还有另外一个问题:如果 master 分支有 Pull Requests,我需要先将更新取回本地,然后编译更新再提交回远程 gh-pages 分支。

年轻的想法

于是,我就想这说将这个过程自动化。首先考虑了使用 GitHub Webhooks,这是 Github 提供的一种机制,使应用能与 Github 通讯。这种机制实际上就是 Pub/Sub,当 Github 监测到资源(如仓库)有变化就往预先设定的 URL 发送一个 POST 请求(Pub),告知变化情况,而后接收变化的服务器(Sub)即可做一些额外的事情。

这个思路需要有一个服务器并启动一个服务来接收 Github 的请求。这里又有种不同的策略,这两种策略都是基于源码放置在 Github 的前提。第一个是源码将最终文档直接部署在这台服务器上(如使用 Nginx),当接收到 Github 通知直接编译更新到服务器指定的文件夹下即可。另一种策略是当服务器接收到通知后编译更新,而后将编译后的版本提交到 Github 仓库的 gh-pages 分支,让 Github 做 Host。

Travis CI 登场

由于比较穷,租不起服务器(果然有点年轻),于是就想说有没有免费的方案可以用。于是就惦记起各种 CI 服务,本着够用就好少折腾的原则直接选了 Travis CI,对外宣称是免费的还要求那么多不是太合适,其实本质是读英文文档嫌累π_π。没有折腾精神的开发不是一个好开发,这能怪我么,好吧,确实是我的错,我太穷了。

Poor Guy

那么,终归是找到了解决方案。有了持续构建系统,就可以做到有更新自动 Build,意味着就可以多看两集韩国噢八剧,一集英国基情剧,N 集日本爱情剧以及英雄联盟。那么预想的整个流程就是 :

  • 更新代码到 Github
  • Github 跑去告诉 Travis CI 说有个东西变了
  • Travis CI 勃然大怒、立马安排 Build
  • Travis CI build 成功后,将输出丢到 Github gh-pages 分支
  • Github build Pages
  • 领盒饭收工

非常 Easy,哪里不会去 Google!

那些年的那些坑

接下来就是实际动手了。注册 Travis CI,Github 集成 Travis CI ,Travis CI 为特定的仓库变动自动 Build 这些事情就是像个 Boss 一样点点鼠标就行了。问题就在于最后一步,怎么让 Travis CI 往 Github 提交代码呢?将 Github 帐号信息写在 .travis.yml 文件里毕竟是一个太过年轻的做法。然后我找到了 Hexo 作者 SkyArrow这篇文章,通篇看下来,我长嘘了一口气在想要不就老老实实手动编译更新吧。然而,就在我长嘘短叹人生之艰难的时候,突然发现 Travis CI 除了支持 加密文件 也支持 加密 Token Key。于是,事情有了转机。

另一种方案

这种方案不再使用 RSA 加密算法去生成一对密钥,取而代之的是 Github 提供的 Personal Access Token。这个 Token 与 帐号密码 以及 SSH Keys 同样具有 Github 写入能力,因此只要使用 Travis CI 提供的加密工具来加密这个 Token 即可。

方案原理

根据 Travis CI 的文档,他会使用一对 Key Pair 中的 Public Key 来加密你提供的 Token 得到一个 Secure Token(这个 Secure Token 可以安全地放置在 .travis.yml 中),而在 Build 的时候他会使用 Private Key 来解密这个 Secure Token 获取最初提供的 Github Personal Access Token,见下图:

Travis CI - Encrypt Keys

具体操作

了解了背后的原理就可以大胆地去着手实现。按照原理来讲,大致需要三个步骤,第一获取 GitHub Personal Access Token;第二使用 Travis CI 的工具加密这个 Token,并保存到 .travis.yml 文件中;第三配置文件使用 Access Token。具体的操作步骤如下:

  1. 生成一个 Github Personal Access Token。前往 Github 帐号 Settings 页面,在左侧选择 Personal Access Token,然后在右侧面板点击 “Generate new token” 来新建一个 Token。需要注意的是,创建完的 Token 只有第一次可见,之后再访问就无法看见(只能看见他的名称),因此要保存好这个值。

  2. 使用 Travis CI 的 命令行工具 加密 GitHub 的 Personal Access Token。这个工具是一个 gem 包,因此需要 Ruby 环境。假设已经安装好 Ruby 环境,即可安装 Travis CI 的命令行工具,以及加密:

    1
    2
    3
    4
    5
    # 安装 Travis CI 命令行工具
    gem install travis

    # 加密 Personal Access Token
    travis encrypt -r iissnan/theme-next-docs GH_Token=XXX

    第二条命令中 -r 后的参数是 GitHub 仓库的名字(<用户名>/<仓库名>);GH_TOKEN 将作为环境变量使用。将这条命令输出的结果复制到 .travis.yml 文件下:

    1
    2
    3
    4
    env:
    global:
    - GH_REF: github.com/iissnan/theme-next-docs.git
    - secure: "XXXXXX"

    这个设置之中包含了 仓库的地址(设置在 GH_REF 环境变量中)以及 Access Token (被加密了,设置在 GH_TOKEN 环境变量中)。这两个环境变量将 Build 的时候被使用,用于往 GitHub gh-pages 分支推送。

  3. 让 Travis CI 往 Github 仓库的 gh-pages 分支提交。根据需求的不同,这里的配置也不尽相同,但重要的是如何使用 GH_REF 和 GH_TOKEN 这两个环境变量。NexT 文档是执行 gulp dist 来生成最终的 HTML文件,然后推送到 GitHub Pages,其配置如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    # S: Build Lifecycle
    install:
    - npm install

    before_script:
    - npm install -g gulp

    script:
    - gulp dist

    after_script:
    - cd dist
    - git init
    - git config user.name "iissnan"
    - git config user.email "email_address"
    - git add .
    - git commit -m "Update docs"
    - git push -f "https://${GH_TOKEN}@${GH_REF}" master:gh-pages
    # E: Build LifeCycle

到此,整个流程就结束了。NexT 文档使用的配置在 这里,有需要的可以参考下。