Compare commits
21
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71ca680c86 | ||
|
|
a1c37185d8 | ||
|
|
e52767665b | ||
|
|
fa73b81427 | ||
|
|
04eb8b4896 | ||
|
|
edf1e2bd39 | ||
|
|
5aee9551d0 | ||
|
|
e29b5b4101 | ||
|
|
6f63214d30 | ||
|
|
9202936b2e | ||
|
|
935a411c0c | ||
|
|
e85094a8e9 | ||
|
|
bb2aac39c8 | ||
|
|
8b79c4db20 | ||
|
|
2d312b366e | ||
|
|
43f2cf074b | ||
|
|
1b8c3966cb | ||
|
|
c6dad5f11b | ||
|
|
b8161f15e4 | ||
|
|
7934367f15 | ||
|
|
d48e47adfe |
@@ -50,6 +50,42 @@ not the test server — so it can run either way. Probe, then follow that branch
|
||||
4. Everything else in the canonical script (origin/dev remote setup, release recommendation,
|
||||
emitted next-steps) is identical.
|
||||
|
||||
Every time the branch was prepared **here**, remember it exists only on this host — the
|
||||
operator's machine must fetch it first. Always emit this **step 0** before the push step
|
||||
(anonymous public fetch, no credentials needed):
|
||||
|
||||
```
|
||||
# 0. On a machine WITHOUT the branch pre-fetched, get it from the autonomic mirror
|
||||
# (fetch by URL — works regardless of what the local remotes are named):
|
||||
cd <local checkout of the recipe>
|
||||
git fetch https://git.autonomic.zone/recipe-maintainers/<recipe>.git +refs/pull/<PR_NUM>/head:refs/heads/<HEAD_REF>
|
||||
git checkout <HEAD_REF>
|
||||
```
|
||||
|
||||
If the operator's checkout does NOT yet have the mirror remote, emit once before the fetch:
|
||||
|
||||
```
|
||||
git remote add gitea https://git.autonomic.zone/recipe-maintainers/<recipe>.git
|
||||
```
|
||||
|
||||
In both modes the final output is a set of commands for the operator to run on a machine **with
|
||||
push access to `git.coopcloud.tech`** — always print them, even when everything local is
|
||||
already prepared.
|
||||
|
||||
**Verify the merge-base BEFORE rebasing — bases can be traps.** Upstream repos can
|
||||
carry a stray, divergent `main` ALONGSIDE the real `master` base branch. Rebase targets must be
|
||||
chosen by evidence: `git ls-remote origin | grep -E 'refs/heads/(main|master)$'`, then
|
||||
`git merge-base <head_ref> origin/<candidate>` — the correct base is the one where the upgrade
|
||||
branch's merge-base is its tip's parent (i.e. `<head_ref>` is already directly on that line —
|
||||
then NO cherry-pick is needed, the raw branch compares clean). Cherry-picking onto the wrong
|
||||
base produces the very messy 3-dot compare this guard exists to prevent (real case: gitea —
|
||||
first "rebase" landed on a stray `main` and had to be redone onto `master`).
|
||||
|
||||
**Branch-name mismatch mirror vs upstream.** The mirror and upstream can use different base-branch
|
||||
names, and a mirror can even carry a stray, divergent `main` alongside the real base (gitea: mirror
|
||||
PR against `main`, upstream `master`; keycloak PR #9: base `main`, both mirror and upstream on
|
||||
`master`). The canonical script now detects the upstream default branch by evidence (`git
|
||||
ls-remote --symref origin HEAD`, then the git.coopcloud.tech API, then the PR base with a warning)
|
||||
and uses it for the compare URL AND the post-merge release steps — never hardcode `main`, and when
|
||||
it prints the base-mismatch warning, confirm the upgrade branch actually sits on that line
|
||||
(`git merge-base` against both candidates) before emitting the push step.
|
||||
|
||||
@@ -13,7 +13,8 @@ Usage:
|
||||
launch-report.py status show session state
|
||||
|
||||
Env: REPORT_BACKEND (default opencode), REPORT_MODEL (default tracks tier),
|
||||
LOOP_TIER / REPORT_TIER (default 'zen'; 'go' or 'zen'; only for opencode),
|
||||
LOOP_TIER / REPORT_TIER (default 'go' — the only tier with a key on this host; ZEN
|
||||
models are dead here; 'go' or 'zen'; only for opencode),
|
||||
OPENCODE_SHARE (1=attach web server + public --share link), REPORT_SESSION, REPORT_DIR.
|
||||
"""
|
||||
import os, subprocess, sys, time
|
||||
@@ -24,9 +25,9 @@ SESSION = os.environ.get("REPORT_SESSION", "cc-ci-report")
|
||||
WORKDIR = os.environ.get("REPORT_DIR", "/srv/cc-ci")
|
||||
LOG_DIR = os.environ.get("LOG_DIR", "/srv/cc-ci/.cc-ci-logs")
|
||||
BACKEND = os.environ.get("REPORT_BACKEND", "opencode")
|
||||
TIER = os.environ.get("LOOP_TIER", os.environ.get("REPORT_TIER", "zen"))
|
||||
TIER = os.environ.get("LOOP_TIER", os.environ.get("REPORT_TIER", "go"))
|
||||
_TIER_MODEL = {"go": "opencode-go/glm-5.2", "zen": "opencode/glm-5.2"}
|
||||
_DEFAULT_MODEL = _TIER_MODEL.get(TIER, _TIER_MODEL["zen"]) if BACKEND == "opencode" else "opus"
|
||||
_DEFAULT_MODEL = _TIER_MODEL.get(TIER, _TIER_MODEL["go"]) if BACKEND == "opencode" else "opus"
|
||||
MODEL = os.environ.get("REPORT_MODEL", _DEFAULT_MODEL)
|
||||
CLAUDE_BIN = os.environ.get("CLAUDE_BIN", "claude")
|
||||
CLAUDE_FLAGS = os.environ.get("CLAUDE_FLAGS", "--dangerously-skip-permissions")
|
||||
|
||||
@@ -44,11 +44,13 @@ WORKDIR = os.environ.get("UPGRADER_DIR", "/srv/cc-ci")
|
||||
LOG_DIR = os.environ.get("LOG_DIR", "/srv/cc-ci/.cc-ci-logs")
|
||||
|
||||
# LOOP_BACKEND / LOOP_MODEL take precedence (unified control from the operator).
|
||||
# LOOP_TIER selects the OpenCode subscription: "zen" (default) or "go". Go hit a monthly
|
||||
# usage limit; ZEN is the working alternative. Set LOOP_TIER=go when the Go limit resets.
|
||||
# LOOP_TIER selects the OpenCode subscription: "go" (DEFAULT — the only tier with a key on
|
||||
# this host) or "zen" (opt-in on a host that has a ZEN key). ZEN models are DEAD here — the
|
||||
# endpoint errors server-side, and env loss in the tmux→agent→launch-report chain is exactly
|
||||
# how it silently ate the 2026-09-18 weekly report. Never default back to zen.
|
||||
# Only affects the opencode backend; claude ignores the tier.
|
||||
BACKEND = os.environ.get("LOOP_BACKEND", os.environ.get("UPGRADER_BACKEND", "opencode"))
|
||||
TIER = os.environ.get("LOOP_TIER", os.environ.get("UPGRADER_TIER", "zen"))
|
||||
TIER = os.environ.get("LOOP_TIER", os.environ.get("UPGRADER_TIER", "go"))
|
||||
_TIER_CFG = {
|
||||
"go": {"model": "opencode-go/glm-5.2", "endpoint": "https://opencode.ai/zen/go/v1/chat/completions",
|
||||
"auth": "opencode-go", "label": "OpenCode Go"},
|
||||
@@ -58,7 +60,7 @@ _TIER_CFG = {
|
||||
if BACKEND == "opencode" and TIER not in _TIER_CFG:
|
||||
print(f"[upgrader] ERROR: unknown LOOP_TIER '{TIER}' — use 'go' or 'zen'", flush=True)
|
||||
sys.exit(1)
|
||||
_tier = _TIER_CFG.get(TIER, _TIER_CFG["zen"])
|
||||
_tier = _TIER_CFG.get(TIER, _TIER_CFG["go"])
|
||||
_DEFAULT_MODEL = _tier["model"] if BACKEND == "opencode" else "sonnet"
|
||||
MODEL = os.environ.get("LOOP_MODEL", os.environ.get("UPGRADER_MODEL", _DEFAULT_MODEL))
|
||||
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
# Plan: migrate cc-ci domains from `*.commoninternet.net` to `ci.autonomic.zone`
|
||||
|
||||
**Date:** 2026-09-21 · **Status:** DRAFT — blocked only on operator DNS access (Gandi zone `autonomic.zone`)
|
||||
**Principle:** same host, same IP (195.201.88.249), same services — only names change. Dual-run new
|
||||
alongside old; old names retire last; rollback at any point is "change nothing further" because the
|
||||
old DNS zone keeps answering until Phase 4.
|
||||
|
||||
---
|
||||
|
||||
## 1. Inventory — what answers on what today
|
||||
|
||||
| Name | Served by | Router/config source |
|
||||
|---|---|---|
|
||||
| `ci.commoninternet.net` | results dashboard + bridge `/hook` | `cc-ci/nix/modules/dashboard.nix`, `bridge.nix` (traefik Host rules) |
|
||||
| `report.ci.commoninternet.net` | weekly report static site | `cc-ci/nix/modules/reports.nix` |
|
||||
| `drone.ci.commoninternet.net` | Drone server | `cc-ci/nix/modules/drone.nix` (abra app; env `DOMAIN`, `DRONE_SERVER_HOST`); runner RPC: `drone-runner.nix` |
|
||||
| `oc.ci.commoninternet.net` | opencode UI (traefik → host nginx basic auth) | `cc-ci-orch/nix/modules/orchestrator-host.nix` (`opencodeUiHost`), set in `nix/hosts/cc-ci/configuration.nix` |
|
||||
| `traefik.ci.commoninternet.net` | traefik stack identity + deploy-proxy health gate | `cc-ci/nix/modules/proxy.nix` |
|
||||
| `warm-*.ci.commoninternet.net` | warm canonical recipe stacks | `warm-keycloak.nix`, warm-bluesky-pds (caddy labels) |
|
||||
| `<tag>-<6hex>.ci.commoninternet.net` | per-run recipe test deploys | `cc-ci/runner/harness/naming.py` `app_domain()` — **resolves via public DNS** (containers use 1.1.1.1/8.8.8.8) |
|
||||
| `backups.ci.commoninternet.net` | backup-bot-two — stack name only, no web route | `backupbot.nix` |
|
||||
| `acme.commoninternet.net` (NS `ns-acme`) | local acme-dns (systemd, port 53) | `cc-ci/nix/modules/acme-dns.nix` |
|
||||
|
||||
**Cert chain today:** nixos `security.acme` (lego) issues `ci.commoninternet.net` + `*.ci.commoninternet.net`
|
||||
via DNS-01 through the local acme-dns; Gandi delegates `_acme-challenge.ci.commoninternet.net` CNAME →
|
||||
`9d3f0428-eb97-4b14-bb07-da2f0f6c2073.acme.commoninternet.net`. `postRun` fires
|
||||
`cc-ci-acme-traefik-handoff.service` → installs cert into `/var/lib/ci-certs/live` → restarts
|
||||
`deploy-proxy.service`, whose reconciler re-derives the `traefik_ci_commoninternet_net_ssl_{cert,key}_v<hash>`
|
||||
swarm secrets (traefik runs co-op-cloud wildcard/no-ACME mode off those secrets).
|
||||
**DNS:** both zones on Gandi. IPv4-only (no AAAA records anywhere; none needed).
|
||||
|
||||
**External references:** Gitea OAuth app for Drone (redirect URI → drone.ci…); per-repo webhooks →
|
||||
`/hook` — **only `recipe-maintainers/ghost` and `recipe-maintainers/discourse` have any** (everything
|
||||
else is bridge-driven via the Drone API); ~60 doc references across cc-ci-orch (AGENTS.md, README,
|
||||
plan.md, skills).
|
||||
|
||||
## 2. Target naming
|
||||
|
||||
| Old | New |
|
||||
|---|---|
|
||||
| ci.commoninternet.net | ci.autonomic.zone |
|
||||
| report.ci.commoninternet.net | report.ci.autonomic.zone |
|
||||
| drone.ci.commoninternet.net | drone.ci.autonomic.zone |
|
||||
| oc.ci.commoninternet.net | oc.ci.autonomic.zone |
|
||||
| traefik.ci.commoninternet.net | traefik.ci.autonomic.zone |
|
||||
| *.ci.commoninternet.net (warm-\*, per-run apps, backups identity) | *.ci.autonomic.zone |
|
||||
| acme.commoninternet.net | **stays** through Phases 1–2 (infra, not a front door); optional Phase 3 |
|
||||
|
||||
Swarm **stack names** (`traefik_ci_commoninternet_net`, `ccci-*`, warm stacks) are identifiers, not
|
||||
domains — keep them where possible to avoid churn in keep-lists/prune filters, EXCEPT abra-managed
|
||||
apps whose name **is** the domain (drone, backup-bot, per-run apps).
|
||||
|
||||
## 3. DNS records to set (zone `autonomic.zone`, Gandi — operator)
|
||||
|
||||
| # | Name | Type | Value | TTL | Purpose |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | `ci.autonomic.zone` | A | `195.201.88.249` | 300 | apex front door (wildcard does NOT cover the apex) |
|
||||
| 2 | `*.ci.autonomic.zone` | A | `195.201.88.249` | 300 | report/drone/oc/traefik/warm-\*/`<tag>-<6hex>` run apps |
|
||||
| 3 | `_acme-challenge.ci.autonomic.zone` | CNAME | `9d3f0428-eb97-4b14-bb07-da2f0f6c2073.acme.commoninternet.net.` | 300 | DNS-01 for the new wildcard — **reuses the existing restricted acme-dns account** (`disable_registration=true`; the lego *account* authenticates, not the FQDN, so one acme-dns subdomain serves both zones) |
|
||||
|
||||
Also: lower TTL to 300 on the old `ci`/`*.ci` records a few days ahead (fast rollback later).
|
||||
Phase 3 (optional) adds: `acme.ci.autonomic.zone` + `ns-acme.ci.autonomic.zone` A records.
|
||||
|
||||
**DNS verification (before touching the host):**
|
||||
```bash
|
||||
dig +short ci.autonomic.zone A @ns-187-a.gandi.net # 195.201.88.249
|
||||
dig +short report.ci.autonomic.zone @1.1.1.1 # 195.201.88.249 (via wildcard)
|
||||
dig +short drone.ci.autonomic.zone @8.8.8.8 # 195.201.88.249
|
||||
dig +short _acme-challenge.ci.autonomic.zone CNAME # 9d3f0428-….acme.commoninternet.net.
|
||||
```
|
||||
|
||||
## 4. Phase 1a — certificate (issue BEFORE any routing change)
|
||||
|
||||
1. `cc-ci/nix/modules/acme-dns.nix`: add a second `security.acme.certs."ci.autonomic.zone"` entry
|
||||
(domain + `*.ci.autonomic.zone` SAN, `dnsProvider = "acmedns"`, `dnsResolver = "1.1.1.1:53"`).
|
||||
Split the lego env per cert — today `ACME_DNS_STORAGE_PATH` is baked to
|
||||
`/var/lib/acme/ci.commoninternet.net/…`; generate one env file per cert so each keeps its own
|
||||
acme-dns account file.
|
||||
2. Extend `cc-ci-acme-traefik-handoff` to stage the new cert alongside the old
|
||||
(`/var/lib/ci-certs/live` holds both, e.g. `fullchain.pem` = new + `fullchain-old.pem`).
|
||||
3. Extend the **deploy-proxy reconciler** (`proxy.nix`) for **dual-cert SNI**: new wildcard pair as
|
||||
`ssl_cert/ssl_key` (default store) + old pair as a second secret pair wired through a small
|
||||
file-provider `[[tls.certificates]]` so `*.ci.commoninternet.net` keeps serving during the bake
|
||||
window. This is the one fiddly bit — validate against the **Let's Encrypt staging** endpoint
|
||||
first (the module already has the staging-marker pattern).
|
||||
4. `nixos-rebuild test` → confirm both certs issued and served (`openssl s_client -servername` for a
|
||||
name in each zone, check SANs) → `switch`.
|
||||
|
||||
## 5. Phase 1b — routing + service config (one stack at a time, health-gated)
|
||||
|
||||
During the bake window every router rule carries **both** hostnames
|
||||
(`Host(`ci.autonomic.zone`) || Host(`ci.commoninternet.net`)`); old names drop out only in Phase 4.
|
||||
|
||||
- `dashboard.nix` + `bridge.nix` (share the `ci` host: dashboard `/` + bridge `/hook`), `reports.nix`,
|
||||
and the traefik health-gate name in `proxy.nix`.
|
||||
- `bridge.py` / `dashboard.py` code defaults for `DRONE_URL` / `DASH_URL`.
|
||||
- `recipe-report.py` — footer, publish URL text and its verification curl → `report.ci.autonomic.zone`
|
||||
(regenerate the index after, so the home page stops linking the old name).
|
||||
- Drone **abra app rename** (name = domain, so this is a new stack):
|
||||
1. Export enabled-repo list from the old Drone API.
|
||||
2. Deploy new abra app `drone.ci.autonomic.zone` (same module; secrets `rpc_secret`,
|
||||
`client_secret` re-insert from `/run/secrets` as today). **Fresh DB** — Drone data volume is
|
||||
stack-name-prefixed; repos re-sync on Gitea login, build history is lost (accepted; export the
|
||||
old repo list and re-enable via API).
|
||||
3. Point `drone-runner.nix` `DRONE_RPC_HOST` at the new name; restart runner.
|
||||
4. Re-run `scripts/bootstrap-drone-oauth.sh` with `DRONE=https://drone.ci.autonomic.zone`
|
||||
(updates the Gitea OAuth redirect URI; script already uses `--resolve` to hit loopback).
|
||||
5. Update the two webhooks (ghost, discourse) to `https://drone.ci.autonomic.zone/hook?secret=<same>`
|
||||
— read the secret off the existing hook config before deleting it.
|
||||
6. `abra app undeploy` + remove the old drone app once green.
|
||||
- `configuration.nix`: `opencodeUiHost = "oc.ci.autonomic.zone"`; extend the `networking.hosts`
|
||||
self-pin list with the new names (keep old pins during bake).
|
||||
- Gitea `GITEA_SERVER` stays `git.autonomic.zone` — unchanged.
|
||||
|
||||
## 6. Phase 2 — harness / warm / per-run naming (the `_ci_commoninternet_net` blast radius)
|
||||
|
||||
- `cc-ci/runner/harness/naming.py` `app_domain()` → `.ci.autonomic.zone` (single source of truth).
|
||||
- Regexes/patterns that match the derived names: `docker-prune.nix` run-app filter,
|
||||
`tests/concurrency/concutil.py` (`DOMAIN` const + `RUN_APP_RE`), any separate `RUN_APP_RE` in
|
||||
`lifecycle.py` / `warm.py` / `warm_reconcile.py` / `canonical.py` / `generic.py` / `http.py`,
|
||||
`nightly-sweep` keep-list patterns, `tests/gitea/recipe_meta.py` comment.
|
||||
- Redeploy warm stacks under new names (abra `warm-<recipe>.ci.autonomic.zone`), reconcile, drain old.
|
||||
- `backupbot.nix` `DOMAIN` (stack-name-only change).
|
||||
- Verify: full deterministic test suite + one live `!testme` run end-to-end.
|
||||
|
||||
## 7. Phase 3 (optional) — move acme-dns off commoninternet.net
|
||||
|
||||
Only if `commoninternet.net` is being dropped entirely: new `domain`/`nsname` in acme-dns config
|
||||
(`acme.ci.autonomic.zone` / `ns-acme.ci.autonomic.zone` + Gandi records), briefly re-enable
|
||||
registration or hand-migrate the account DB, re-point `_acme-challenge` CNAMEs, re-issue both certs.
|
||||
|
||||
## 8. Phase 4 — retirement (after ≥7 clean days)
|
||||
|
||||
Remove old names from the dual router rules → remove `security.acme.certs."ci.commoninternet.net"`
|
||||
+ its env file + old cert secret pair → drop old `networking.hosts` pins → delete old Gandi records
|
||||
(`ci`, `*.ci`, `_acme-challenge.ci`) → restore TTLs to 3600. Keep `acme.commoninternet.net` unless
|
||||
Phase 3 was done.
|
||||
|
||||
## 9. Verification checklist
|
||||
|
||||
- **DNS:** the dig table in §3 against a Gandi NS and a public resolver.
|
||||
- **Cert:** `openssl s_client -connect 195.201.88.249:443 -servername <name>` for one name per zone —
|
||||
SANs correct for both, `ssl_verify_result=0`.
|
||||
- **Front doors:** `ci` → 200 · `report.ci` → 200 · `drone.ci` → 200/303 · `oc.ci` → **401**
|
||||
(basic auth intact) · `traefik.ci/api/version` → 200.
|
||||
- **SSO:** Drone login through Gitea on the new redirect URI.
|
||||
- **Webhook:** empty commit to `recipe-maintainers/ghost` main → build fires on the new host.
|
||||
- **Bridge/!testme:** `!testme` on a scratch PR → build runs, `results.json` written, PR comment
|
||||
contains **new** domain links.
|
||||
- **Report:** publish one report page and verify it at `https://report.ci.autonomic.zone`.
|
||||
- **Host:** `systemctl --failed` empty; `deploy-proxy` reconcile logs clean.
|
||||
|
||||
## 10. Risks / gotchas
|
||||
|
||||
- **Dual-cert SNI in the traefik reconciler** is the highest-risk change — stage-first, verify SNI
|
||||
selection for both zones before switching.
|
||||
- **Drone DB** does not follow the abra rename — accept fresh build history; export/re-apply the
|
||||
enabled-repo list; re-insert secrets from `/run/secrets` (module already does).
|
||||
- **Per-run recipe domains resolve via public DNS** — the `*.ci.autonomic.zone` wildcard A record is
|
||||
load-bearing for every test deploy; never merge Phase 2 before it propagates.
|
||||
- Old PR comments/badges link old names — they keep working until Phase 4 removes the old records.
|
||||
- ~60 doc references (AGENTS.md, README, plan.md, skills, status skill) — one cosmetic doc PR at the
|
||||
end so the docs don't lie.
|
||||
- No domain-bearing secret values were found (webhook secrets are per-repo and copied as-is).
|
||||
|
||||
## 11. Sequencing
|
||||
|
||||
1. **Operator:** TTLs down + 3 Gandi records (§3) — 5 minutes.
|
||||
2. **Agent:** cert PR (§4) → `test` → verify dual SNI → `switch`.
|
||||
3. **Agent:** routing/env PRs (§5) stack-by-stack, health-gated; OAuth + webhooks + drone rename.
|
||||
4. **Agent:** end-to-end verification (§9) + report republish under the new name.
|
||||
5. **Bake ≥7 days** → Phase 2 PR → full suite → bake → Phase 4 retirement.
|
||||
@@ -9,6 +9,7 @@
|
||||
- nginx even-numbered minor versions (1.28.x, 1.30.x) are mainline; odd (1.27.x, 1.29.x) are stable. The recipe tracks mainline.
|
||||
- compose.git-pull.yml is an optional overlay for git-pull functionality; alpine/git version lives there.
|
||||
- compose.sftp.yml and compose.sso.yml are other optional overlays; linuxserver/openssh-server uses `latest` tag and is not version-pinned.
|
||||
- nginx 1.31.6 (15 Sep 2026) is a security patch release: CVE-2026-90439 (heap buffer overflow in a worker under certain configurations when using HTTP/3 with OpenSSL 3.5.0 and earlier). Change: QUIC transport parameters extension received in an SSL connection is now always ignored. Bugfixes: binary upgrade with control API socket + perl module; predicate-location evaluation error ignored; nested location lookup with regex/predicate locations; segfault reading config with `geo` … `ranges` + corrupted binary base file. None affect the custom-html static-file-serving use case (no HTTP/3/QUIC, no predicate locations, no perl, no `geo`).
|
||||
- Breaking change in nginx 1.31.0: HTTP/2 and HTTP/3 requests with Connection/Proxy-Connection/Keep-Alive/Transfer-Encoding/Upgrade/TE headers are now rejected.
|
||||
- Breaking change in nginx 1.29.7: keepalive in upstream block is enabled by default; proxy_http_version changed to 1.1; Connection proxy header no longer sent by default.
|
||||
- nginx 1.31.3 (15 Jul 2026) is a security patch release: CVE-2026-42533 (heap buffer overflow in `map` directive with regex matching / non-cacheable variable in string expression), CVE-2026-60005 (uninitialized memory access with unnamed regex captures + `slice` directive / background cache update → memory disclosure or worker crash), CVE-2026-56434 (use-after-free in `ngx_http_ssi_filter_module` processing crafted proxied backend response). Two non-security `Change`s: HTTP/2 response header/trailer size now limited by `proxy_buffer_size`/`grpc_buffer_size` in `ngx_http_proxy_v2_module`+`ngx_http_grpc_module`; external-entity loading disabled by default in `ngx_http_xslt_filter_module` (new `xml_external_entities` directive re-enables it). Plus bugfixes (HTTP/2 out-of-order pseudo-headers, flow control, Upgrade header in HTTP/2/3 responses). NONE of these changes affect the custom-html static-file-serving use case (no `map`, no `slice`/cache, no SSI, no xslt, no proxy_v2/grpc). No breaking config changes — no operator action required beyond the image pull.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
## Standing notes
|
||||
- hedgedoc 1.11.0 (2026): 4 security CVEs fixed (HTML injection, YAML DoS, CSRF via Gist export, rate-limit bypass). No breaking changes, no migrations, no schema changes. Optional new env var `CMD_RATE_LIMIT_USING_CLOUDFLARE` only needed if running behind Cloudflare — not required for standard deployments.
|
||||
- pgautoupgrade: handles Postgres major-version upgrades automatically on container start. Bump ONE major at a time (16→17, then 17→18 on next cycle). The image tag is `<pg-major>-alpine`.
|
||||
- pgautoupgrade 18+ (deferred, learned 2026-09-11): PG 18 changes the data-directory layout (docker-library/postgres#1259; PGDATA → `/var/lib/postgresql/<MAJOR>/docker`) — this recipe mounts `postgres:/var/lib/postgresql/data` with no PGDATA, and per the pgautoupgrade README ("Error message when mounting data to /var/lib/postgresql/data on Postgres v18+") that needs a dedicated PGDATA/volume-mount change before bumping to 18, or you can get an empty instance instead of an upgraded one. 16→17 verified live on cc-ci 2026-09-11 (`Upgrade to PostgreSQL 17.11 complete.`, data survived).
|
||||
- pgautoupgrade: handles Postgres major-version upgrades automatically on container start; multi-major in ONE deploy works (16.15→18.6 verified live 2026-09-18 — pg_upgrade ran directly on the v16 files to 18.6, data survived). The image tag is `<pg-major>-alpine`.
|
||||
- pgautoupgrade 18+ (RESOLVED on PR #3, 2026-09-18): PG 18 changes the data-directory layout (docker-library/postgres#1259; PGDATA → `/var/lib/postgresql/<MAJOR>/docker`, `/var/lib/postgresql/data` becomes a symlink → mount error or EMPTY instance if the mount stays at .../data). The fix carried by PR #3: move the volume mount to `postgres:/var/lib/postgresql` (NO PGDATA env pin needed) — the entrypoint's `MOVING_TO_NEW_STRUCTURE` detects `/var/lib/postgresql/PG_VERSION`, upgrades, and moves data into `<MAJOR>/docker` automatically. Verified live on cc-ci 2026-09-18: `Upgrade to PostgreSQL 18.6 complete.`, pad data byte-identical, app green; 16→17 separately verified 2026-09-11 (`Upgrade to PostgreSQL 17.11 complete.`). Upstream coopcloud renovate PR #22 bumps the tag WITHOUT this mount fix — do not merge that alone.
|
||||
- hedgedoc 1.12.0 (2026-08): requires Node 20.17+ (app image bundles its own node — not recipe-relevant); webp uploads; some highlight.js languages removed/changed. No breaking changes or migrations for the recipe.
|
||||
- cc-ci tests use the sqlite backend (default compose.yml), not the postgresql compose override — so pgautoupgrade bumps do not affect CI test coverage (verify pg bumps via a direct --chaos deploy with the postgresql override instead).
|
||||
|
||||
@@ -68,6 +68,17 @@
|
||||
reject). Done in the 2026-09-11 upgrade (v3.1.0→v3.2.0, extending PR #4 from upstream main
|
||||
794560f; PR #4's pgvectors0.3.0/valkey-8e8d64b4 divergences superseded — DB realigned to
|
||||
immich's official combo, which upstream main itself chose 2026-08-07).
|
||||
- **immich-server v3.2.1 (2026-09-14) + v3.2.2 (2026-09-15) pins** (from `docker/docker-compose.yml`
|
||||
@ the v3.2.2 tag): `postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:bcf63357…` and
|
||||
`valkey:9@sha256:70739f85ad…` — **BOTH identical to the recipe's v3.2.0-era pins** (DB and
|
||||
redis stay put; live valkey `9` tag has drifted to `c123e371…` = 9.1.2 but immich's tested
|
||||
pin `70739f85…` = 9.1.1 is kept per the 2026-07-17 precedent; live postgres tag digest
|
||||
re-verified = the pin, not stale). `example.env` @ v3.2.2 byte-identical to v3.2.0. Both
|
||||
releases are bug-fix-only (v3.2.1: sync connection-pool exhaustion, search modal, partner
|
||||
assets on people page, person merge, server auto-VACUUM after TypeORM migrations; v3.2.2:
|
||||
reassign-faces fix) — no breaking changes, no config additions, no operator action. Done in
|
||||
the 2026-09-18 upgrade (v3.2.0→v3.2.2, app+ML only; fresh PR — PR #4 was closed when its
|
||||
v3.2.0 content merged upstream as #19 / `1.11.0+v3.2.0`).
|
||||
- **2026-08-07 INFRA note: cc-ci runner's gitea clone-token is STALE (HTTP 401).** `!testme` build
|
||||
#1210 died at the recipe `git clone` step (~5s, before any deploy) with `could not read Username for
|
||||
'https://git.autonomic.zone'` — the runner's mounted gitea token `13e299f2…` is rejected (verified
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
| backend | lasuite/impress-backend | https://github.com/suitenumerique/docs | https://github.com/suitenumerique/docs/releases |
|
||||
| celery | lasuite/impress-backend | https://github.com/suitenumerique/docs | https://github.com/suitenumerique/docs/releases |
|
||||
| y-provider | lasuite/impress-y-provider | https://github.com/suitenumerique/docs | https://github.com/suitenumerique/docs/releases |
|
||||
| docspec | ghcr.io/docspecio/api | https://github.com/docspecIO/api | https://github.com/docspecIO/api/releases |
|
||||
| docspec | ghcr.io/docspec/api | https://github.com/docspec/docspec | https://github.com/docspec/docspec/releases |
|
||||
| db | pgautoupgrade/pgautoupgrade | https://github.com/pgautoupgrade/docker-pgautoupgrade | https://github.com/pgautoupgrade/docker-pgautoupgrade/releases |
|
||||
| redis | redis | https://github.com/redis/redis | https://hub.docker.com/_/redis/tags |
|
||||
| minio | minio/minio | https://github.com/minio/minio | https://github.com/minio/minio/releases |
|
||||
| minio | quay.io/minio/minio | https://github.com/minio/minio | https://github.com/minio/minio/releases |
|
||||
| web | nginx | https://github.com/nginx/nginx | https://nginx.org/en/CHANGES |
|
||||
|
||||
## Standing notes
|
||||
@@ -36,3 +36,17 @@
|
||||
- redis sidecar (cache/broker for celery+backend): prefer the patch line (8.8.1 over 8.10.0) unless a
|
||||
feature is needed — 8.8.1 is a security patch (RedisBloom/TDigest RESTORE RCE); 8.10.0 is a minor
|
||||
with many new features.
|
||||
- **2026-09-18: docspec image switched with impress v5.7.0** (upstream PR suitenumerique/docs#2553):
|
||||
`ghcr.io/docspecio/api:3.0.x` (Elixir, repo ARCHIVED) → `ghcr.io/docspec/api:1.21.4` (Rust rewrite,
|
||||
github.com/docspec/docspec). New image listens on **3000** (was 4000): recipe sets
|
||||
`DOCSPEC_API_URL=http://docspec:3000/conversion` and healthcheck `http://127.0.0.1:3000/health`
|
||||
(path `/conversion` and the raw-body request contract unchanged).
|
||||
- ⚠️ When checking GHCR tags by script: the `/tags/list` endpoint CAPS AT 100 tags — a missing tag
|
||||
there means nothing. Verify the **manifest** endpoint instead (e.g. `ghcr.io/docspec/api:1.21.4`
|
||||
is absent from tags/list but pulls fine).
|
||||
- **2026-09-13ish: upstream #25 moved the minio pin to `quay.io/minio/minio`** (same
|
||||
RELEASE.2025-09-07T16-13-09Z tag — manifest-verified pullable on quay; quay's own tags/list is
|
||||
paginated and misleading, use manifest checks there too).
|
||||
- **v5.7.0 moved the favorites API endpoint to `/documents/favorites/`** — breaking only for
|
||||
external API consumers, not the recipe/cc-ci tests (which use /authenticate/, /users/me/,
|
||||
/documents/).
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
| db | pgautoupgrade/pgautoupgrade | https://github.com/pgautoupgrade/docker-pgautoupgrade | https://github.com/pgautoupgrade/docker-pgautoupgrade/releases |
|
||||
| redis | redis | https://github.com/redis/redis | https://github.com/redis/redis/releases |
|
||||
| mailcatcher | sj26/mailcatcher | https://github.com/sj26/mailcatcher | https://github.com/sj26/mailcatcher/releases |
|
||||
| minio | minio/minio | https://github.com/minio/minio | https://github.com/minio/minio/releases |
|
||||
| minio-createbuckets | minio/minio | https://github.com/minio/minio | https://github.com/minio/minio/releases |
|
||||
| minio | quay.io/minio/minio | https://github.com/minio/minio | https://github.com/minio/minio/releases |
|
||||
| minio-createbuckets | quay.io/minio/minio | https://github.com/minio/minio | https://github.com/minio/minio/releases |
|
||||
| collabora | collabora/code | https://github.com/CollaboraOnline/online | https://www.collaboraonline.com/release-notes/ (per-version: .../collabora-online-25-04-release-notes/) |
|
||||
| onlyoffice | onlyoffice/documentserver-de | https://github.com/ONLYOFFICE/DocumentServer | https://github.com/ONLYOFFICE/DocumentServer/blob/master/CHANGELOG.md |
|
||||
| web | nginx | https://github.com/nginx/nginx | https://nginx.org/en/CHANGES |
|
||||
@@ -19,9 +19,12 @@
|
||||
- lasuite/drive-frontend and lasuite/drive-backend share the same version tag (drive monorepo).
|
||||
- minio and onlyoffice tags use non-semver formats; abra cannot auto-detect upgrades for them.
|
||||
- collabora/code uses a 5-part version scheme; abra cannot auto-detect upgrades for it.
|
||||
- minio: Docker Hub `minio/minio` `latest` is frozen at `RELEASE.2025-09-07T16-13-09Z` (the
|
||||
`RELEASE.2025-10-15T17-29-55Z` GitHub security release is NOT on Docker Hub `minio/minio`).
|
||||
Newer minio releases moved to the **AIStor** product line (`quay.io/minio/aistor/minio`).
|
||||
A future minio bump requires a registry+product switch — operator decision, not a routine tag bump.
|
||||
- minio: Docker Hub `minio/minio` was **REMOVED** (verified 2026-09-18: Hub repo API 404, registry
|
||||
manifest 401 on anonymous pull; last successful docker.io pull was the 2026-09-11 run). The same
|
||||
release tag lives on **`quay.io/minio/minio`** (MinIO's official distribution channel) — recipe
|
||||
pins switched there in lasuite-drive PR #7 (2026-09-18), same release `RELEASE.2025-09-07T16-13-09Z`,
|
||||
mirroring the identical fix merged upstream for lasuite-docs ("fix: pull MinIO image from Quay",
|
||||
PR #25, 2026-09-15). A future minio **version** bump still requires the AIStor product-line
|
||||
decision — quay.io only re-hosts the frozen community line.
|
||||
- nginx 1.31.x (1.31.0, 1.31.1) contains multiple security CVE fixes; upgrade is recommended.
|
||||
- nginx 1.31.0 breaking change: HTTP/2 and HTTP/3 requests with Connection/Proxy-Connection/Keep-Alive/Transfer-Encoding/Upgrade headers are now rejected (affects proxied HTTP/2 backends).
|
||||
|
||||
@@ -6,6 +6,23 @@
|
||||
| postgres | postgres | https://github.com/postgres/postgres | https://www.postgresql.org/docs/release/ |
|
||||
|
||||
## Standing notes
|
||||
- **2026-09-18 re-check** (Mattermost release-policy docs; GitHub release; Docker Hub; vendor
|
||||
security feed): **11.7 ESR line UNCHANGED** — newest patch **11.7.11** (released 2026-09-15,
|
||||
"Low to High severity level security fixes"; Docker Hub tag pushed 2026-09-15). v10.11 ESR
|
||||
remains EXPIRED (2026-08-15); 10.11.24 sits on the dead line — do NOT target. This run
|
||||
extended open PR #2 **11.7.10 → 11.7.11** (branch tip 5696f0b4). Direct `--chaos` deploy on
|
||||
cc-ci exercised the full operator path (deploy at 10.11.22 → seed admin user via API →
|
||||
upgrade to 11.7.11): schema migrations ran on boot, **seeded user data survived** (same
|
||||
id/roles, login 200), ping 200 with x-version-id 11.7.11; `!testme` **GREEN**, drone 1373
|
||||
(level 5: install/upgrade/backup/restore/custom all pass, clean teardown). Vendor feed: **6
|
||||
server advisories fixed in 11.7.11** (MMSA-2026-00711 Low; -00761/-00763/-00764/-00765/-00773
|
||||
Medium, all affecting 11.7.x <= 11.7.10; CVE ids pending 30-day disclosure, due 2026-10-15).
|
||||
**CVE-2026-13426 re-adjudicated FIXED** for the 10.11.22 → 11.7.11 window (fresh GitHub
|
||||
compare: fix commit 3321db82 is an ancestor of v11.7.11 — ahead/behind 0 — and diverged from
|
||||
v10.11.22) → window CVE count **14** (13 deterministic + 1 adjudicated). postgres **15-alpine
|
||||
HELD** (DB-major out of scope). Gotchas learned: `abra app new` RESETS the recipe checkout to
|
||||
main (re-checkout the branch before a `--chaos` deploy), and `abra app new --domain` does NOT
|
||||
write DOMAIN into the app .env (edit the .env + redeploy to fix traefik routing).
|
||||
- **2026-09-11 re-check** (Mattermost Server Releases docs; endoflife.date/api/mattermost.json;
|
||||
GitHub releases + module tags): **11.7 ESR line UNCHANGED** — newest patch still **11.7.10**
|
||||
(2026-08-26; no 11.7.11; v11.11.0 is still a GitHub **prerelease**, innovation). **10.11 ESR
|
||||
|
||||
@@ -205,3 +205,37 @@
|
||||
2.36.0 Array.merge→mergeIntoObject + workflow-tags API migration; 2.37.0 "Any workflow" caller-policy
|
||||
deprecation + JSON content-type on decorator body routes + binary-data endpoint adapt; 2.39.0
|
||||
workflow-version endpoint deprecation. Recommended release: `-y` (feature minor).
|
||||
- 2.38.7 (2026-09-11, patch): 3 core fixes (MCP toolkit members on workers; consent-grant reuse;
|
||||
trust the CA from GIT_SSL_CAINFO for source-control HTTPS remotes).
|
||||
- 2.39.3 (2026-09-11, Pre-release): 1 core fix (GIT_SSL_CAINFO trust — 2.38.7 backport).
|
||||
- 2.39.4 (2026-09-11, Pre-release): 1 core fix (sandbox npm install flags by provider).
|
||||
- 2.39.5 (2026-09-14, stable line): editor fix (revoke end-user credentials) + feature
|
||||
(instance-report log streaming events).
|
||||
- 2.39.6 (2026-09-16, stable line): core fix (Gateway credits eligibility) + features (**v3
|
||||
breaking-change rule for the storage-directory rename**; **warn on deprecated N8N_DB_PING_TIMEOUT
|
||||
env var** — recipe does not set it).
|
||||
- 2.39.7 (2026-09-17, stable line; **`stable` tag points here** — the stable badge moved to the
|
||||
2.39.x line): core breaking-change-detection resilience; editor numeric credential inputs.
|
||||
- 2.40.0 (2026-09-15, Pre-release, feature minor — large): Microsoft Dataverse core node; AI Agent
|
||||
forced first-iteration tool calls; Git-based promotion model (selective promote, timestamped
|
||||
branches, promotion entities/DTOs); **workflow publication service enabled by default**;
|
||||
ephemeral sandboxes; agent episodic memory; ai_preference table; HTTP Request
|
||||
PROPFIND/MKCOL/MOVE/COPY/REPORT; Grist OAuth2; MS Teams online-meeting/channel-message ops; v3
|
||||
migration-report rules (storage-directory rename, preExecute) + "chat hub goes away" warning;
|
||||
N8N_DB_PING_TIMEOUT deprecation warning; secret-redaction consolidation; queue-mode execution
|
||||
timeout for stuck jobs; ~150 core/editor/node bugfixes. **No breaking compose/env/migration
|
||||
changes; no N8N_* renames.**
|
||||
- 2.40.1 (2026-09-16, Pre-release): GitHub release body EMPTY (maintenance republish, no listed
|
||||
changes).
|
||||
- 2.40.2 (2026-09-17, Pre-release): core breaking-change-detection resilience + editor numeric
|
||||
credential inputs (the 2.39.7 pair on the 2.40 line). Docker Hub manifest verified active.
|
||||
- 2.40.3 (2026-09-18, Pre-release): core "repair data-encryption keys stored as the raw instance
|
||||
key" + editor blank-workflow-preview fix. Published the morning of the 2026-09-18 run — newer
|
||||
than the operator's snapshot (which named 2.40.2 as newest); NOT taken that run (operator's
|
||||
explicit target 2.40.2), flagged in the plan/PR as a trivial follow-up bump.
|
||||
- 2026-09-18 run: PR #8 (branch upgrade-ef0dd56, at 2.39.2 since 2026-09-11, !testme GREEN then)
|
||||
extended **2.38.4 → 2.40.2** on upstream main tip 0b436ec (still 3.5.0+2.38.4 — upstream has not
|
||||
started the bump). Stable badge on 2.39.7; 2.40.x pre-release per precedent. Rolling upgrade
|
||||
safe (sqlite, TypeORM auto-migrate). Operator flags unchanged (API-caller-level deprecations,
|
||||
encryption-key rework informational, N8N_DB_PING_TIMEOUT warn-only, recipe doesn't set it).
|
||||
2.40.3 exists (see above) — flagged, not taken. Recommended release: `-y`.
|
||||
|
||||
@@ -92,15 +92,13 @@
|
||||
User = "loops"; Group = "users";
|
||||
WorkingDirectory = "/srv/cc-ci";
|
||||
# Optional per-run overrides for backend/model (LOOP_BACKEND, LOOP_MODEL, OPENCODE_SHARE,
|
||||
# UPGRADER_ARGS, …). The leading "-" makes it optional: absent file → claude/sonnet defaults.
|
||||
# Current config (as of 2026-08-16): the upgrader + report run on tinfoil/deepseek-v4-pro
|
||||
# (LOOP_MODEL + REPORT_MODEL in the env file); the hourly SUPERVISOR stays on glm-5.2
|
||||
# (SUPERVISOR_MODEL defaults to opencode-go/glm-5.2 in launch-supervisor.py, NOT overridden
|
||||
# here). Subagents bind deepseek via the cc-ci repo's opencode config. LOOP_TIER=zen is kept
|
||||
# so the tier check passes; the watchdog's usage-limit probe sends the deepseek model name to
|
||||
# the zen endpoint, which returns 200 (not 429) → resume immediately (correct: tinfoil has no
|
||||
# rolling usage limit to wait out). No rebuild needed to switch — the env file is read at each
|
||||
# timer fire. Holds no secrets (the tinfoil API key lives in the opencode config / auth.json).
|
||||
# UPGRADER_ARGS, …). The leading "-" makes it optional: absent file → the in-code defaults,
|
||||
# which are opencode + LOOP_TIER=go (the only tier with a key on this host; ZEN models are
|
||||
# dead here — the in-code default is 'go' for exactly that reason, see launch-upgrader.py).
|
||||
# Current config: LOOP_TIER=go with LOOP_MODEL / REPORT_MODEL / SUPERVISOR_MODEL all
|
||||
# opencode-go/glm-5.3-flash (upgrader, report, supervisor). Subagents bind deepseek via the
|
||||
# cc-ci repo's opencode config. No rebuild needed to switch — the env file is read at each
|
||||
# timer fire. Holds no secrets (the API key lives in the opencode config / auth.json).
|
||||
EnvironmentFile = "-/srv/cc-ci/upgrader.env";
|
||||
};
|
||||
environment = { HOME = "/home/loops"; };
|
||||
|
||||
Submodule references/recipe-maintainer updated: acd5cfbd95...6698723a77
@@ -78,9 +78,45 @@ not the test server — so it can run either way. Probe, then follow that branch
|
||||
4. Everything else in the canonical script (origin/dev remote setup, release recommendation,
|
||||
emitted next-steps) is identical.
|
||||
|
||||
Every time the branch was prepared **here**, remember it exists only on this host — the
|
||||
operator's machine must fetch it first. Always emit this **step 0** before the push step
|
||||
(anonymous public fetch, no credentials needed):
|
||||
|
||||
```
|
||||
# 0. On a machine WITHOUT the branch pre-fetched, get it from the autonomic mirror
|
||||
# (fetch by URL — works regardless of what the local remotes are named):
|
||||
cd <local checkout of the recipe>
|
||||
git fetch https://git.autonomic.zone/recipe-maintainers/<recipe>.git +refs/pull/<PR_NUM>/head:refs/heads/<HEAD_REF>
|
||||
git checkout <HEAD_REF>
|
||||
```
|
||||
|
||||
If the operator's checkout does NOT yet have the mirror remote, emit once before the fetch:
|
||||
|
||||
```
|
||||
git remote add gitea https://git.autonomic.zone/recipe-maintainers/<recipe>.git
|
||||
```
|
||||
|
||||
In both modes the final output is a set of commands for the operator to run on a machine **with
|
||||
push access to `git.coopcloud.tech`** — always print them, even when everything local is
|
||||
already prepared.
|
||||
|
||||
**Verify the merge-base BEFORE rebasing — bases can be traps.** Upstream repos can
|
||||
carry a stray, divergent `main` ALONGSIDE the real `master` base branch. Rebase targets must be
|
||||
chosen by evidence: `git ls-remote origin | grep -E 'refs/heads/(main|master)$'`, then
|
||||
`git merge-base <head_ref> origin/<candidate>` — the correct base is the one where the upgrade
|
||||
branch's merge-base is its tip's parent (i.e. `<head_ref>` is already directly on that line —
|
||||
then NO cherry-pick is needed, the raw branch compares clean). Cherry-picking onto the wrong
|
||||
base produces the very messy 3-dot compare this guard exists to prevent (real case: gitea —
|
||||
first "rebase" landed on a stray `main` and had to be redone onto `master`).
|
||||
|
||||
**Branch-name mismatch mirror vs upstream.** The mirror and upstream can use different base-branch
|
||||
names, and a mirror can even carry a stray, divergent `main` alongside the real base (gitea: mirror
|
||||
PR against `main`, upstream `master`; keycloak PR #9: base `main`, both mirror and upstream on
|
||||
`master`). The canonical script now detects the upstream default branch by evidence (`git
|
||||
ls-remote --symref origin HEAD`, then the git.coopcloud.tech API, then the PR base with a warning)
|
||||
and uses it for the compare URL AND the post-merge release steps — never hardcode `main`, and when
|
||||
it prints the base-mismatch warning, confirm the upgrade branch actually sits on that line
|
||||
(`git merge-base` against both candidates) before emitting the push step.
|
||||
""",
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user