WebHook結(jié)合GitHub Action與Coding實(shí)現(xiàn)博客持續(xù)集成部署到個(gè)人服務(wù)器

流程圖

前言

什么是GitHub ActionGitHub Page

  • Github Action 可以幫助您自動(dòng)完成軟件開發(fā)周期內(nèi)的任務(wù),它是事件驅(qū)動(dòng)的,而且已經(jīng)和GitHub深度整合,可以運(yùn)行很多GitHub事件,最普遍的是推送到master分支。但是actions并不僅僅只是部署和發(fā)布,它們都是容器,毫不夸張地說你可以做任何事情 —— 有著無盡的可能性!你可以用它們壓縮合并CSSJavaScript,有人在你的項(xiàng)目倉庫里創(chuàng)建issue的時(shí)候向你發(fā)送信息,以及更多……沒有任何限制。

  • GitHub Page 是Github提供的一款免費(fèi)的~,用于托管個(gè)人的靜態(tài)網(wǎng)站,可以用它來搭建私人博客,也算是省去了購買服務(wù)器、域名等等一系列復(fù)雜的操作。

    規(guī)則:建立一個(gè)倉庫命名為 用戶名.github.io ,官方文檔

    使用:倉庫下創(chuàng)建一個(gè)index.html文件訪問網(wǎng)址即可快速查看

什么是Coding

  • CODING 系騰訊旗下全資子公司, 旗下一站式軟件研發(fā)管理平臺(tái)—CODING(https://coding.net/ )是一站式軟件研發(fā)管理協(xié)作平臺(tái),提供 Git/SVN 代碼托管、項(xiàng)目協(xié)同、測試管理、制品庫、CI/CD 等一系列在線工具,幫助研發(fā)團(tuán)隊(duì)快速落地敏捷開發(fā)與 DevOps 開發(fā)方式,提升研發(fā)管理效率,實(shí)現(xiàn)研發(fā)效能升級。

  • Coding 想做的就是幫助開發(fā)者能夠高效的在云端完成軟件開發(fā)的工作。代碼托管,項(xiàng)目管理,演示平臺(tái),質(zhì)量管理等等都是為了幫助開發(fā)者在云端完成一系列高難度的軟件開發(fā)動(dòng)作。給開發(fā)者提供極致的云端開發(fā)體驗(yàn),強(qiáng)調(diào)的是私有庫,強(qiáng)調(diào)團(tuán)隊(duì)協(xié)作,強(qiáng)調(diào)整合體驗(yàn),強(qiáng)調(diào)訪問速度。

  • 為什么要用Coding?

對于Coding的作用,其實(shí)就類似Gitee一樣,屬于國內(nèi)部署,速度提升非常明顯,而且還可以被百度收錄。由于眾說周知的原因,國內(nèi)訪問GitHub速度感人,我也試過一開始直接獲取GitHub的代碼,但是速度太慢,效率很低,因此最終選擇了Coding作為倉庫鏡像,當(dāng)然這里換成Gitee也是一樣的。

什么是Webhook

  • Webhook是一個(gè)API概念,術(shù)語“網(wǎng)絡(luò)鉤子”,有時(shí)也被稱為“反向 API”。因?yàn)樗峁┝薃PI規(guī)則,你需要設(shè)計(jì)要使用的API。Webhook將向你的應(yīng)用發(fā)起http請求,典型的是post請求,應(yīng)用程序由請求驅(qū)動(dòng)。我們能用事件描述的事物越多,webhook的作用范圍也就越大。

  • 準(zhǔn)確的說webhook是一種web回調(diào)或者h(yuǎn)ttp的push API,是向APP或者其他應(yīng)用提供實(shí)時(shí)信息的一種方式。Webhook在數(shù)據(jù)產(chǎn)生時(shí)立即發(fā)送數(shù)據(jù),也就是你能實(shí)時(shí)收到數(shù)據(jù)。使用 webhooks,您可以在服務(wù)器上發(fā)生某些事件時(shí)獲得推送通知。你可以使用 webhooks“訂閱”活動(dòng)。

需求及場景

