部署:update.sh 改由 deploy/update-bg.sh 在服务器上后台跑(setsid),SSH 断了它照样跑完。2026-11-03 开发机上 FlClash 切换配置重建了 TUN 网卡,SSH 正好断在 docker compose up 中间:旧容器删了、新容器只创建没启动,网站停了 40 分钟。输出写到 /var/tmp/iot_sim-update.log,--follow <提交号> 接着看输出、退出码和 update.sh 一样,这个提交的部署还没开始时返回 75;上一次还在跑就等它结束。Gitea 的"起服务"ssh 返回 255 就 10 秒后重连 --follow,75 就重新开始,最多 6 次。GitHub 备用部署也改用 update-bg.sh。部署说明同步
Deploy / deploy (push) Successful in 1m28s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-11-03 13:32:21 +08:00
1 parent 2aa267839f
commit f242308a3b
4 files changed
+85 -4

No files matched your search

+2 -1
View File
@@ -70,6 +70,7 @@
`.gitea/workflows/deploy.yml`:push 到 origin(git.xuyue.cc)的 main 时,**开发机上的 gitea-runner** 编 C# 运行时、网页和 `server.js`,跑服务端测试(含沙箱)、网页端测试和类型检查(有一项不过就不部署),把产物 rsync 到服务器的 `~/IOT_SIM/prebuilt/`,再在服务器上执行 `deploy/update.sh <提交号> --prebuilt`:拉代码、用现成产物构建镜像(只剩几条 COPY)、重启,等到"编译服务已就绪"才算成功。push 后一两分钟上线,进度在 git.xuyue.cc/xuyue/IOT_SIM/actions。
- `update.sh` 由 `deploy/update-bg.sh` 在服务器上**后台**跑,SSH 断了(开发机切代理、换网络)它照样跑完,runner 重连后接着看输出、拿结果。以前它跑在 SSH 会话里,断线时正好在 `docker compose up` 中间,旧容器删了、新容器没启动,网站就停了。服务器上的部署输出在 `/var/tmp/iot_sim-update.log`。
- **重启会结束正在运行的学生程序**,上课时间尽量不要推送 main。
- **push 时开发机得开着。** 没开的话任务在 Gitea 里排队,开机后 runner 连上就接着跑;排队超过 24 小时会被作废,要在 Actions 页面重跑。
- 在 Gitea 的 Actions 页面点 Run workflow 可以手动重新部署(比如改了服务器上的 `.env` 之后)。
@@ -93,7 +94,7 @@ host 模式直接用开发机自己的东西,所以**不需要配任何 secret
### 备用:GitHub 手动触发
`.github/workflows/ci.yml` 只在 PR 时跑测试,push 不触发(不然每次推送两边各部署一遍)。开发机不在、又急着上线时,在 github.com 的 Actions → CI/CD → Run workflow:测试通过后 SSH 到服务器执行 `deploy/update.sh <提交号>`,**服务器自己编**。这个提交要已经推到 origin(服务器从 origin 拉代码,没到时脚本会等最多约 100 秒)。
`.github/workflows/ci.yml` 只在 PR 时跑测试,push 不触发(不然每次推送两边各部署一遍)。开发机不在、又急着上线时,在 github.com 的 Actions → CI/CD → Run workflow:测试通过后 SSH 到服务器执行 `deploy/update-bg.sh <提交号>`(后台跑 `update.sh`),**服务器自己编**。这个提交要已经推到 origin(服务器从 origin 拉代码,没到时脚本会等最多约 100 秒)。
一次性配置(在本机执行,需要已登录的 `gh`):
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
# 在服务器上执行:后台跑 deploy/update.sh,前台跟着输出,退出码和 update.sh 一样。
# 用法:deploy/update-bg.sh <提交号> [--prebuilt] 开始部署这个提交并跟着看
# deploy/update-bg.sh --follow <提交号> 接着看这个提交的部署(SSH 断线重连后用);
# 这个提交的部署还没开始过时退出码 75
#
# 为什么要后台跑:以前 update.sh 直接跑在 SSH 会话里,SSH 一断(开发机网络切换、代理重启)就跟着被杀。
# 2026-11-03 断在 docker compose up 中间:旧容器删了、新容器只创建没启动,网站停了 40 分钟。
# 现在 update.sh 在新会话里跑(setsid),SSH 断了它照样跑完,开发机重连后用 --follow 拿结果。
set -euo pipefail
# 包在函数里:update.sh 会 git reset,运行中替换本文件,bash 先读完函数再执行,不受影响
main() {
cd "$(dirname "$0")/.."
local state=/var/tmp/iot_sim-update
# state.sha:这次部署的提交 state.pid:后台进程 state.log:输出 state.exit:update.sh 的退出码(跑完才有)
if [[ ${1:-} == --follow ]]; then
local sha=${2:?用法:deploy/update-bg.sh --follow <提交号>}
if [[ $(cat $state.sha 2>/dev/null || true) != "$sha" ]]; then
echo "服务器上没有 $sha 的部署记录(还没开始)"
exit 75
fi
follow
fi
local sha=${1:?用法:deploy/update-bg.sh <提交号> [--prebuilt]}
# 上一次还在跑(比如它的 SSH 断了、又推了新的)就等它跑完,两次 compose up 不能同时跑
local old
old=$(cat $state.pid 2>/dev/null || true)
if [[ -n $old ]] && kill -0 "$old" 2>/dev/null; then
echo "== 上一次部署($(cat $state.sha 2>/dev/null))还在跑,等它结束"
while kill -0 "$old" 2>/dev/null; do sleep 2; done
fi
rm -f $state.pid $state.exit
echo "$sha" >$state.sha
# 后台进程自己写 pid($$),保证记下的就是会写退出码的那个进程
setsid bash -c 'echo $$ >"$0.pid"; deploy/update.sh "$@"; echo $? >"$0.exit"' \
"$state" "$@" >$state.log 2>&1 </dev/null &
for _ in $(seq 50); do [[ -s $state.pid ]] && break; sleep 0.1; done
follow
}
follow() {
local pid code
pid=$(cat $state.pid 2>/dev/null || true)
if [[ -n $pid ]]; then
# update 进程结束后 tail 自己退出
tail -n +1 -f --pid="$pid" $state.log
else
cat $state.log 2>/dev/null || true
fi
code=$(cat $state.exit 2>/dev/null || true)
if [[ -z $code ]]; then
echo "== 部署进程意外结束,没有留下退出码,看看 docker compose ps"
exit 1
fi
exit "$code"
}
main "$@"