Upgrade the gitea recipe app image gitea/gitea1.27.3-rootless → 28.0.0-rootless
(upstream v28.0.0, released 2026-09-29 — a major-version jump from the 1.x line).
What changed
service
image
old → new
app
gitea/gitea
1.27.3-rootless → 28.0.0-rootless
db (postgres overlay 15.19 / mariadb 10.11.19) held — db majors are not in-place-safe
(logical pg_dump/psql backup only; no pg_upgrade), per standing precedent.
No app.ini.tmpl/compose overlay changes required — see Operator Action Required below.
Operator Action Required (BREAKING, from the v28.0.0 release notes)
BREAKING — git egress rework (fix(git)!: use internal proxy for all git operations, #39426): ALLOWED_DOMAINS / BLOCKED_DOMAINS / ALLOW_LOCALNETWORKS are deprecated (migrated to [security] ALLOWED_HOST_LIST / BLOCKED_HOST_LIST); the external deny preset is removed — use EGRESS_MODE = strict for an
exclusive allow-list. In the default lax mode ALLOWED_HOST_LISTno longer restricts public
hosts (a startup warning flags this). All Git network operations now route through gitea's
internal proxy (SSRF hardening). Recipe impact: none by default — this recipe sets none of those keys. Operators who DO
restrict git egress must switch to EGRESS_MODE = strict + explicit CIDRs to keep a deny-by-default
policy.
CVEs fixed by this upgrade: ≥1 — CVE-2026-73802 (critical, GHSA-x4q3-gcj3-m6cf, fixed by 28.0.0).
1 further advisory undetermined (CVE-2026-62925, medium) — vendor published no fix version.
⚠ GitHub advisories API was rate-limited from the cc-ci host during the scan, so the count is
incomplete (floor, not ceiling) — see the per-recipe log.
Union with the release-note read: 5 SECURITY fixes with no CVE id + the 2 above.
Version bump (operator's final step — the PR does NOT touch the label)
Major version jump + upstream BREAKING changes ⇒ release as a major:
abra recipe release gitea -x
Verification
Direct --chaos deploy on cc-ci (dev-gitea.ci.commoninternet.net, mariadb overlay): converged
1/1, app log Gitea version: 28.0.0, /api/healthz 200, admin user + repo created via API
(HTTP 201/200), /api/v1/version → {"version":"28.0.0"}. Dev deploy torn down (no leak).
!testme on this PR (existing tests, full suite).
NOT merged — for operator review.
Tested green on the cc-ci recipe CI server (full suite, cold, against this PR head). NOT merged — for operator review.
Upgrade the `gitea` recipe app image `gitea/gitea` **1.27.3-rootless → 28.0.0-rootless**
(upstream **v28.0.0**, released 2026-09-29 — a major-version jump from the 1.x line).
## What changed
| service | image | old → new |
|---------|-------|-----------|
| app | gitea/gitea | `1.27.3-rootless` → `28.0.0-rootless` |
- `db` (postgres overlay 15.19 / mariadb 10.11.19) **held** — db majors are not in-place-safe
(logical pg_dump/psql backup only; no pg_upgrade), per standing precedent.
- No `app.ini.tmpl`/compose overlay changes required — see Operator Action Required below.
**Upstream release notes:** app gitea/gitea 1.27.3→28.0.0: https://github.com/go-gitea/gitea/releases/tag/v28.0.0
## Operator Action Required (BREAKING, from the v28.0.0 release notes)
- **BREAKING — git egress rework** (`fix(git)!: use internal proxy for all git operations`,
[#39426](https://github.com/go-gitea/gitea/pull/39426)): `ALLOWED_DOMAINS` / `BLOCKED_DOMAINS` /
`ALLOW_LOCALNETWORKS` are deprecated (migrated to `[security] ALLOWED_HOST_LIST` /
`BLOCKED_HOST_LIST`); the `external` deny preset is removed — use `EGRESS_MODE = strict` for an
exclusive allow-list. In the default `lax` mode `ALLOWED_HOST_LIST` **no longer restricts public
hosts** (a startup warning flags this). All Git network operations now route through gitea's
internal proxy (SSRF hardening).
*Recipe impact: **none by default** — this recipe sets none of those keys. Operators who DO
restrict git egress must switch to `EGRESS_MODE = strict` + explicit CIDRs to keep a deny-by-default
policy.*
- **BREAKING — actions retention** (`feat(actions)!: add RUN_RETENTION_DAYS`, [#38855](https://github.com/go-gitea/gitea/pull/38855)):
new `[actions] RUN_RETENTION_DAYS` (0 = keep forever). No recipe change needed (defaults apply).
- **SECURITY** (named in the release notes; CVE ids published separately):
- reject invalid/duplicate Git objects on push ([#39472](https://github.com/go-gitea/gitea/pull/39472));
- identify presented SSH public keys by fingerprint ([#39423](https://github.com/go-gitea/gitea/pull/39423));
- keep cancelled/unapproved fork PR runs behind the approval gate ([#39399](https://github.com/go-gitea/gitea/pull/39399));
- bump `golang.org/x/crypto` SSH to address a DoS ([#39219](https://github.com/go-gitea/gitea/pull/39219));
- enforce repository-scoped authorization for team access / deletion / package unlinking ([#39063](https://github.com/go-gitea/gitea/pull/39063)).
## Advisory scan (deterministic pre-step; gitea 1.27.3 → 28.0.0)
- **CVEs fixed by this upgrade: ≥1** — CVE-2026-73802 (critical, GHSA-x4q3-gcj3-m6cf, fixed by 28.0.0).
- 1 further advisory undetermined (CVE-2026-62925, medium) — vendor published no fix version.
- ⚠ GitHub advisories API was **rate-limited from the cc-ci host** during the scan, so the count is
incomplete (floor, not ceiling) — see the per-recipe log.
- Union with the release-note read: 5 SECURITY fixes with no CVE id + the 2 above.
## Version bump (operator's final step — the PR does NOT touch the label)
Major version jump + upstream BREAKING changes ⇒ release as a **major**:
abra recipe release gitea -x
## Verification
- Direct `--chaos` deploy on cc-ci (`dev-gitea.ci.commoninternet.net`, mariadb overlay): converged
1/1, app log `Gitea version: 28.0.0`, `/api/healthz` 200, admin user + repo created via API
(HTTP 201/200), `/api/v1/version` → `{"version":"28.0.0"}`. Dev deploy torn down (no leak).
- `!testme` on this PR (existing tests, full suite).
- NOT merged — for operator review.
Tested green on the cc-ci recipe CI server (full suite, cold, against this PR head). NOT merged — for operator review.
cc @trav @notplants
Three of the four custom tests pass (test_admin_api_user_org_token_lifecycle — admin API CRUD, test_git_push — full SCM push round-trip, test_gitea_root_returns_200). The one failure is test_lfs_roundtrip, and it fails in the test's own restart polling loop, not in the LFS
round-trip or the recipe.
What the failing test asserts vs. what actually failed
# after `docker service update --force <stack>_app` (restart):deadline=time.time()+120whiletime.time()<deadline:status2,_=_api(live_app,"/version",user=user,password=password)# urllib, timeout=20ifstatus2==200:breaktime.sleep(5)assertstatus2==200,"gitea did not come back up after restart"
The traceback is a TimeoutError: The read operation timed out on that _api() call
(urllib.request.urlopen(..., timeout=20)) — i.e. the single /api/v1/version request stalled
~20 s and raised, aborting the whole test. Crucially:
the test is running _api()from inside the cc-ci runner/harness process on the cc-ci host
(it shells out to docker service update directly), so this poll is a local HTTP call to the
app's public /api/v1/version — it is not the app's own healthcheck;
the app was healthy and serving — all four other tests in the same custom stage pass, the
install/upgrade stages already asserted the same app healthz/version endpoint, and the App
registry in this very run reports install/upgrade/backup/restore = pass;
so the restart did not break the app; the test's fixed 20 s socket timeout simply expired on one
poll during the app's post-restart window.
Why this reads as genuinely stale
This exact test is green on the current 1-safe-line base: the 2026-08-31 run (app 1.27.2 →
1.27.3-rootless) reports !testme GREEN on the first run, custom tier included.
gitea 28.0.0 is a major version jump that substantially refactored git/model layers and boot
wiring; a cold-restart boot + first-request window is exactly the kind of timing the fixed 20 s
timeout (and the comment's own # Wait for gitea to come back up TODO) is brittle to.
Per tests/STYLE.md §5, "derive waits from the recipe's declared readiness, not a guess" — this
loop hard-codes timeout=20 per attempt instead of using the recipe's declared HTTP_TIMEOUT
(600 s) / READY_PROBE. That is the test's bug, not the recipe's.
Nothing in the recipe needed to change for it: the dire dev deploy of this same PR head
converged, and admin + repo API + /api/v1/version all returned 200 on 28.0.0.
What I did NOT do (DEFAULT mode)
I did not modify the test or the harness. !testme cannot go fully green on this PR without a
test change, and test changes are gated behind --with-tests. So this PR is recorded as SUCCESS-PENDING-TESTS in DEFAULT mode.
Operator options
Re-run /recipe-upgrade gitea --with-tests — that will open a verified cc-ci test PR to
replace the brittle fixed-timeout restart poll with the recipe's declared readiness
(HTTP_TIMEOUT / READY_PROBE, per tests/STYLE.md §5). This PR's !testme then goes green
once the test PR is merged.
Treat the run as a flake and simply re-post !testme on this PR (within the budget) — if the
restart window is kind, the same test passes; but the fixed-timeout brittleness remains.
The recipe upgrade itself (single line: gitea/gitea:1.27.3-rootless → 28.0.0-rootless) is
verified live on the cc-ci dev swarm and by the install/upgrade/backup/restore/lint tiers. NOT merged.
## `!testme` is RED — but only on a stale LFS custom test (recipe upgrade itself verified GREEN on every lifecycle tier)
The upgrade itself looks correct. The cc-ci run (`#31`, on `91e42c8a`) is:
```
install pass
upgrade pass ← in-place 1.27.3 → 28.0.0 redeploy, ci-marker repo survived
backup pass
restore pass
lint pass
custom FAIL ← tests/gitea/custom/test_lfs_roundtrip.py (only)
```
Three of the four custom tests pass (`test_admin_api_user_org_token_lifecycle` — admin API CRUD,
`test_git_push` — full SCM push round-trip, `test_gitea_root_returns_200`). The one failure is
`test_lfs_roundtrip`, and it fails **in the test's own restart polling loop, not in the LFS
round-trip or the recipe**.
### What the failing test asserts vs. what actually failed
```python
# after `docker service update --force <stack>_app` (restart):
deadline = time.time() + 120
while time.time() < deadline:
status2, _ = _api(live_app, "/version", user=user, password=password) # urllib, timeout=20
if status2 == 200:
break
time.sleep(5)
assert status2 == 200, "gitea did not come back up after restart"
```
The traceback is a **`TimeoutError: The read operation timed out`** on that `_api()` call
(`urllib.request.urlopen(..., timeout=20)`) — i.e. the single `/api/v1/version` request stalled
~20 s and raised, aborting the whole test. Crucially:
- the test is running `_api()` **from inside the cc-ci runner/harness process on the cc-ci host**
(it shells out to `docker service update` directly), so this poll is a *local* HTTP call to the
app's public `/api/v1/version` — it is **not** the app's own healthcheck;
- the app **was healthy and serving** — all four other tests in the same `custom` stage pass, the
install/upgrade stages already asserted the same app healthz/version endpoint, and the App
registry in this very run reports `install/upgrade/backup/restore = pass`;
- so the restart did not break the app; the test's fixed 20 s socket timeout simply expired on one
poll during the app's post-restart window.
### Why this reads as genuinely stale
- This exact test is green on the current 1-safe-line base: the **2026-08-31** run (app 1.27.2 →
1.27.3-rootless) reports `!testme GREEN on the first run`, custom tier included.
- gitea 28.0.0 is a major version jump that substantially refactored git/model layers and boot
wiring; a cold-restart boot + first-request window is exactly the kind of timing the fixed 20 s
timeout (and the comment's own `# Wait for gitea to come back up` TODO) is brittle to.
- Per `tests/STYLE.md` §5, "derive waits from the recipe's declared readiness, not a guess" — this
loop hard-codes `timeout=20` per attempt instead of using the recipe's declared `HTTP_TIMEOUT`
(600 s) / `READY_PROBE`. That is the test's bug, not the recipe's.
- Nothing in the recipe needed to change for it: the dire dev deploy of this same PR head
converged, and admin + repo API + `/api/v1/version` all returned 200 on 28.0.0.
### What I did NOT do (DEFAULT mode)
I did **not** modify the test or the harness. `!testme` cannot go fully green on this PR without a
test change, and test changes are gated behind `--with-tests`. So this PR is recorded as
**`SUCCESS-PENDING-TESTS`** in DEFAULT mode.
### Operator options
1. **Re-run `/recipe-upgrade gitea --with-tests`** — that will open a verified cc-ci test PR to
replace the brittle fixed-timeout restart poll with the recipe's declared readiness
(`HTTP_TIMEOUT` / `READY_PROBE`, per `tests/STYLE.md` §5). This PR's `!testme` then goes green
once the test PR is merged.
2. Treat the run as a **flake** and simply re-post `!testme` on this PR (within the budget) — if the
restart window is kind, the same test passes; but the fixed-timeout brittleness remains.
The recipe upgrade itself (single line: `gitea/gitea:1.27.3-rootless` → `28.0.0-rootless`) is
verified live on the cc-ci dev swarm and by the install/upgrade/backup/restore/lint tiers. NOT merged.
Diagnosis (from run #31): the failure was in the test's own post-restart wait — _api() caught only HTTPError, so one urlopen(timeout=20) socket timeout during the 28.0.0 migration-window restart raised out of the poll loop instead of being retried; the poll also hard-coded a 120s deadline / 20s request timeout while recipe_meta declares HTTP_TIMEOUT = 600 (STYLE.md §5). The app itself was fine — install/upgrade/backup/restore + every other custom test passed on run #31, and 28.0.0 served /api/healthz + authed /api/v1/version on a separate dev deploy.
Fix (assertions untouched): _api() is now never-raising (mirrors lifecycle.http_fetch → transient errors return (0, {}) and the bounded poll retries); poll deadline derives from recipe_meta.HTTP_TIMEOUT (600).
Verification: with PR #44 checked out on the CI host, the full cold suite against this PR's head (91e42c8) passed 5/5 tiers with test_lfs_roundtrip green (incl. the restart + wait) — evidence in the #44 thread.
Dependent pair: !testme on this PR goes green only after #44 is merged (it runs the deployed cc-ci tests). Operator: merge #44, then re-!testme here. Merge of #10 remains yours; recommended release after upstream merge: abra recipe release gitea -x.
Stale-test fix PR is up and verified: https://git.autonomic.zone/recipe-maintainers/cc-ci/pulls/44
Diagnosis (from run #31): the failure was in the test's own post-restart wait — `_api()` caught only `HTTPError`, so one `urlopen(timeout=20)` socket timeout during the 28.0.0 migration-window restart raised out of the poll loop instead of being retried; the poll also hard-coded a 120s deadline / 20s request timeout while `recipe_meta` declares `HTTP_TIMEOUT = 600` (STYLE.md §5). The app itself was fine — install/upgrade/backup/restore + every other custom test passed on run #31, and 28.0.0 served `/api/healthz` + authed `/api/v1/version` on a separate dev deploy.
Fix (assertions untouched): `_api()` is now never-raising (mirrors `lifecycle.http_fetch` → transient errors return `(0, {})` and the bounded poll retries); poll deadline derives from `recipe_meta.HTTP_TIMEOUT` (600).
Verification: with PR #44 checked out on the CI host, the full cold suite against this PR's head (`91e42c8`) passed 5/5 tiers with `test_lfs_roundtrip` green (incl. the restart + wait) — evidence in the #44 thread.
**Dependent pair: `!testme` on this PR goes green only after #44 is merged** (it runs the deployed cc-ci tests). Operator: merge #44, then re-`!testme` here. Merge of #10 remains yours; recommended release after upstream merge: `abra recipe release gitea -x`.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Upgrade the
gitearecipe app imagegitea/gitea1.27.3-rootless → 28.0.0-rootless(upstream v28.0.0, released 2026-09-29 — a major-version jump from the 1.x line).
What changed
1.27.3-rootless→28.0.0-rootlessdb(postgres overlay 15.19 / mariadb 10.11.19) held — db majors are not in-place-safe(logical pg_dump/psql backup only; no pg_upgrade), per standing precedent.
app.ini.tmpl/compose overlay changes required — see Operator Action Required below.Upstream release notes: app gitea/gitea 1.27.3→28.0.0: https://github.com/go-gitea/gitea/releases/tag/v28.0.0
Operator Action Required (BREAKING, from the v28.0.0 release notes)
fix(git)!: use internal proxy for all git operations,#39426):
ALLOWED_DOMAINS/BLOCKED_DOMAINS/ALLOW_LOCALNETWORKSare deprecated (migrated to[security] ALLOWED_HOST_LIST/BLOCKED_HOST_LIST); theexternaldeny preset is removed — useEGRESS_MODE = strictfor anexclusive allow-list. In the default
laxmodeALLOWED_HOST_LISTno longer restricts publichosts (a startup warning flags this). All Git network operations now route through gitea's
internal proxy (SSRF hardening).
Recipe impact: none by default — this recipe sets none of those keys. Operators who DO
restrict git egress must switch to
EGRESS_MODE = strict+ explicit CIDRs to keep a deny-by-defaultpolicy.
feat(actions)!: add RUN_RETENTION_DAYS, #38855):new
[actions] RUN_RETENTION_DAYS(0 = keep forever). No recipe change needed (defaults apply).golang.org/x/cryptoSSH to address a DoS (#39219);Advisory scan (deterministic pre-step; gitea 1.27.3 → 28.0.0)
incomplete (floor, not ceiling) — see the per-recipe log.
Version bump (operator's final step — the PR does NOT touch the label)
Major version jump + upstream BREAKING changes ⇒ release as a major:
Verification
--chaosdeploy on cc-ci (dev-gitea.ci.commoninternet.net, mariadb overlay): converged1/1, app log
Gitea version: 28.0.0,/api/healthz200, admin user + repo created via API(HTTP 201/200),
/api/v1/version→{"version":"28.0.0"}. Dev deploy torn down (no leak).!testmeon this PR (existing tests, full suite).Tested green on the cc-ci recipe CI server (full suite, cold, against this PR head). NOT merged — for operator review.
cc @trav @notplants
!testme
🌻 cc-ci —
gitea@91e42c8a❌ failurefull logs · dashboard
!testmeis RED — but only on a stale LFS custom test (recipe upgrade itself verified GREEN on every lifecycle tier)The upgrade itself looks correct. The cc-ci run (
#31, on91e42c8a) is:Three of the four custom tests pass (
test_admin_api_user_org_token_lifecycle— admin API CRUD,test_git_push— full SCM push round-trip,test_gitea_root_returns_200). The one failure istest_lfs_roundtrip, and it fails in the test's own restart polling loop, not in the LFSround-trip or the recipe.
What the failing test asserts vs. what actually failed
The traceback is a
TimeoutError: The read operation timed outon that_api()call(
urllib.request.urlopen(..., timeout=20)) — i.e. the single/api/v1/versionrequest stalled~20 s and raised, aborting the whole test. Crucially:
_api()from inside the cc-ci runner/harness process on the cc-ci host(it shells out to
docker service updatedirectly), so this poll is a local HTTP call to theapp's public
/api/v1/version— it is not the app's own healthcheck;customstage pass, theinstall/upgrade stages already asserted the same app healthz/version endpoint, and the App
registry in this very run reports
install/upgrade/backup/restore = pass;poll during the app's post-restart window.
Why this reads as genuinely stale
1.27.3-rootless) reports
!testme GREEN on the first run, custom tier included.wiring; a cold-restart boot + first-request window is exactly the kind of timing the fixed 20 s
timeout (and the comment's own
# Wait for gitea to come back upTODO) is brittle to.tests/STYLE.md§5, "derive waits from the recipe's declared readiness, not a guess" — thisloop hard-codes
timeout=20per attempt instead of using the recipe's declaredHTTP_TIMEOUT(600 s) /
READY_PROBE. That is the test's bug, not the recipe's.converged, and admin + repo API +
/api/v1/versionall returned 200 on 28.0.0.What I did NOT do (DEFAULT mode)
I did not modify the test or the harness.
!testmecannot go fully green on this PR without atest change, and test changes are gated behind
--with-tests. So this PR is recorded asSUCCESS-PENDING-TESTSin DEFAULT mode.Operator options
/recipe-upgrade gitea --with-tests— that will open a verified cc-ci test PR toreplace the brittle fixed-timeout restart poll with the recipe's declared readiness
(
HTTP_TIMEOUT/READY_PROBE, pertests/STYLE.md§5). This PR's!testmethen goes greenonce the test PR is merged.
!testmeon this PR (within the budget) — if therestart window is kind, the same test passes; but the fixed-timeout brittleness remains.
The recipe upgrade itself (single line:
gitea/gitea:1.27.3-rootless→28.0.0-rootless) isverified live on the cc-ci dev swarm and by the install/upgrade/backup/restore/lint tiers. NOT merged.
Stale-test fix PR is up and verified: recipe-maintainers/cc-ci#44
Diagnosis (from run #31): the failure was in the test's own post-restart wait —
_api()caught onlyHTTPError, so oneurlopen(timeout=20)socket timeout during the 28.0.0 migration-window restart raised out of the poll loop instead of being retried; the poll also hard-coded a 120s deadline / 20s request timeout whilerecipe_metadeclaresHTTP_TIMEOUT = 600(STYLE.md §5). The app itself was fine — install/upgrade/backup/restore + every other custom test passed on run #31, and 28.0.0 served/api/healthz+ authed/api/v1/versionon a separate dev deploy.Fix (assertions untouched):
_api()is now never-raising (mirrorslifecycle.http_fetch→ transient errors return(0, {})and the bounded poll retries); poll deadline derives fromrecipe_meta.HTTP_TIMEOUT(600).Verification: with PR #44 checked out on the CI host, the full cold suite against this PR's head (
91e42c8) passed 5/5 tiers withtest_lfs_roundtripgreen (incl. the restart + wait) — evidence in the #44 thread.Dependent pair:
!testmeon this PR goes green only after #44 is merged (it runs the deployed cc-ci tests). Operator: merge #44, then re-!testmehere. Merge of #10 remains yours; recommended release after upstream merge:abra recipe release gitea -x.!testme
🌻 cc-ci —
gitea@91e42c8a✅ passedfull logs · dashboard
!testme
🌻 cc-ci —
gitea@91e42c8a✅ passedfull logs · dashboard
Pull request closed