WebHook结合GitHub Action与Coding实现博客持续集成部署到个人服务器

流程图

前言

什么是GitHub ActionGitHub Page

  • Github Action 可以帮助您自动完成软件开发周期内的任务,它是事件驱动的,而且已经和GitHub深度整合,可以运行很多GitHub事件,最普遍的是推送到master分支。但是actions并不仅仅只是部署和发布,它们都是容器,毫不夸张地说你可以做任何事情 —— 有着无尽的可能性!你可以用它们压缩合并CSSJavaScript,有人在你的项目仓库里创建issue的时候向你发送信息,以及更多……没有任何限制。

  • GitHub Page 是Github提供的一款免费的~,用于托管个人的静态网站,可以用它来搭建私人博客,也算是省去了购买服务器、域名等等一系列复杂的操作。

    规则:建立一个仓库命名为 用户名.github.io官方文档

    使用:仓库下创建一个index.html文件访问网址即可快速查看

什么是Coding

  • CODING 系腾讯旗下全资子公司, 旗下一站式软件研发管理平台—CODING(https://coding.net/ )是一站式软件研发管理协作平台,提供 Git/SVN 代码托管、项目协同、测试管理、制品库、CI/CD 等一系列在线工具,帮助研发团队快速落地敏捷开发与 DevOps 开发方式,提升研发管理效率,实现研发效能升级。

  • Coding 想做的就是帮助开发者能够高效的在云端完成软件开发的工作。代码托管,项目管理,演示平台,质量管理等等都是为了帮助开发者在云端完成一系列高难度的软件开发动作。给开发者提供极致的云端开发体验,强调的是私有库,强调团队协作,强调整合体验,强调访问速度。

  • 为什么要用Coding

对于Coding的作用,其实就类似Gitee一样,属于国内部署,速度提升非常明显,而且还可以被百度收录。由于众说周知的原因,国内访问GitHub速度感人,我也试过一开始直接获取GitHub的代码,但是速度太慢,效率很低,因此最终选择了Coding作为仓库镜像,当然这里换成Gitee也是一样的。

什么是Webhook

  • Webhook是一个API概念,术语“网络钩子”,有时也被称为“反向 API”。因为他提供了API规则,你需要设计要使用的APIWebhook将向你的应用发起http请求,典型的是post请求,应用程序由请求驱动。我们能用事件描述的事物越多,webhook的作用范围也就越大。

  • 准确的说webhook是一种web回调或者http的push API,是向APP或者其他应用提供实时信息的一种方式。Webhook在数据产生时立即发送数据,也就是你能实时收到数据。使用 webhooks,您可以在服务器上发生某些事件时获得推送通知。你可以使用 webhooks“订阅”活动。

需求及场景

众所周知,GitHub作为全球最大同性交友社区,为了与大家更方便友好积极的交流 🐶,我的源码存放地址是存放在GitHub的,内容包括博客的更新、一些测试用例、教程、Demo等。

因为我的博客是部署在阿里云的个人服务器上,有时博客更新又比较频繁,一开始是通过ssh链接到服务器后然后通过命令git clonegit pullnpm run build 这种操作方法去手动进行更新,这里存在几个问题:

  1. 频繁登录服务器,非常繁琐
  2. GitHub 速度问题,每次更新都要等待很久
  3. 服务器性能问题,对于低配服务器执行 npm run build 打包很吃力

思路及流程图

思路

  1. 本地电脑通过git提交到GitHub仓库
  2. GitHub Action监听到push event事件触发ci.yml执行脚本
  3. build打包生成部署文件推送到GitHub gh-pages分支与Coding master分支
  4. Coding设置webhook监听push event事件触发webhook钩子
  5. webhook钩子通信到个人服务器内启动的http server,验证身份与仓库,执行webhook.sh脚本
  6. webhook.sh脚本cd进入nginx下的博客部署目录,进行git pull更新操作
  7. 根据结果生成日志,或添加邮件通知功能(结合Nodemailer库实现)

流程图

流程图

创建个人访问令牌 Access Token

接下来自动化部署与持续集成会用到以https方式提交代码到仓库的方案,这里需要配置下access token个人访问令牌作为环境变量提供给Action与脚本使用。

GitHub Token

第一步,按照官方文档 ,生成一个github token (令牌)。

第二步,将这个密钥储存到当前仓库的Settings/Secrets里面。

Settings/Secrets是储存私密的环境变量的地方。环境变量的名字可以随便起,这里用的是ACCESS_TOKEN。如果你不用这个名字,.github/workflows/ci.yml脚本里的变量名也要跟着改。

Coding Token

第一步,按照官方文档 ,生成一个coding token (令牌)。

第二步,同 GitHub

几种方案

:::tip
这里示例几种我尝试过的方案,下面会仔细分析介绍每种方案的优劣,以及我逐渐改进与尝试的方法,和最终实现。
:::

scp 命令直接部署到服务器

主要思路

通过使用scp命令直接把本地部署文件拷贝至远程服务器

scpsecure copy的简写,是 linux 系统下基于 ssh 登陆进行安全的远程文件拷贝命令,scp传输是加密的。

  1. 本地进行npm run build打包编译,获得部署文件

  2. 直接通过上传替换源文件进行更新

    # 1. 该命令会把当前目录下的dist文件遍历上传到blog目录下:/blog/dist
    scp -r ./dist root@ip/usr/local/app/blog/
    # 2. 输入服务器密码,等待传输完成即可
    # 当然也可以通过ssh的方式无密码上传,但这并不能达到我的最终目的,这里不再赘述
    

最终效果

  • 服务器编译的性能问题已解决
  • GitHub更新拉取速度慢的问题已解决
  • 每次更新执行打包编译、登录操作等步骤依旧繁琐

GitHub Action Pages

主要思路

通过使用GitHub提供的PageAction服务实现gh-pages持续集成与部署

gh-pages 的搭建教程网上很多,具体实现我就不再重复了。
这里我提供一种我的方案:

通过动态生成gh-pages分支并推送到github仓库实现多地址部署

如我的博客的GitHub地址:https://JS-banana.github.io/vuepress

脚本编写

  1. 根目录下创建 .github>workflows>ci.yml

  2. ci.yml文件

    jobs: # 工作流
      build: # 自定义名称
      runs-on: ubuntu-latest #运行在虚拟机环境ubuntu-latest
    
      strategy:
        matrix:
        node-version: [14.x]
    
      steps: # 步骤
        - name: Checkout # 步骤1
        uses: actions/checkout@v1 # 使用的动作。格式:userName/repoName。
        #作用:检出仓库,获取源码。 官方actions库:https://github.com/actions
        - name: Use Node.js ${{ matrix.node-version }} # 步骤2
        uses: actions/setup-node@v1 # 作用:安装nodejs
        with:
          node-version: ${{ matrix.node-version }} # 版本
        - name: Deploy # 步骤3 部署到github gh-pages
        env: # 设置环境变量
          GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} # token私密变量
        run: |
          npm install
          npm run build
          cd ./dist
          git init
          git config user.name "name"
          git config user.email "email"
          git add .
          git commit -m "$(date) Update from Action"
          git push --force --quiet "https://JS-banana:${GITHUB_TOKEN}@github.com/JS-banana/vuepress.git" master:gh-pages
    

    该page项目可以作为备选地址使用,通过CNAME定向到我们自己的域名服务器下。
    CNAME:即别名记录。这种记录允许您将多个名字映射到另外一个域名。

  3. 执行 echo 'ssscode.com' > CNAME 命令,生成 CNAME 文件,然后把CNAME文件放到生成的dist目录下,这一步可以通过bash脚本处理

  4. 我们再把上面的脚本调整下

    jobs: # 工作流
      build: # 自定义名称
      runs-on: ubuntu-latest #运行在虚拟机环境ubuntu-latest
    
      strategy:
        matrix:
        node-version: [14.x]
    
      steps: # 步骤
        - name: Checkout # 步骤1
        uses: actions/checkout@v1 # 使用的动作。格式:userName/repoName。
        #作用:检出仓库,获取源码。 官方actions库:https://github.com/actions
        - name: Use Node.js ${{ matrix.node-version }} # 步骤2
        uses: actions/setup-node@v1 # 作用:安装nodejs
        with:
          node-version: ${{ matrix.node-version }} # 版本
        - name: Deploy # 步骤3 部署到github gh-pages
        env: # 设置环境变量
          GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} # token私密变量
        run: npm install && npm run deploy
    
  5. 创建deploy.sh文件,并在package.json字段scripts下添加"deploy": "bash deploy.sh"命令

    #!/usr/bin/env sh
    
    # 确保脚本抛出遇到的错误
    set -e
    
    # date
    nowDate=$(date "+%Y-%m-%d %H:%M:%S")
    
    # 生成静态文件
    npm run build
    
    # 进入生成的文件夹
    cd ./dist
    
    # CNAME
    echo 'www.ssscode.com\ssscode.com' > CNAME  # 自定义域名
    
    # github url
    githubUrl=https://JS-banana:${GITHUB_TOKEN}@github.com/JS-banana/vuepress.git
    
    # 配置 git 用户信息
    git config --global user.name "JS-banana"
    git config --global user.email "sss213018@163.com"
    
    # commit
    git init
    git add -A
    git commit -m "deploy.sh===>update:${nowDate}"
    git push -f $githubUrl master:gh-pages # 推送到github
    
    cd - # 退回开始所在目录
    rm -rf ./dist
    
  • 到这一步,我们已经完成通过GitHub Action实现持续集成,打包生成部署文件并推送到gh-pages分支。

