---
date: 2026-05-10
title: Gitea 1.25→1.26 升级 + act_runner 跨版本兼容 + OOM 全机宕机一连串教训
tags: [gitea, ops, ci-cd, oom, runner, upgrade]
related_pr: 277
related_issue: 273
---

# Gitea 1.26.1 升级踩坑全记录

为了启用 Actions `concurrency` syntax（PR #272 deploy 并发问题需要），把 Gitea 升级 1.25.4 → 1.26.1。整个 ops session 暴露 **5 个互锁问题**，几乎让我连续误诊一个下午。

---

## 教训 1：升级前必须确认服务器**资源 capacity**

### 现象
升级后约 30 分钟，Gitea 机（`43.130.59.228`）整体宕机：
- ssh banner timeout（sshd 死）
- HTTP 504（nginx + Gitea 死）
- ping 通（OS 还活）
- → OOM Killer 把所有 service 杀光，systemd 无法重启（资源未释放）

### 根因
`free -h` 显示该机**只有 1.9 GB 内存**。Gitea 1.26.1 比 1.25 内存占用高 + act_runner v1.0.2 升级后接 3 个并发 task + nginx + sshd → OOM 必现。

### 教训
**升级前 mandatory check**：
```bash
free -h | head -2  # 至少 4 GB 才升 minor / major
df -h              # 至少 5 GB free
uptime             # load < 2
```
单服务部署 + 1.9 GB 这种 spec 升级**任何**软件都是高风险——必须先扩容再升级。

---

## 教训 2：act_runner / Gitea 跨版本协议非显式不兼容

### 现象
升级 Gitea 1.26.1 后，所有 deploy-test workflow 卡：
- API 显示 `status=in_progress`
- Step "Deploy to Test server" 计时跑（5、10、15 分钟）
- **CI log 3.4 KB 完全空**（::endgroup:: 后 0 字节远端 stdout）
- 远端 170 上 0 个 ssh in 连接 → ssh 命令实际**没真发出去**

### 误诊路径
1. ❌ 第一推断：act_runner v0.6.0 / v0.3.0 跟 1.26.1 协议不兼容 → 升 workspace-runner v1.0.2 →OOM 全机宕机
2. ❌ 第二推断：deploy.sh redirect stdout 的 issue #273 第 8 项 → 修了但同样 invisible
3. ✅ **真因**：dedicated-runner-1 跑的 act_runner v0.6.0 是 pre-Gitea-1.26 协议版本，**部分 task 接收成功部分 silent hang**——v0.6.0 跟 1.26 兼容性是间歇性的，不是 100% break

### 教训
- act_runner 跟 Gitea **大版本必须配套**（v1.0.x 配 Gitea 1.26+，v0.6.x 配 Gitea 1.25-）
- 上游时间线：Gitea 1.26.0 发布 (2026-04-18) → act_runner v1.0.0 发布 (2026-05-05, 含 rename act_runner→gitea-runner) → 这是 protocol 适配的明确信号
- **升级 Gitea 必须同步升级 act_runner**——不能只升一边

---

## 教训 3：act_runner 重命名为 gitea-runner（v1.0.0 breaking）

2026-05-05 重命名：
- Repo: `gitea/act_runner` → `gitea/runner`
- Binary: `act_runner` → `gitea-runner`
- Docker: `gitea/act_runner` → `gitea/runner`

但 **config 文件 / env vars / CLI flags / registration 格式不变**——所以 systemd unit 不动 + binary 改名同名安装即可：

```bash
sudo install -m 755 gitea-runner-1.0.2-linux-amd64 /usr/local/bin/act_runner
# systemd unit 仍 ExecStart=/usr/local/bin/act_runner daemon ...
```

GitHub `gitea/act_runner` 已 redirect → 但 release API 返回 404；正确路径 `gitea.com/api/v1/repos/gitea/runner/releases`。

---

## 教训 4：CI log 黑洞的真正根因 = ssh 真没发

之前 issue #273 第 8 项判断 deploy job CI log 黑洞是 deploy.sh redirect stdout。**实际有两层叠加**：

| 层 | 现象 | 修复 |
|---|---|---|
| 表层 | deploy.sh `exec > >(tee >(sed ... >> file))` 嵌套 process substitution 在 ssh non-tty 下 buffer stdout | PR #277 改 deploy.sh，CI 模式跳过 redirect |
| **真根因** | act_runner 老协议 + Gitea 1.26 → ssh 步骤 silent hang，从未真正发出去 | 升级 act_runner v1.0.2 |

如果只修表层不升 runner，CI log 仍空（ssh 没发就没 stdout 可流）。

---

## 教训 5：bash heredoc 多 `sudo` 命令 ssh 输出截断问题

ssh 跑大 heredoc（多个 `sudo` 命令分块）时输出会截断：

```bash
ssh host '
sudo cmd1 ...
sudo cmd2 ...   # 之后输出可能被截
sudo cmd3 ...
'
```

绕行：把关键 `install / restart` 等 mutating step 拆**多个独立 ssh call**，或者在 bash 内 `set -x` + 显式 `echo "STAGE N done"` mark 进度，方便事后复盘。

---

## 升级 Runbook (验证可行)

### Gitea 1.25 → 1.26 (sqlite + binary 安装)

