从本地开发到一句话上线:AI + GitHub Actions + 宝塔自动部署

从本地开发到一句话上线:AI + GitHub Actions + 宝塔自动部署
阿晖网站在本地改好了,接下来又是压缩文件、上传宝塔、解压覆盖、重启服务……
一次还好,每次更新都重复,确实有点烦。😵💫
所以,我给自己的项目接上了一条自动部署流程。配置好以后,只需要告诉 AI:
帮我提交代码,推送并触发部署,完成后检查网站是否正常。
AI 就能通过已授权的开发工具完成提交、推送、查看部署结果;GitHub Actions 负责构建并更新服务器。
这篇文章记录整个过程,也包括第一次部署失败时踩到的坑。🛠️
🔒 本文所有域名、目录、仓库名均使用示例,不包含真实服务器信息或密钥。示例适用于 Node.js + Vite + PM2 项目;其他项目需要调整构建命令、部署文件和健康检查地址。
这篇讨论的是带 Node.js 服务端的 Vite 应用。纯静态 Vite 或 Hexo 网站通常由 Nginx 直接提供构建产物,不需要 PM2;请先确认项目类型再套用流程。
🧩 一、整条流程怎么工作?
1 | 👤 提出修改和上线需求 |
这里几个工具各有分工:
| 工具 | 作用 |
|---|---|
| AI 开发助手 | 修改代码、运行命令、推送和排查错误 |
| GitHub 私有仓库 | 保存源码和部署配置 |
| GitHub Actions | 自动执行部署 |
| 云服务器 | 持续运行应用 |
| 宝塔 / Nginx | 管理域名、HTTPS 和反向代理 |
| PM2 | 管理 Node.js 进程 |
第一次需要把权限和流程配好,之后更新就省事多了。✨
🔍 二、先查清楚网站现在怎么运行
我的网站已经运行了一段时间,因此第一步不是重装,而是确认现有配置。
📍 在服务器终端执行:
1 | node --version |
然后查看目标应用:
1 | pm2 describe my-web-app |
重点记录:
exec cwd:项目实际目录。script path、script args:启动方式。node.js version:实际使用的 Node.js 版本。status:当前进程状态。
例如,这类项目可能通过以下命令启动:
1 | npm run server |
再检查网站目录:
1 | cd /www/wwwroot/example.com |
如果出现:
1 | fatal: not a git repository |
说明服务器上的文件不是 Git 工作目录。之前通过压缩包上传的网站很常见,不代表网站有问题。
这次就采用 Actions 构建后上传文件 的方式,不要求服务器执行 git pull。
💡 还有一个容易误会的地方:网站出现在宝塔的“PHP 项目”列表里,不足以说明应用使用 PHP。要结合启动命令和反向代理配置判断实际运行方式。
🛟 三、先备份,再自动化
正式更新前,先给现有网站做一个备份。
📍 在服务器终端执行,先修改第一行目录:
1 | APP_DIR="/www/wwwroot/example.com" |
这个备份保留构建文件和配置,排除了体积较大的依赖目录。
⚠️ 备份可能包含服务器 .env,因此只留在服务器的受限目录里,不要提交到 GitHub。
同时明确更新边界:
.env继续留在服务器。- 数据库、上传文件和运行时数据不属于发布包。
- 只重启当前网站,不重启同服务器上的其他服务。
🔐 四、配置部署密钥和 Secrets
GitHub Actions 需要通过 SSH 连接服务器,因此要准备一把专用部署密钥。
📍 在可信的 Linux / Bash 终端执行:
1 | ssh-keygen \ |
示例工作流没有配置私钥口令,生成时口令留空。生成的两个文件分别是:
1 | github_actions_deploy 私钥:配置到 GitHub Secrets |
用自己的服务器地址替换后,安装公钥:
1 | ssh-copy-id \ |
这里的 DEPLOY_USER 必须有权限更新网站目录,并管理对应 PM2 进程。已有应用属于哪个用户,就需要考虑该用户的 PM2 环境;随意换用户可能看到一个空的进程列表。
接着,在 GitHub 仓库打开:
Settings → Secrets and variables → Actions → New repository secret
添加:
| Secret 名称 | 内容 |
|---|---|
SERVER_HOST |
服务器地址 |
SERVER_PORT |
SSH 端口 |
SERVER_USER |
部署用户 |
SERVER_SSH_KEY |
专用部署私钥的完整内容 |
SERVER_FINGERPRINT |
经可信渠道确认的 SSH 主机 SHA256 指纹 |
在服务器控制台执行 ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub -E sha256,将确认过的 SHA256:... 值保存为 SERVER_FINGERPRINT。这是服务器主机指纹,与部署公钥不同;两处 SSH Action 都应填写。配置方法见 SSH Action 官方说明 和 SCP Action 参数。
私钥只填写到 Secrets,不放在代码、聊天记录或截图里。🔒
📦 五、确认仓库不会带上个人数据
提交前,检查忽略规则,至少排除:
1 | node_modules/ |
这是基础示例;项目如果有上传目录、数据库或下载目录,也应分别排除。
📍 在本地项目的 Bash 终端执行:
1 | git status --short |
注意:.gitignore 不会自动取消已经跟踪的文件。如果真实密钥曾经进入提交,删除当前文件也不能清除历史泄露,需要更换密钥并处理历史。
我的另一个实际插曲是:部署完才发现仓库是公开的。😅
如果不准备开源,建议一开始就创建私有仓库。网站公开访问与源码是否公开,是两回事。
⚙️ 六、添加 GitHub Actions 工作流
在项目里创建:
1 | .github/workflows/deploy.yml |
下面是整理后的示例。使用前调整:
- 触发分支
main。 - 服务器目录。
- PM2 应用名称。
- 上传文件列表。
- 健康接口和端口。
项目必须存在对应的 build 脚本,并提交一致的 package-lock.json。示例保留原稿使用的 Action 版本标签,实际接入时应检查维护状态与运行器兼容性。示例沿用服务器现有运行环境,Actions 的 Node.js 22 不会自动升级服务器上的 Node.js。
1 | name: Deploy website |
服务端改用 npm ci,让依赖严格遵循锁文件;锁文件不一致时会失败,而不会现场修改锁文件。它会移除旧 node_modules,因此示例先停止目标应用再安装。此处保留完整依赖,运行时依赖整理清楚后才考虑 --omit=dev。参见 npm ci 官方文档。
SSH 非交互会话也要能找到 node、npm 和 pm2。宝塔或 nvm 管理的 Node.js 可能需要显式设置 PATH,并使用现有进程所属用户的 PM2 环境。
📌 这是一套基础覆盖部署方案,停机时间包含依赖安装和重启,也不会自动删除服务器上已经废弃的旧文件。它还没有实现原子切换或自动回滚。
依赖安装或健康检查失败时,工作流只会报错,服务可能保持停止或异常状态。首次采用前应演练恢复:保留失败版本,恢复备份里的程序与锁文件,重新安装依赖,再启动同一个 PM2 应用并检查接口。备份排除了依赖,不是直接解压就能完成回滚;数据库还需单独备份。
cp -a 会合并目录,已废弃的旧文件仍可能存在;并且覆盖期间现有服务可能读到新旧混合内容。需要可靠切换时,应使用独立发布目录,而不是把这个示例当成零停机方案。临时发布目录和备份也需要按明确的保留策略定期清理。
如果服务端还依赖其他配置、静态资源或本地模块,需要补充上传列表,不能只照搬文件名。
🚀 七、提交并触发部署
📍 在本地项目 Bash 终端执行:
1 | git status --short |
确认内容后,提交部署文件:
1 | git add .github/workflows/deploy.yml |
如果实际使用其他分支,工作流和推送命令要一起调整。
随后打开 GitHub 仓库的 Actions,查看运行过程:
1 | Checkout ✅ |
也可以让已经授权的 GitHub CLI 查看:
1 | gh run list --limit 5 |
查看某次运行:
1 | gh run view RUN_ID |
查看失败日志:
1 | gh run view RUN_ID --log-failed |
💥 八、真实踩坑:PM2 online,为什么部署还失败?
第一次运行时,构建、上传和 PM2 重启都成功了,但健康检查失败:
1 | curl: (7) Failed to connect to 127.0.0.1 port 5175 |
原来的脚本是:
1 | pm2 restart my-web-app --update-env |
问题在于:PM2 已经拉起进程,不代表应用已经开始监听端口。 ⏳
因此把固定等待改为多次重试。后一次部署中,第一次连接仍然失败,稍后就返回了:
1 | {"ok":true} |
这次部署最终通过。✅
不过,连接失败也可能来自启动异常、端口配置错误等原因。如果持续失败,应查看服务器日志,而不是无限延长等待:
1 | pm2 describe my-web-app |
日志应在可信环境里查看,分享前检查是否包含敏感信息。
🩺 九、怎样才算真的上线成功?
不能只看“代码推送成功”。还需要确认:
- GitHub Actions 完成且成功。
- 健康接口返回预期内容。
- 网站首页可以打开。
- 实际页面出现本次更新。
📍 在能访问网站的 Bash 终端执行:
1 | curl --fail --show-error \ |
预期:
1 | {"ok":true} |
工作流中的 curl --fail 只检查 HTTP 错误,尚未断言 JSON 中的 ok。如果接口可能返回 HTTP 200 但 ok: false,应添加 JSON 断言。只有再核对本次页面变化或提交版本,才能确认没有命中旧服务。
检查首页:
1 | curl --silent --show-error \ |
预期:
1 | HTTP 200 |
健康接口通过代表基础服务可用,股票接口、图片分析等业务功能仍需要单独检查。🔎
🤖 十、以后怎么让 AI 帮我上线?
完成首次配置,并让 AI 开发工具获得本地项目和 GitHub 的授权后,可以使用这段提示词:
1 | 请将当前项目更新部署到服务器。 |
这句话背后真正发挥作用的是:可执行的部署脚本、预先配置的权限,以及明确的验证标准。 🧠
AI 可以把这些步骤串起来,还能在失败时继续查看日志、修改流程、重新验证。
🌱 十一、后续还可以继续完善什么?
这次已经打通了从本地推送到服务器更新的流程。下一步可以继续增加:
- 🧪 部署前自动运行项目测试。
- ↩️ 健康检查失败后自动回滚。
- 📦 每次发布使用独立目录,通过软链接切换版本。
- 🔐 进一步限制专用部署账户权限,并将第三方 Actions 固定到审查过的提交 SHA。
- 🔔 推送部署成功或失败通知。
- 🏷️ 在健康接口返回版本号,确认线上确实是最新提交。
现在,再修改网站时,不必每次打开宝塔传压缩包了。告诉 AI 更新并部署,随后查看真实运行结果,就能完成一次可追踪的上线。🚀