Coding与GitHub同步部署

这一步其实原理和上面GitHub Action Pages做法一样,而我们要做的最重要的一步,就是把Codingtoken也配置到GitHub仓库下的Settings/Secrets里面。即新增一个环境变量CODING_TOKEN,该方法同样适用于Gitee,想要使用Gitee的小伙伴也可以亲自尝试。

ci.yml文件增加

env: # 设置环境变量
  GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} # github token 私密变量
+ CODING_TOKEN: ${{ secrets.CODING_TOKEN }} # coding token 私密变量

deploy.sh文件调整为

  # github url
  githubUrl=https://JS-banana:${GITHUB_TOKEN}@github.com/JS-banana/vuepress.git

+ # coding url
+ #注意!!!这里需要使用coding提供的个人令牌的用户名和token (https://[name]:[token]@e.coding.net/xx)
+ codingUrl=https://ptzv1yuleer1:${CODING_TOKEN}@e.coding.net/ssscode/blog/vuepress.git 

  git push -f $githubUrl master:gh-pages # 推送到github

- cd - # 退回开始所在目录
- rm -rf ./dist
  
+ git push -f $codingUrl master # 推送到coding

+ cd - # 退回开始所在目录
+ rm -rf ./dist

Webhook与Coding实现持续部署

实现思路