```bash
# 备份（必须）
sudo systemctl stop gitea act-runner
BAK=/root/gitea-backup-pre-1.26.1-$(date +%Y%m%d-%H%M%S)
sudo mkdir -p "$BAK"
sudo cp -a /usr/local/bin/gitea "$BAK/gitea-OLD.bin"
sudo cp -a /etc/gitea/app.ini "$BAK/"
sudo cp -a /var/lib/gitea/data/gitea.db "$BAK/"
sudo tar czf "$BAK/repos.tar.gz" -C /var/lib/gitea/data/git repositories

# 下载 + 校验
cd /tmp
wget https://dl.gitea.com/gitea/<VER>/gitea-<VER>-linux-amd64
wget https://dl.gitea.com/gitea/<VER>/gitea-<VER>-linux-amd64.sha256
sha256sum -c gitea-<VER>-linux-amd64.sha256

# 替换 + 启动 + 验证
sudo install -m 755 gitea-<VER>-linux-amd64 /usr/local/bin/gitea
sudo systemctl start gitea
curl -s http://localhost:3000/api/v1/version  # 应返回新版
```

### act_runner v0.x → v1.x

```bash
# 备份
sudo systemctl stop act-runner
sudo cp /usr/local/bin/act_runner /root/act_runner-bak/

# 下载（注意 repo 改名 gitea/runner）
wget https://gitea.com/gitea/runner/releases/download/v<VER>/gitea-runner-<VER>-linux-amd64
sudo install -m 755 gitea-runner-<VER>-linux-amd64 /usr/local/bin/act_runner

# 启动 + 验证 declare
sudo systemctl start act-runner
sudo journalctl -u act-runner -n 10 --no-pager
# 应看到: "runner: <name>, with version: v<VER>, ..., declare successfully"
```

### **同台机 vs 多台机的关键决策**

- Gitea + act_runner **不要同机**——任一升级或负载就 OOM
- act_runner 给 `runner.capacity` 限制并发（默认 1，在 `/etc/act_runner/config.yaml` 的 `runner.capacity:` 下设）
- Gitea 机推荐 ≥ 4 GB；act_runner 机推荐 ≥ 8 GB（next build 吃内存）

---

## 教训 6（最深 root cause）：SSH `sk-*` host key 类型不被对端支持

### 现象
升级 Gitea 1.26.1 + act_runner v1.0.2 后，**仍**所有 deploy job CI log 黑洞。一开始误以为是协议兼容问题。

### 真根因（rabbit hole 第三层）
runner 机的 ssh client 在 negotiate 时**优先 offer FIDO 安全密钥（`sk-ecdsa-sha2-nistp256@openssh.com` / `sk-ssh-ed25519@openssh.com`）host key 类型**。但 deploy 目标机 sshd 不支持 sk-* 类型（FIDO 是 hardware token 特有），negotiate 直接失败，`Connection closed [preauth]`。

```
sshd[170]: Unable to negotiate with 43.166.205.48:
  no matching host key type found.
  Their offer: sk-ecdsa-sha2-nistp256@openssh.com [preauth]
```

ssh 客户端进程**仍 alive 在 `CLOSE_WAIT` 状态**（对端 FIN 但本地没读完），所以从 act_runner 角度看 step 在跑（ssh 进程没退）。

### 诊断决定性证据（**这条最重要**）
1. CI log 卡 `::endgroup::` 后 0 字节 → ssh 像没发出
2. 但 runner 机上 `ps -ef` 看到 ssh 进程 alive  → ssh 发出了
3. `lsof -p <ssh_pid> -i` 显示 socket `CLOSE_WAIT` → 对端已关连接
4. **目标机 `/var/log/auth.log` grep 远端 IP** → 看到 sshd 报 `no matching host key type found, Their offer: sk-*`
5. **从开发机用同样的 private key 跑同样的 ssh + heredoc** → 完美工作 → 说明问题在 runner 机的 ssh client 配置

### 修复（两层）

**临时（runner 机本地）**：
```bash
# /home/ubuntu/.ssh/config
Host <target-ip>
    HostKeyAlgorithms ssh-ed25519,ecdsa-sha2-nistp256,rsa-sha2-512,rsa-sha2-256,ssh-rsa
    PubkeyAcceptedAlgorithms ssh-ed25519,ecdsa-sha2-nistp256,rsa-sha2-512,rsa-sha2-256,ssh-rsa
```

**长期（yml 层防御性，跨 runner 可移植）**：
```yaml
ssh -i ~/.ssh/deploy_key \
  -o StrictHostKeyChecking=accept-new \
  -o HostKeyAlgorithms=ssh-ed25519,ecdsa-sha2-nistp256,rsa-sha2-512,rsa-sha2-256,ssh-rsa \
  ubuntu@"$DEPLOY_HOST" bash -s
```

### 教训
1. **CI log 看不到 = ssh 真没出 / 远端 stdout 没流回 / 远端命令没执行**——三种情况都可能，必须远端机的 sshd auth.log 一起看才能区分
2. **SSH negotiate 失败一般在 [preauth] 阶段**，error 体现在 sshd 端不在 client 端——客户端只看到"connection closed"，不知道为什么
3. **`ssh-keyscan -H <host>` 默认拿所有类型 host key**（包括 sk-*），ssh client 拿到后偏好顺序由 client 全局 config 决定。如果不显式指定 `HostKeyAlgorithms`，跨机器 / 跨版本行为可能漂移
4. **诊断 ssh hang 的 5 步 SOP**（顺序）：
   1. CI log size
   2. runner 机 `ps -ef --forest | grep ssh`
   3. `lsof -p <ssh_pid> -i` 看 TCP state
   4. **远端机** `/var/log/auth.log` grep runner IP
   5. 用同样 private key 从别的机器手动复现

---

## 关联

- Issue #273 SOP 跟踪 umbrella
- PR #272 (dev → test cutover) 的并发问题驱动了升级
- PR #277 deploy.sh CI mode fix
- PR #280 concurrency 配置
- PR #283（待开）yml 加 HostKeyAlgorithms 防御性配置
