背景
最近准备把一个开发板用起来,想着做一个开发环境,用来部署前端和后端服务。于是开始调研如何在一块没有公网IP的开发板上做持续部署。
技术方案
由于代码时存储在github上的,部署是在办公室的开发板,连上了wifi,但没有公网ip,办法像服务器那样直接使用ssh进入服务器进行部署。
在开发板上做CD,我们主要解决两个问题,一是在提交代码后,如何触发部署的事件,可以使用GitHub Action,还有一种方法是使用GitHub的 WebHook。
另外一个难题是,在公网里如何知道开发板的IP,这个问题可以使用nodejs的包 localtunnel来解决。 https://localtunnel.github.io/www/
查看我之前的一篇文章 https://blog.csdn.net/github_35631540/article/details/139606085
配置
首先我们利用nodejs在 开发板里启动一个服务,
代码大致如下
// deploy-server.js
const express = require('express');
const crypto = require('crypto');
const { exec } = require('child_process');
const app = express();
const WEBHOOK_SECRET = '1234567'; // 需与GitHub设置一致
const PORT = 3300;
app.use(express.json());
app.post('/webhook', (req, res) => {
// 1. 验证签名
const sig = req.headers['x-hub-signature-256'];
const hmac = crypto.createHmac('sha256', WEBHOOK_SECRET);
const digest = 'sha256=' + hmac.update(JSON.stringify(req.body)).digest('hex');
console.log('webhook req: ')
console.log(req)
if (sig !== digest) {
return res.status(403).send('Invalid signature');
}
// 2. 只处理push事件
if (req.headers['x-github-event'] !== 'push') {
return res.status(200).send('Ignored event');
}
// 3. 执行部署脚本
exec('./deploy.sh', (error, stdout, stderr) => {
if (error) {
console.error(`部署失败: ${error}`);
return res.status(500).send('Deployment failed');
}
console.log(`部署成功: ${stdout}`);
res.status(200).send('Deployment succeeded');
});
});
// 再写一个get请求
app.get('/hi', (req, res) => {
res.send('Hello World!');
})
app.listen(PORT, () => {
console.log(`Webhook服务运行在 http://localhost:${PORT}`);
});
deploy.sh 的内容
#!/bin/bash
echo "Deploying to devBoardCICD"
# cd /path/to/your/project
# git pull origin main
# npm install
# pm2 restart all # 如果使用pm2管理进程
启动服务,服务会在3300端口暴露出来。
然后再使用localtunnel 将其暴露到外网
运行 lt --port 3300
your url is: https://neat-walls-nail.loca.lt
这个地址是可以直接被webhook访问到的,并且不需要输入密码。
在github的仓库里 配置webhook,如https://github.com/PmcFizz/Lumos/settings/hooks
添加webhook时就这样配置,密钥,payload 地址。
测试
在webhook里 你可以获取非常多,非常详细的信息
body: {
ref: 'refs/heads/dev',
before: '9287f85fd1655e359b63991436a730f6970d0af3',
after: '2e6aca35db7f4d3ec433de794050d5f4d2498f09',
repository: {
id: 1007467682,
node_id: 'R_kgDOPAy8og',
name: 'xxxxx-admin',
full_name: 'el-xxxxx/xxxxx-admin',
private: true,
owner: [Object],
html_url: 'https://github.com/el-xxxxx/xxxxx-admin',
description: null,
fork: false,
url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin',
forks_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/forks',
keys_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/keys{/key_id}',
collaborators_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/collaborators{/collaborator}',
teams_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/teams',
hooks_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/hooks',
issue_events_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/issues/events{/number}',
events_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/events',
assignees_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/assignees{/user}',
branches_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/branches{/branch}',
tags_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/tags',
blobs_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/git/blobs{/sha}',
git_tags_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/git/tags{/sha}',
git_refs_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/git/refs{/sha}',
trees_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/git/trees{/sha}',
statuses_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/statuses/{sha}',
languages_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/languages',
stargazers_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/stargazers',
contributors_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/contributors',
subscribers_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/subscribers',
subscription_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/subscription',
commits_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/commits{/sha}',
git_commits_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/git/commits{/sha}',
comments_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/comments{/number}',
issue_comment_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/issues/comments{/number}',
contents_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/contents/{+path}',
compare_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/compare/{base}...{head}',
merges_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/merges',
archive_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/{archive_format}{/ref}',
downloads_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/downloads',
issues_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/issues{/number}',
pulls_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/pulls{/number}',
milestones_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/milestones{/number}',
notifications_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/notifications{?since,all,participating}',
labels_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/labels{/name}',
releases_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/releases{/id}',
deployments_url: 'https://api.github.com/repos/el-xxxxx/xxxxx-admin/deployments',
created_at: 1750736699,
updated_at: '2025-06-24T06:35:57Z',
pushed_at: 1750920872,
git_url: 'git://github.com/el-xxxxx/xxxxx-admin.git',
ssh_url: 'git@github.com:el-xxxxx/xxxxx-admin.git',
clone_url: 'https://github.com/el-xxxxx/xxxxx-admin.git',
svn_url: 'https://github.com/el-xxxxx/xxxxx-admin',
homepage: null,
size: 286,
stargazers_count: 0,
watchers_count: 0,
language: 'TypeScript',
has_issues: true,
has_projects: true,
has_downloads: true,
has_wiki: false,
has_pages: false,
has_discussions: false,
forks_count: 0,
mirror_url: null,
archived: false,
disabled: false,
open_issues_count: 0,
license: [Object],
allow_forking: false,
is_template: false,
web_commit_signoff_required: false,
topics: [],
visibility: 'private',
forks: 0,
open_issues: 0,
watchers: 0,
default_branch: 'main',
stargazers: 0,
master_branch: 'main',
organization: 'el-xxxxx',
custom_properties: {}
},
pusher: { name: 'PmcFizz', email: '13631686641@sina.cn' },
organization: {
login: 'el-xxxxx',
id: 199537495,
node_id: 'O_kgDOC-SzVw',
url: 'https://api.github.com/orgs/el-xxxxx',
repos_url: 'https://api.github.com/orgs/el-xxxxx/repos',
events_url: 'https://api.github.com/orgs/el-xxxxx/events',
hooks_url: 'https://api.github.com/orgs/el-xxxxx/hooks',
issues_url: 'https://api.github.com/orgs/el-xxxxx/issues',
members_url: 'https://api.github.com/orgs/el-xxxxx/members{/member}',
public_members_url: 'https://api.github.com/orgs/el-xxxxx
/public_members{/member}',
avatar_url: 'https://avatars.githubusercontent.com/u/199537495?v=4',
description: ''
},
sender: {
login: 'PmcFizz',
id: 13403284,
node_id: 'MDQ6VXNlcjEzNDAzMjg0',
avatar_url: 'https://avatars.githubusercontent.com/u/13403284?v=4',
gravatar_id: '',
url: 'xxxxx',
html_url: 'https://github.com/PmcFizz',
followers_url: 'xxxxx/followers',
following_url: 'xxxxx/following{/other_user}',
gists_url: 'xxxxx/gists{/gist_id}',
starred_url: 'xxxxx/starred{/owner}{/repo}',
subscriptions_url: 'xxxxx/subscriptions',
organizations_url: 'xxxxx/orgs',
repos_url: 'xxxxx/repos',
events_url: 'xxxxx/events{/privacy}',
received_events_url: 'xxxxx/received_events',
type: 'User',
user_view_type: 'public',
site_admin: false
},
...
最后打印出
如果shell脚本运行不了,记得加一下权限。
chmod +x ./deploy.sh