首先,实现一个 nodejs http server用于接收请求,即 webhook.js钩子服务,这里我建议域名解析一个二级域名供webhook专门使用,如:webhook.ssscode.com,当然,直接在当前项目下以 /webhook 路径作为触发路由也可以。这里我们以第一种方案为例,使用nodejspm2守护程序启动webhook.js,然后再通过nginx反向代理本地启动的服务映射到 webhook.ssscode.com 即可。

创建 webhook.js

主要使用了NodeJs模块http(创建server) 与 child_process (执行bash脚本)

废话不多说,直接上代码

// webhook.js
const server = require('http');
const { spawn } = require('child_process');

server
  .createServer((req, res) => {
    // accept request
    console.log(`accept request:${new Date()}`);
    // 接收POST请求
    if (req.method === 'POST') {
      //TODO: secret 验证

      let data = '';

      req.on('data', chunk => {
        data += chunk;
      });

      req.on('end', () => {
        // console.log(JSON.parse(data));
        try {
          const reqData = JSON.parse(data);
          // 确定身份
          if (reqData.pusher.username !== 'xxx') { // coding 个人访问令牌 Access Token 用户名
            res.writeHead(400);
            res.end('noooo!');
            return;
          }
          // 确定分支 master
          if (reqData.ref === 'refs/heads/master') {
            // 确定仓库
            const repository_name = reqData.repository.name;
            runCommand('sh', [`${repository_name}.sh`], console.log);
          }
          // response
          res.writeHead(200);
          res.end('ok');
        } catch (error) {
          console.log('error:', error);
          res.writeHead(500);
          res.end('error!');
        }
      });
    } else {
      res.writeHead(404);
      res.end('no info!');
    }
  })
  .listen(3010); // 端口

// run command
function runCommand(cmd, args, callback) {
  let response = '';
  const child = spawn(cmd, args);
  child.stdout.on('data', buffer => {
    response += buffer.toString();
  });
  child.stdout.on('end', () => callback(response));
}

核心代码是 runCommand 函数,server服务接收请求参数验证身份与仓库信息,满足条件执行对应脚本。这里没有像 github-webhook-handler 包一样对密匙进行处理验证,只是简单的验证了身份和条件。(github、coding等配置webhook时可以设置验证秘钥)

接下来编写我们的bash脚本,这里之后可以优化成执行对应项目下的对应脚本(即 如果存在多个项目或博客,相关脚本在对应项目下创建,由bash执行)。

创建bash脚本

vuepress.sh核心代码

#!/usr/bin/env sh
# 确保脚本抛出遇到的错误
set -e

cd /usr/local/app/vuepress-blog/dist

echo 'start===>git'