眾所周知,GitHub作為全球最大同性交友社區(qū),為了與大家更方便友好積極的交流 ??,我的源碼存放地址是存放在GitHub的,內(nèi)容包括博客的更新、一些測試用例、教程、Demo等。

因?yàn)槲业牟┛褪遣渴鹪诎⒗镌频膫€(gè)人服務(wù)器上,有時(shí)博客更新又比較頻繁,一開始是通過ssh鏈接到服務(wù)器后然后通過命令git clone、git pull、npm run build 這種操作方法去手動(dòng)進(jìn)行更新,這里存在幾個(gè)問題:

  1. 頻繁登錄服務(wù)器,非常繁瑣
  2. GitHub 速度問題,每次更新都要等待很久
  3. 服務(wù)器性能問題,對于低配服務(wù)器執(zhí)行 npm run build 打包很吃力

思路及流程圖

思路

  1. 本地電腦通過git提交到GitHub倉庫
  2. GitHub Action監(jiān)聽到push event事件觸發(fā)ci.yml執(zhí)行腳本
  3. build打包生成部署文件推送到GitHub gh-pages分支與Coding master分支
  4. Coding設(shè)置webhook監(jiān)聽push event事件觸發(fā)webhook鉤子
  5. webhook鉤子通信到個(gè)人服務(wù)器內(nèi)啟動(dòng)的http server,驗(yàn)證身份與倉庫,執(zhí)行webhook.sh腳本
  6. webhook.sh腳本cd進(jìn)入nginx下的博客部署目錄,進(jìn)行git pull更新操作
  7. 根據(jù)結(jié)果生成日志,或添加郵件通知功能(結(jié)合Nodemailer庫實(shí)現(xiàn))

流程圖

流程圖

創(chuàng)建個(gè)人訪問令牌 Access Token

接下來自動(dòng)化部署與持續(xù)集成會(huì)用到以https方式提交代碼到倉庫的方案,這里需要配置下access token個(gè)人訪問令牌作為環(huán)境變量提供給Action與腳本使用。

GitHub Token

第一步,按照官方文檔 ,生成一個(gè)github token (令牌)。

第二步,將這個(gè)密鑰儲(chǔ)存到當(dāng)前倉庫的Settings/Secrets里面。

Settings/Secrets是儲(chǔ)存私密的環(huán)境變量的地方。環(huán)境變量的名字可以隨便起,這里用的是ACCESS_TOKEN。如果你不用這個(gè)名字,.github/workflows/ci.yml腳本里的變量名也要跟著改。

Coding Token

第一步,按照官方文檔 ,生成一個(gè)coding token (令牌)。

第二步,同 GitHub

幾種方案

:::tip
這里示例幾種我嘗試過的方案,下面會(huì)仔細(xì)分析介紹每種方案的優(yōu)劣,以及我逐漸改進(jìn)與嘗試的方法,和最終實(shí)現(xiàn)。
:::

scp 命令直接部署到服務(wù)器

主要思路

通過使用scp命令直接把本地部署文件拷貝至遠(yuǎn)程服務(wù)器

scpsecure copy的簡寫,是 linux 系統(tǒng)下基于 ssh 登陸進(jìn)行安全的遠(yuǎn)程文件拷貝命令,scp傳輸是加密的。

  1. 本地進(jìn)行npm run build打包編譯,獲得部署文件

  2. 直接通過上傳替換源文件進(jìn)行更新

    # 1. 該命令會(huì)把當(dāng)前目錄下的dist文件遍歷上傳到blog目錄下:/blog/dist
    scp -r ./dist root@ip/usr/local/app/blog/
    # 2. 輸入服務(wù)器密碼,等待傳輸完成即可
    # 當(dāng)然也可以通過ssh的方式無密碼上傳,但這并不能達(dá)到我的最終目的,這里不再贅述
    

最終效果

  • 服務(wù)器編譯的性能問題已解決
  • GitHub更新拉取速度慢的問題已解決
  • 每次更新執(zhí)行打包編譯、登錄操作等步驟依舊繁瑣

GitHub Action Pages

主要思路

通過使用GitHub提供的PageAction服務(wù)實(shí)現(xiàn)gh-pages持續(xù)集成與部署

