# Enrolling a recipe under cc-ci (D5) Adding a recipe is a small, repeatable, **no-harness-surgery** operation: ## 1. Make the recipe available on the mirror Recipes under test live on the private mirror `git.autonomic.zone/recipe-maintainers/`, synced from upstream `git.coopcloud.tech`. If not yet mirrored, mirror it (abra fetch + push to the org) — see the recipe mirror+PR flow (plan §4.1). A recipe may ship its own `tests/` dir in its repo; those are discovered and run against the live app (D4 — see below). ## 2. Add the per-recipe test tree in this repo ``` tests// ├── recipe_meta.py # optional per-recipe harness config (see below) ├── install_steps.sh # optional custom install-steps hook (pre-deploy setup) ├── test_install.py # optional install overlay (else the generic install tier runs) ├── test_upgrade.py # optional upgrade overlay (else the generic upgrade tier runs) ├── test_backup.py # optional backup overlay (else the generic backup tier runs) └── test_restore.py # optional restore overlay (else the generic restore tier runs) ``` **A recipe is testable with ZERO config:** with no overlay files, the **generic lifecycle suite** runs (install/upgrade/backup/restore) against a single shared deployment — see `docs/testing.md` for the full model (tiers, deploy-once, override-vs-extend, precedence, the install-steps hook). The per-recipe dir only holds the bits where the recipe needs *more* than the generic. To add recipe-specific coverage, drop a `tests//test_.py` **overlay** (it OVERRIDES the generic for that op; absent ⇒ generic runs). Overlays are **assertion-only** against the shared live deployment (the `live_app` fixture; they never deploy), and reuse the generic op + serving check by composition (`from harness import generic; generic.do_upgrade(...)` etc.), adding recipe-specific assertions. Copy an existing overlay (`tests/custom-html/` simple/volume marker; `tests/keycloak/` admin-API; `tests/matrix-synapse/` `db`-service psql marker). **Do not edit the shared `tests/conftest.py` / `runner/harness/` to add a recipe** — set per-recipe config in `recipe_meta.py`: ```python HEALTH_PATH = "/realms/master" # path that returns a healthy status (default "/") HEALTH_OK = (200,) # acceptable status codes (default 200/301/302) DEPLOY_TIMEOUT = 600 # seconds for services to converge (default 600) HTTP_TIMEOUT = 600 # seconds for the app to answer (default 300) BACKUP_CAPABLE = True # override backup-capability auto-detect (default: scan compose) EXTRA_ENV = {"KEY": "value"} # or EXTRA_ENV(domain) -> dict; extra .env keys set at deploy ``` Useful `harness.lifecycle` helpers for overlays: `http_get`, `http_fetch`, `http_body`, `exec_in_app` (use this for data markers — volume/DB, robust to the serving layer); the lifecycle ops themselves come from `harness.generic` (`assert_serving`, `do_upgrade`, `do_backup`, `do_restore`). The harness forces `LETS_ENCRYPT_ENV=""` (no ACME), a unique short domain per run, and guarantees teardown. ## 3. Recipe-local tests (D4) If the recipe's own repo contains `tests/test_*.py`, the runner snapshots them right after fetch and runs them against the **live deployment** as a `recipe-local` stage. Contract: those tests receive env `CCCI_BASE_URL` (e.g. `https://.ci.commoninternet.net/`) and `CCCI_APP_DOMAIN`. ## 4. Add the repo to the bridge poll list The trigger is **polling** (primary): add the repo's full name to the comment-bridge `POLL_REPOS` csv (`nix/modules/bridge.nix`) and `nixos-rebuild switch`. The bridge then polls that repo's open PRs every 30s and fires a run on a new `!testme` comment from an authorized org member. This needs only **read + comment** access — no webhook, no repo-admin. `!testme` on a PR runs install/upgrade/backup + any recipe-local tests, and reports back to the PR. ### Optional: lower-latency webhook (admin-registered) Polling already satisfies D1 (<60s). For lower latency an **admin** may *optionally* register a Gitea `issue_comment` webhook (the bot does **not** self-register one — that needs repo-admin): - URL `https://ci.commoninternet.net/hook`, content-type `application/json`, event `Issue Comment`, secret = the shared webhook HMAC (`secrets/secrets.yaml` → `webhook_hmac`). - The Gitea instance must allow the host (admin: add `ci.commoninternet.net` to the `[webhook] ALLOWED_HOST_LIST`). The webhook and poller are deduped by comment id, so a comment seen by both fires only once. ## Run locally ```sh RECIPE= PR= REF= SRC=recipe-maintainers/ \ STAGES=install,upgrade,backup cc-ci-run runner/run_recipe_ci.py ```