# 覆盖更新
git fetch --all
git reset --hard origin/master
# git clean -f
# git pull

cd - # 退回开始所在目录

为了方便记录和查看以及拓展通知消息等,这里增加异常判断处理及日志输出

- cd - # 退回开始所在目录

+ function log_info (){
+   DATE_N=`date "+%Y-%m-%d %H:%M:%S"`
+   USER_N=`whoami`
+   echo "${DATE_N} ${USER_N} execute $0 [INFO] $@" >> /usr/local/app/webhook/logInfo.txt #执行成功日志打印路径
+ }

+ function log_error (){
+   DATE_N=`date "+%Y-%m-%d %H:%M:%S"`
+   USER_N=`whoami`
+   echo -e "\033[41;37m ${DATE_N} ${USER_N} execute $0 [ERROR] $@ \033[0m"  >> /usr/local/app/webhook/logError.txt #执行失败日志打印路径
+ }

+ if [  $? -eq 0  ]
+ then
+   log_info "$@ sucessed."
+   echo -e "\033[32m $@ sucessed. \033[0m"
+ else
+   log_error "$@ failed."
+   echo -e "\033[41;37m $@ failed. \033[0m"
+   exit 1
+ fi

+ trap 'fn_log "DO NOT SEND CTR + C WHEN EXECUTE SCRIPT !!!! "'  2

+ cd - # 退回开始所在目录

创建nginx配置

nginx配置

server {
  #外网
  listen 80;
  server_name webhook.ssscode.com; #域名

  # 监听本地服务
  location / {
    # 开启反向代理
    proxy_pass http://127.0.0.1:3010/;
  }
}

执行 node ./webhook.js 即可启动测试~

pm2

PM2 是 node 进程管理工具,可以利用它来简化很多 node应用管理的繁琐任务,如性能监控、自动重启、负载均衡等,而且使用非常简单。PM2简易使用手册

为了更方便的管理NodeJs程序以及监控,这里推荐使用 pm2 start webhook.js 启动服务

Github或Gitee结合webhook

因为我采用的是Coding结合webhook的方式,如果有小伙伴对其他方式感兴趣,也可以自行搭建,原理类似,目前GithubGitee也有开源的npm包提供快速搭建webhook通信服务。github-webhook-handlergitee-webhook-handler

结语

大功告成~

总的来说就是反复尝试了很多方法,摸索找思路,再不断的改进,最终也是实现了当初的想法,不过也确实走了不少弯路。在此,把整个过程记录下来,供自己参考也为了加深理解,希望能帮到有需要的小伙伴,少走些弯路~

如果自己全部配置各种服务与代码业务逻辑,也确实挺花费时间和精力的,涉及的技术点也比较杂,很多地方只能达到勉强使用,还差得远,整个过程也是边学习便尝试。不过,全部弄完之后,对于自己的技术成长与业务理解也是很有帮助的~

类似Hexo这样的快速搭建博客的框架用起来倒是可以省不少事,感兴趣的可以尝试下,这个我也有在弄,不过还没达到我的预期效果,看看之后有没有可写的东西再分享分享吧

之后对于Docker也是很有必要深入学习一番了~

2021.06.08 补充

使用 Travis 结合 sshpass 工具实现本机文件上传到远程服务器。

.travis.yml

language: node_js
node_js:
  - 12
branchs:
  - master
addons:
  apt:
    packages:
    - sshpass
install:
  "npm install"
script:
  - "npm run build"
after_success:
  - ./deploy.sh

deploy.sh

#!/usr/bin/env sh

# 确保脚本抛出遇到的错误
set -e

# 打包静态资源
npm run build

# 将dist文件发送到远程
sshpass -p ${serverPass} scp -o stricthostkeychecking=no -r dist/ root@${serverIP}:/home/web/movie-trailer

优点:方便快捷

缺点:使用 sshpass 是最不安全的,因为所有系统上的用户在命令行中通过简单的 “ps” 命令就可看到密码。

说明

原文博客:https://ssscode.com/pages/337720/

参考

https://coding.net/

https://docs.github.com/cn/actions

https://xugaoyi.com/pages/6b9d359ec5aa5019/

https://docs.github.com/cn/github/authenticating-to-github/creating-a-personal-access-token

https://help.coding.net/docs/member/tokens.html

https://ipcmen.com/

http://www.ruanyifeng.com/blog/2016/07/yaml.html

https://juejin.cn/post/6844903710037016584

https://linux.cn/article-8086-1.html

https://bbs.huaweicloud.com/blogs/152237

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

推荐阅读更多精彩内容