gh-pages 的搭建教程網(wǎng)上很多,具體實(shí)現(xiàn)我就不再重復(fù)了。
這里我提供一種我的方案:

通過動(dòng)態(tài)生成gh-pages分支并推送到github倉庫實(shí)現(xiàn)多地址部署

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

腳本編寫

  1. 根目錄下創(chuàng)建 .github>workflows>ci.yml

  2. ci.yml文件

    jobs: # 工作流
      build: # 自定義名稱
      runs-on: ubuntu-latest #運(yùn)行在虛擬機(jī)環(huán)境ubuntu-latest
    
      strategy:
        matrix:
        node-version: [14.x]
    
      steps: # 步驟
        - name: Checkout # 步驟1
        uses: actions/checkout@v1 # 使用的動(dòng)作。格式: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: # 設(shè)置環(huán)境變量
          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項(xiàng)目可以作為備選地址使用,通過CNAME定向到我們自己的域名服務(wù)器下。
    CNAME:即別名記錄。這種記錄允許您將多個(gè)名字映射到另外一個(gè)域名。

  3. 執(zhí)行 echo 'ssscode.com' > CNAME 命令,生成 CNAME 文件,然后把CNAME文件放到生成的dist目錄下,這一步可以通過bash腳本處理

  4. 我們再把上面的腳本調(diào)整下

    jobs: # 工作流
      build: # 自定義名稱
      runs-on: ubuntu-latest #運(yùn)行在虛擬機(jī)環(huán)境ubuntu-latest
    
      strategy:
        matrix:
        node-version: [14.x]
    
      steps: # 步驟
        - name: Checkout # 步驟1
        uses: actions/checkout@v1 # 使用的動(dòng)作。格式: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: # 設(shè)置環(huán)境變量
          GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} # token私密變量
        run: npm install && npm run deploy
    
  5. 創(chuàng)建deploy.sh文件,并在package.json字段scripts下添加"deploy": "bash deploy.sh"命令

    #!/usr/bin/env sh
    
    # 確保腳本拋出遇到的錯(cuò)誤
    set -e
    
    # date
    nowDate=$(date "+%Y-%m-%d %H:%M:%S")
    
    # 生成靜態(tài)文件
    npm run build
    
    # 進(jìn)入生成的文件夾
    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
    
  • 到這一步,我們已經(jīng)完成通過GitHub Action實(shí)現(xiàn)持續(xù)集成,打包生成部署文件并推送到gh-pages分支。

Coding與GitHub同步部署

這一步其實(shí)原理和上面GitHub Action Pages做法一樣,而我們要做的最重要的一步,就是把Codingtoken也配置到GitHub倉庫下的Settings/Secrets里面。即新增一個(gè)環(huán)境變量CODING_TOKEN,該方法同樣適用于Gitee,想要使用Gitee的小伙伴也可以親自嘗試。

ci.yml文件增加

env: # 設(shè)置環(huán)境變量
  GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} # github token 私密變量
+ CODING_TOKEN: ${{ secrets.CODING_TOKEN }} # coding token 私密變量

deploy.sh文件調(diào)整為

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

+ # coding url
+ #注意?。?!這里需要使用coding提供的個(gè)人令牌的用戶名和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實(shí)現(xiàn)持續(xù)部署

實(shí)現(xiàn)思路

首先,實(shí)現(xiàn)一個(gè) nodejs http server用于接收請求,即 webhook.js鉤子服務(wù),這里我建議域名解析一個(gè)二級域名供webhook專門使用,如:webhook.ssscode.com,當(dāng)然,直接在當(dāng)前項(xiàng)目下以 /webhook 路徑作為觸發(fā)路由也可以。這里我們以第一種方案為例,使用nodejspm2守護(hù)程序啟動(dòng)webhook.js,然后再通過nginx反向代理本地啟動(dòng)的服務(wù)映射到 webhook.ssscode.com 即可。

創(chuàng)建 webhook.js

