前言
什么是GitHub Action
、GitHub Page
Github Action 可以帮助您自动完成软件开发周期内的任务,它是事件驱动的,而且已经和
GitHub
深度整合,可以运行很多GitHub
事件,最普遍的是推送到master
分支。但是actions
并不仅仅只是部署和发布,它们都是容器,毫不夸张地说你可以做任何事情 —— 有着无尽的可能性!你可以用它们压缩合并CSS
和JavaScript
,有人在你的项目仓库里创建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规则,你需要设计要使用的API
。Webhook
将向你的应用发起http
请求,典型的是post
请求,应用程序由请求驱动。我们能用事件描述的事物越多,webhook
的作用范围也就越大。准确的说
webhook
是一种web回调或者http的push API
,是向APP或者其他应用提供实时信息的一种方式。Webhook
在数据产生时立即发送数据,也就是你能实时收到数据。使用webhooks
,您可以在服务器上发生某些事件时获得推送通知。你可以使用 webhooks“订阅”活动。
需求及场景
众所周知,GitHub
作为全球最大同性交友社区,为了与大家更方便友好积极的交流 🐶,我的源码存放地址是存放在GitHub
的,内容包括博客的更新、一些测试用例、教程、Demo等。
因为我的博客是部署在阿里云的个人服务器上,有时博客更新又比较频繁,一开始是通过ssh链接到服务器后然后通过命令git clone
、git pull
、npm run build
这种操作方法去手动进行更新,这里存在几个问题:
- 频繁登录服务器,非常繁琐
-
GitHub
速度问题,每次更新都要等待很久 - 服务器性能问题,对于低配服务器执行
npm run build
打包很吃力
思路及流程图
思路
- 本地电脑通过
git
提交到GitHub
仓库 -
GitHub Action
监听到push event
事件触发ci.yml
执行脚本 -
build
打包生成部署文件推送到GitHub gh-pages
分支与Coding master
分支 -
Coding
设置webhook
监听push event
事件触发webhook
钩子 -
webhook
钩子通信到个人服务器内启动的http server
,验证身份与仓库,执行webhook.sh
脚本 -
webhook.sh
脚本cd
进入nginx
下的博客部署目录,进行git pull
更新操作 - 根据结果生成日志,或添加邮件通知功能(结合
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
命令直接把本地部署文件拷贝至远程服务器
scp
是secure copy
的简写,是linux
系统下基于ssh
登陆进行安全的远程文件拷贝命令,scp
传输是加密的。
本地进行
npm run build
打包编译,获得部署文件-
直接通过上传替换源文件进行更新
# 1. 该命令会把当前目录下的dist文件遍历上传到blog目录下:/blog/dist scp -r ./dist root@ip/usr/local/app/blog/ # 2. 输入服务器密码,等待传输完成即可 # 当然也可以通过ssh的方式无密码上传,但这并不能达到我的最终目的,这里不再赘述
最终效果
- 服务器编译的性能问题已解决
- GitHub更新拉取速度慢的问题已解决
- 每次更新执行打包编译、登录操作等步骤依旧繁琐
GitHub Action Pages
主要思路
通过使用GitHub
提供的Page
与 Action
服务实现gh-pages
持续集成与部署
gh-pages 的搭建教程网上很多,具体实现我就不再重复了。
这里我提供一种我的方案:
通过动态生成gh-pages
分支并推送到github仓库实现多地址部署
如我的博客的GitHub
地址:https://JS-banana.github.io/vuepress
脚本编写
根目录下创建
.github>workflows>ci.yml
-
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:即别名记录。这种记录允许您将多个名字映射到另外一个域名。 执行
echo 'ssscode.com' > CNAME
命令,生成CNAME
文件,然后把CNAME
文件放到生成的dist
目录下,这一步可以通过bash
脚本处理-
我们再把上面的脚本调整下
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
-
创建
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做法一样,而我们要做的最重要的一步,就是把Coding的token
也配置到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
路径作为触发路由也可以。这里我们以第一种方案为例,使用nodejs
的pm2
守护程序启动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
的方式,如果有小伙伴对其他方式感兴趣,也可以自行搭建,原理类似,目前Github
与Gitee
也有开源的npm
包提供快速搭建webhook通信服务。github-webhook-handler、gitee-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://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
http://www.ruanyifeng.com/blog/2016/07/yaml.html
https://juejin.cn/post/6844903710037016584