最近因为个人需要就用 node 写了个小项目,想到现在基本上都用 node 来写服务端,于是写的时候就想要顺便试试用 GitHub Actions 来做自动部署,这样就不用写完还要我自己构建代码然后再打开宝塔面板上传文件再重启服务了
这个 action 的前提条件是使用宝塔面板,已经创建了项目并且正在运行中。等现在这个服务器到期迁移到新服务器之后我打算扔掉宝塔面板,到时候会研究新的 CI/CD,大概率是用 pm2 吧
设计思路
- 从 package.json 中获取当前的版本
- 通过接口获取线上运行的版本
- 对比版本号,版本号一致则结束任务,不一致则开始进行构建
- 构建代码,上传
- 运行宝塔的重启脚本重启服务端
完整 Workflow
yaml
name: Node Service CI/CD
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Read package info
id: pkg
run: |
NAME=$(jq -r .name package.json)
VERSION=$(jq -r .version package.json)
echo "name=$NAME" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Fetch remote version
id: remote
run: |
RESPONSE=$(curl -s --max-time 20 https://${{ secrets.API_URL }}/info || echo "fail")
if [ "$RESPONSE" = "fail" ]; then
REMOTE_V="none"
else
REMOTE_V=$(echo "$RESPONSE" | jq -r '.data.version // "none"')
fi
echo "version=$REMOTE_V" >> $GITHUB_OUTPUT
- name: Compare version
id: compare
run: |
LOCAL="${{ steps.pkg.outputs.version }}"
REMOTE="${{ steps.remote.outputs.version }}"
if [ "$LOCAL" = "$REMOTE" ]; then
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Setup Node
if: steps.compare.outputs.skip == 'false'
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Install dependencies
if: steps.compare.outputs.skip == 'false'
run: npm ci
- name: Build
if: steps.compare.outputs.skip == 'false'
run: npm run build
- name: Prepare target directory
if: steps.compare.outputs.skip == 'false'
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_IP }}
username: github_upload
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
mkdir -p "/www/wwwroot/${{ steps.pkg.outputs.name }}"
rm -rf "/www/wwwroot/${{ steps.pkg.outputs.name }}/*"
- name: Upload dist
if: steps.compare.outputs.skip == 'false'
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.SERVER_IP }}
username: github_upload
key: ${{ secrets.SERVER_SSH_KEY }}
source: 'dist/*'
target: '/www/wwwroot/${{ steps.pkg.outputs.name }}'
strip_components: 1
- name: Restart service
if: steps.compare.outputs.skip == 'false'
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_IP }}
username: ${{ secrets.SERVER_USERNAME }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
sudo /www/server/nodejs/vhost/scripts/deploy_restart.sh "${{ steps.pkg.outputs.name }}"部分内容解释
-
构建与上传
构建与上传时会使用 package.json 中的
name来作为项目名,将构建完成的文件上传到相应的路径中,重启服务时使用的也是这个项目名,需要注意 -
Workflow 需要的 secret 列表
secret 说明 示例 API_URL 线上服务的 API 地址,不需要协议 api.chiyukiruon.com SERVER_IP 服务器地址 - SERVER_USERNAME 服务器登录用户名 - SERVER_SSH_KEY 服务器登录私钥 - -
版本检查接口
对应的服务需要实现
/info接口来实现版本检查功能,接口返回值示例:json{ "code": 200, "message": "Service is ready", "data": { "service": "API Service", "version": "1.6.2", "environment": "production" } } -
重启服务
重启服务使用的是宝塔面板的重启脚本,只有使用这个脚本重启才能让宝塔面板识别到项目已经启动,默认情况下这个脚本的路径是
/www/server/nodejs/vhost/scripts/deploy_restart.sh,只要后面再加上项目名称就可以重启对应项目了