主要使用了NodeJs模塊http(創(chuàng)建server) 與 child_process (執(zhí)行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 驗(yàn)證

      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 個(gè)人訪問令牌 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 函數(shù),server服務(wù)接收請求參數(shù)驗(yàn)證身份與倉庫信息,滿足條件執(zhí)行對應(yīng)腳本。這里沒有像 github-webhook-handler 包一樣對密匙進(jìn)行處理驗(yàn)證,只是簡單的驗(yàn)證了身份和條件。(github、coding等配置webhook時(shí)可以設(shè)置驗(yàn)證秘鑰)

接下來編寫我們的bash腳本,這里之后可以優(yōu)化成執(zhí)行對應(yīng)項(xiàng)目下的對應(yīng)腳本(即 如果存在多個(gè)項(xiàng)目或博客,相關(guān)腳本在對應(yīng)項(xiàng)目下創(chuàng)建,由bash執(zhí)行)。

創(chuàng)建bash腳本

vuepress.sh核心代碼

#!/usr/bin/env sh
# 確保腳本拋出遇到的錯(cuò)誤
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 #執(zhí)行成功日志打印路徑
+ }

+ 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 #執(zhí)行失敗日志打印路徑
+ }

+ 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 - # 退回開始所在目錄

創(chuàng)建nginx配置

nginx配置

server {
  #外網(wǎng)
  listen 80;
  server_name webhook.ssscode.com; #域名

  # 監(jiān)聽本地服務(wù)
  location / {
    # 開啟反向代理
    proxy_pass http://127.0.0.1:3010/;
  }
}

執(zhí)行 node ./webhook.js 即可啟動(dòng)測試~

pm2

PM2 是 node 進(jìn)程管理工具,可以利用它來簡化很多 node應(yīng)用管理的繁瑣任務(wù),如性能監(jiān)控、自動(dòng)重啟、負(fù)載均衡等,而且使用非常簡單。PM2簡易使用手冊

為了更方便的管理NodeJs程序以及監(jiān)控,這里推薦使用 pm2 start webhook.js 啟動(dòng)服務(wù)

Github或Gitee結(jié)合webhook

因?yàn)槲也捎玫氖?code>Coding結(jié)合webhook的方式,如果有小伙伴對其他方式感興趣,也可以自行搭建,原理類似,目前GithubGitee也有開源的npm包提供快速搭建webhook通信服務(wù)。github-webhook-handler、gitee-webhook-handler

結(jié)語

大功告成~

總的來說就是反復(fù)嘗試了很多方法,摸索找思路,再不斷的改進(jìn),最終也是實(shí)現(xiàn)了當(dāng)初的想法,不過也確實(shí)走了不少彎路。在此,把整個(gè)過程記錄下來,供自己參考也為了加深理解,希望能幫到有需要的小伙伴,少走些彎路~

如果自己全部配置各種服務(wù)與代碼業(yè)務(wù)邏輯,也確實(shí)挺花費(fèi)時(shí)間和精力的,涉及的技術(shù)點(diǎn)也比較雜,很多地方只能達(dá)到勉強(qiáng)使用,還差得遠(yuǎn),整個(gè)過程也是邊學(xué)習(xí)便嘗試。不過,全部弄完之后,對于自己的技術(shù)成長與業(yè)務(wù)理解也是很有幫助的~

類似Hexo這樣的快速搭建博客的框架用起來倒是可以省不少事,感興趣的可以嘗試下,這個(gè)我也有在弄,不過還沒達(dá)到我的預(yù)期效果,看看之后有沒有可寫的東西再分享分享吧

之后對于Docker也是很有必要深入學(xué)習(xí)一番了~

2021.06.08 補(bǔ)充

使用 Travis 結(jié)合 sshpass 工具實(shí)現(xiàn)本機(jī)文件上傳到遠(yuǎn)程服務(wù)器。

.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

# 確保腳本拋出遇到的錯(cuò)誤
set -e

# 打包靜態(tài)資源
npm run build

# 將dist文件發(fā)送到遠(yuǎn)程
sshpass -p ${serverPass} scp -o stricthostkeychecking=no -r dist/ root@${serverIP}:/home/web/movie-trailer

優(yōu)點(diǎn):方便快捷

缺點(diǎn):使用 sshpass 是最不安全的,因?yàn)樗邢到y(tǒng)上的用戶在命令行中通過簡單的 “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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容