commit f283a371bb1d4dbabada153fc452404cd31764fc Author: autonomic-bot Date: Tue Jun 16 20:18:24 2026 +0000 recipe-maintainer: public snapshot (secrets + deployment plans removed, single commit) Sanitized single-commit public mirror of recipe-maintainer. - Removed test-ssh/.testenv (live creds); added test-ssh/.testenv.example placeholders. - Removed plans/ and planned-updates/ (deployment-planning docs) so no client/ deployment domains appear in the public repo. - All other secret stores were already gitignored. - docs.coopcloud.tech retained as a submodule (public upstream). diff --git a/.claude/commands/includes/guidelines.md b/.claude/commands/includes/guidelines.md new file mode 100644 index 0000000..5d07825 --- /dev/null +++ b/.claude/commands/includes/guidelines.md @@ -0,0 +1,45 @@ +## Guidelines + +Read and follow these guidelines for all recipe operations. + +### Preserving local recipe changes + +Before running `abra recipe fetch --force`, always check for uncommitted changes first: + +``` +git -C ~/.abra/recipes/ status --short +``` + +- **If the working tree is clean:** proceed with `abra recipe fetch --force` to pull the latest upstream. +- **If there are uncommitted changes:** do NOT fetch. Log that the recipe has local modifications and that you are using the local checkout as-is. Use `--chaos` on any subsequent `abra app` commands so they pick up the local state instead of requiring a committed version. + +This matters because `--force` overwrites the entire local checkout, destroying any in-progress work. + +### Recipe version format + +Recipe versions use the format `+v`, for example `0.2.6+v4.5.0`. The `v` prefix before the upstream version is always present, even if the upstream image tag uses a different prefix (e.g. CryptPad's Docker tag `version-2025.9.0` becomes `+v2025.9.0` in the recipe version). + +This version string appears in three places that must stay in sync: +1. The `coop-cloud.${STACK_NAME}.version` deploy label in `compose.yml` +2. The annotated git tag on the release commit +3. The `TYPE=:` line in the app's `.env` file on the server + +Co-op Cloud requires **annotated tags** (not lightweight tags). Always use `git tag -a` with a `-m` message: + +``` +git tag -a "0.5.0+v2026.2.0" -m "chore: publish 0.5.0+v2026.2.0 release" +``` + +### Saving secrets locally + +When generating secrets for an app, always use `--machine` to get machine-readable output and save it to `recipe-info//secrets.json`: + +``` +abra app secret generate --all --machine > recipe-info//secrets.json +``` + +This keeps a local record of the generated secrets (e.g. admin passwords needed for initial login). The `--machine` flag outputs JSON instead of a human-readable table. + +### Active test instance + +The current active test instance is set by `default_instance` in `settings.toml` at the workspace root. Read this value directly to determine which instance to operate on. The instance's server and domain_suffix are in the `[instances.]` section. Domain for any recipe is `.`. diff --git a/.claude/commands/includes/logging.md b/.claude/commands/includes/logging.md new file mode 100644 index 0000000..7cb20e0 --- /dev/null +++ b/.claude/commands/includes/logging.md @@ -0,0 +1,14 @@ +## Logging + +Throughout this entire operation, maintain a detailed log of everything that happens. At the **end** of the operation, write the full log to a file in the `logs/` directory (create it if it doesn't exist). + +**Log file path:** `logs/--.md` — use the skill name from this file's name (e.g. `recipe-check`, `recipe-deploy`), the recipe name from `$ARGUMENTS` (omit if there is no recipe argument), and today's date. + +The log file must include: +- A header with the skill name, recipe name (if applicable), and timestamp +- Every shell command that was run, in fenced code blocks +- The full output of each command (truncate excessively long output but keep enough to be useful) +- Any decisions made, errors encountered, or notable observations +- The final summary/result + +Format the log as readable markdown with clear section headers for each step. diff --git a/.claude/commands/init-instance.md b/.claude/commands/init-instance.md new file mode 100644 index 0000000..5f86c60 --- /dev/null +++ b/.claude/commands/init-instance.md @@ -0,0 +1,116 @@ +--- +description: Deploy all maintained recipes to the active test instance from scratch +allowed-tools: [Bash, Read, Write, Edit, Glob, Grep, WebFetch] +--- + +# Init Instance + +Deploy all maintained recipes to the active test instance in dependency order. Apps that already exist are deployed as-is. Apps that don't exist yet are created following their `setup.md`. + +Read and follow the instructions in `.claude/commands/includes/logging.md`. +Read and follow the instructions in `.claude/commands/includes/guidelines.md`. + +## Steps + +### 1. Determine the active instance + +Run `python3 scripts/get_test_instance.py` to get SERVER and INSTANCE. Read `settings.toml` to get `domain_suffix`. + +### 2. Discover all recipes + +Glob `recipe-info/*/recipe.toml`. For each, read the file to get: +- `name` — the recipe name +- `[dependencies].requires` — list of recipe dependencies (may be empty or absent) + +### 3. Topological sort — build deployment tiers + +Group recipes into tiers based on their `[dependencies].requires`: + +- **Tier 1** (no dependencies): recipes with empty or missing `requires` +- **Tier 2** (depends on tier 1): recipes whose `requires` list only contains tier 1 recipes + +Present the deployment order to the user and **confirm before proceeding**. + +### 4. Deploy each recipe in dependency order + +Process recipes **one at a time** within each tier. For each recipe: + +#### a. Free memory + +Run context reset to undeploy everything except infrastructure and this recipe's dependencies: +```bash +python3 scripts/context_reset.py --recipe +``` + +#### b. Compute domain + +The domain is `.`. + +#### c. Ensure dependencies are deployed + +If the recipe has dependencies (from recipe.toml), check that each dependency is deployed: +- Check if `~/.abra/servers//.env` exists. +- If a dependency's env file does NOT exist, follow its `setup.md` first before continuing with the current recipe. +- If the env file exists, deploy the dependency: `abra app deploy --chaos --force --no-input` + +#### d. Check if this app already exists + +Check for the env file: `~/.abra/servers//..env` + +#### e. If the app does NOT exist — create it following setup.md + +Read `recipe-info//setup.md` and follow every step. Replace ``, ``, and any other placeholders with the actual values from step 1. + +This covers: +- `abra app new` with correct flags +- Secret generation (save to `recipe-info/testsecrets/`) +- Any env file overrides needed before deploy +- `abra app deploy` +- Post-deploy steps (migrations, buckets, etc.) +- SSO integration setup script if applicable +- Redeploy after SSO if needed + +#### f. If the app already exists — just deploy it + +Do NOT recreate the app, remove secrets, or regenerate secrets. Simply deploy what's there: + +```bash +abra app deploy --chaos --force --no-input +``` + +If the deploy fails (e.g. missing secrets, bad env config, services won't start), then the existing setup is broken. In that case, clean up and start fresh: +1. `abra app undeploy --no-input` +2. `abra app volume remove --force --no-input` +3. Remove secrets: `abra app secret remove --all --no-input` (TTY-wrapped) +4. Remove the env file +5. Follow setup.md from scratch (step e) + +If the recipe has an SSO setup script and SSO credentials file doesn't exist yet, also run the SSO setup and redeploy. + +#### g. Sync secrets locally + +After deploy, if secrets are NOT already saved locally in `recipe-info/testsecrets/`, sync them from the running containers: + +```bash +python3 scripts/sync_secrets.py --recipe +``` + +Do NOT remove or regenerate secrets on the server. This only reads secrets from running containers and saves them locally. + +#### h. Log result + +Log PASS if deploy succeeded and health check passes, FAIL otherwise. + +### 5. Summary table + +After all recipes are processed, print a summary: + +| Recipe | Tier | Status | Notes | +|--------|------|--------|-------| +| keycloak | 1 | PASS/FAIL | ... | +| authentik | 1 | PASS/FAIL | ... | +| ... | ... | ... | ... | + +### 6. Suggest testing + +After all recipes are deployed, suggest the user run `/recipe-test ` for each recipe to verify everything is working. List the recipes in the same dependency order used for deployment. diff --git a/.claude/commands/intro.md b/.claude/commands/intro.md new file mode 100644 index 0000000..28e448e --- /dev/null +++ b/.claude/commands/intro.md @@ -0,0 +1,74 @@ +--- +description: Explain what this project is and how to get started +allowed-tools: [Read, Glob] +--- + +Present this entire introduction to the user verbatim — do NOT summarize or condense it. + +# Co-op Cloud Recipe Toolkit — Introduction + +This repository is an AI-assisted toolkit for maintaining [Co-op Cloud](https://coopcloud.tech) recipes. It runs inside a container with `~/.abra` bind-mounted, providing an isolated environment where the `abra` CLI operates normally without access to the host's SSH keys or abra configuration. The only credentials available are the test SSH keys in `test-ssh/`, limiting all deployment operations to the designated test server. + +## What it does + +The toolkit wraps `abra` and other tools into slash-command skills that automate the full recipe maintenance lifecycle: checking for upstream upgrades, planning and applying updates, deploying to a test server, running tests, managing backups, reviewing recipes against best practices, and tagging releases. + +## Repository structure + +- **`.claude/commands/`** — Skill definitions (slash commands). This is where all the automation lives. +- **`recipe-info/`** — Per-recipe data organized by recipe name: upstream release note URLs, test instance environment files, and test scripts. +- **`planned-updates/`** — Upgrade reports and summaries generated by `/recipe-upgrade-plan` and `/recipe-upgrade-apply`. +- **`plans/`** — Project planning documents. +- **`test-ssh/`** — SSH config and keys for the test server where recipe instances are deployed. +- **`settings.toml`** — Defines which test server instances are available and which is the current default. +- **`maintained-recipes.md`** — List of recipes this toolkit actively maintains. Used by `/recipe-overview` and `/recipe-test-all`. +- **`learnings.md`** — Hard-won lessons and `abra` CLI quirks discovered during operation. +- **`docs.coopcloud.tech/`** — Local copy of the Co-op Cloud documentation; used as reference for recipe structure, deployment patterns, and platform conventions. +- **`lib/`** — Python helper library used by test scripts and automation (SSH, abra wrappers, secrets management). +- **`.opencode/`** — OpenCode stubs that point back to `.claude/commands/` so skills work in both Claude Code and OpenCode. + +## Available skills + +### Recipe lifecycle +- **`/recipe-overview`** — Check all maintained recipes, see what needs upgrading +- **`/recipe-check `** — Check a single recipe for available upstream upgrades +- **`/recipe-upgrade-plan `** — Research release notes and create a detailed upgrade plan +- **`/recipe-upgrade-apply `** — Apply the plan: update images, deploy, test, commit, and tag +- **`/recipe-init `** — Bootstrap a new recipe from scratch (fetch, create test instance, deploy) +- **`/recipe-new-tag `** — Bump the version and create an annotated git tag +- **`/recipe-review `** — Audit a recipe against Co-op Cloud best practices + +### Deploying and testing +- **`/recipe-deploy `** — Deploy local recipe checkout to the test server (chaos mode) +- **`/recipe-test `** — Run all tests for a recipe +- **`/recipe-test-new `** — Test a fresh install from scratch +- **`/recipe-test-update `** — Test upgrading an existing deployment +- **`/recipe-test-backup `** — Test the backup/restore cycle +- **`/recipe-test-all`** — Run tests for every maintained recipe + +### Infrastructure and utilities +- **`/init-instance`** — Deploy all maintained recipes to the test server +- **`/test-context-reset`** — Undeploy all apps except traefik +- **`/switch-default-instance`** — Switch between test servers +- **`/sync-secrets`** — Sync secrets from the test server locally +- **`/opencode-sync`** — Sync Claude skills to OpenCode format + +## Recommended workflow + +1. **Daily check-in:** `/recipe-overview` to see what needs attention +2. **Upgrade a recipe:** `/recipe-check` → `/recipe-upgrade-plan` → review the plan → `/recipe-upgrade-apply` +3. **Day-to-day development:** edit a recipe locally → `/recipe-deploy` → `/recipe-test` +4. **Push when satisfied:** `cd ~/.abra/recipes/ && git push && git push --tags` + +### Working with a recipe not yet in recipe-info? + +Run `/recipe-init ` to set up tests for that recipe and a deployment to your local test instance. + +### Developing a new recipe that doesn't exist yet? + +Run `/new-recipe-guide` for detailed instructions on developing a new recipe from scratch. + +## Next steps + +- Run **`/test-setup`** to verify your environment is configured correctly (settings, SSH, abra CLI, connectivity). +- Run **`/setup-sandbox`** for guidance on setting up a sandboxed Docker environment to run Claude Code with this project — see also `sandbox/` for a reference implementation. diff --git a/.claude/commands/new-recipe-guide.md b/.claude/commands/new-recipe-guide.md new file mode 100644 index 0000000..fb56428 --- /dev/null +++ b/.claude/commands/new-recipe-guide.md @@ -0,0 +1,55 @@ +--- +description: Guide for developing a new Co-op Cloud recipe from scratch +allowed-tools: [Read, Glob, Grep] +--- + +# New Recipe Development Guide + +This guide walks through how to develop a new Co-op Cloud recipe from scratch using this toolkit. + +## Step 1: Gather references + +Download any relevant references into `references/` — upstream documentation, example Docker Compose files, configuration guides, environment variable docs, etc. The more context available, the better the recipe will be. + +## Step 2: Study existing patterns + +Before writing anything, consult these resources to understand how Co-op Cloud recipes are structured: + +- **`docs.coopcloud.tech/`** — The Co-op Cloud documentation, especially the recipe structure and deployment conventions +- **`~/.abra/recipes/`** — Other existing recipes, particularly ones similar to the app you're packaging (e.g. if your app uses PostgreSQL, look at how other recipes handle it) +- **`learnings.md`** — Hard-won lessons about `abra` CLI quirks and operational patterns + +## Step 3: Develop the recipe + +Tell Claude to consult the references you downloaded, along with `docs.coopcloud.tech/`, relevant existing recipes in `~/.abra/recipes/`, and `learnings.md`, in order to develop the new recipe. + +A recipe typically includes: + +- **`compose.yml`** — Service definitions with Traefik labels, healthchecks, deploy config, secrets, and environment variables +- **`.env.sample`** — Default environment variable values +- **`README.md`** — Recipe metadata (category, status, upstream URL, etc.) +- **`abra.sh`** — Optional post-deploy hooks + +## Step 4: Create tests + +Tell Claude to create tests in `recipe-info//tests/` to verify the recipe works correctly. Tests should include: + +- **Basic health tests** — Verify the app is reachable and responding +- **OIDC integration tests** — If the app supports OIDC/SSO (e.g. via Authentik or Keycloak), test that the integration works +- **1–2 application-specific tests** — Test core functionality specific to the app (e.g. can create a document, can upload a file, API responds correctly) + +## Step 5: Deploy and iterate + +Use the toolkit skills to deploy and test iteratively: + +1. **`/recipe-deploy `** — Deploy your local changes to the test server +2. **`/recipe-test `** — Run the tests you created +3. Fix any issues, repeat until all tests pass + +## Step 6: Review and tag + +Once tests pass: + +1. **`/recipe-review `** — Audit against Co-op Cloud best practices +2. Fix any issues the review identifies +3. **`/recipe-new-tag `** — Create the first version tag diff --git a/.claude/commands/opencode-sync.md b/.claude/commands/opencode-sync.md new file mode 100644 index 0000000..616dadb --- /dev/null +++ b/.claude/commands/opencode-sync.md @@ -0,0 +1,67 @@ +--- +description: Ensure every Claude skill has a corresponding OpenCode skill alias +allowed-tools: [Read, Write, Glob, Grep, Bash] +--- + +# Sync Claude skills to OpenCode + +Ensure that every Claude command has a corresponding OpenCode skill and command that delegates to it. + +## Steps + +1. **List all Claude commands** — Glob `.claude/commands/*.md` (excluding `opencode-sync.md` itself) and `.claude/commands/includes/*.md`. + +2. **List existing OpenCode skills** — Glob `.opencode/skills/*/SKILL.md` and `.opencode/commands/*.md`. + +3. **For each Claude command**, determine the OpenCode skill name: + - For `.claude/commands/.md` → skill name is `` + - For `.claude/commands/includes/.md` → skill name is `recipe-` (following the existing convention, e.g. `guidelines` → `recipe-guidelines`) + +4. **Read each Claude command's YAML frontmatter** to extract: + - `description` — reuse as the OpenCode description + - `argument-hint` — use to generate the argument context line + +5. **For each missing OpenCode skill**, create `.opencode/skills//SKILL.md`: + +```markdown +--- +name: +description: +--- +Read and follow the full instructions in `.claude/commands/.md`. + +``` + +The argument context line should describe what arguments are expected based on the `argument-hint`: +- If `argument-hint` contains `recipe-name` → `The recipe name will be provided by the user or calling agent.` +- If `argument-hint` contains `b1cc|t1cc` → `The instance name will be provided by the user or calling agent.` +- If there is an `argument-hint` but no specific pattern matched → `Arguments will be provided by the user or calling agent.` +- If there is no `argument-hint` → omit the argument context line entirely + +6. **For each missing OpenCode command**, create `.opencode/commands/.md`: + +```markdown +--- +description: +--- +Load the `` skill and execute it for $ARGUMENTS. +``` + +7. **Detect orphaned OpenCode skills** — For each existing OpenCode skill/command, check whether it references a Claude command (by reading the `SKILL.md` body for the `.claude/commands/...` path). If the referenced Claude command no longer exists: + - Check if the content/description closely matches a current Claude command that lacks an OpenCode equivalent (i.e., it was likely renamed). + - If a rename is detected: delete the old OpenCode skill directory and command file, then create new ones under the correct name. + - If no rename match is found: delete the orphaned OpenCode skill directory and command file (it references a dead Claude command). + +8. **Update stale descriptions** — For each existing OpenCode skill/command that maps to a valid Claude command, read the Claude command's current `description`. If the OpenCode description differs, update it to match. + +9. **Report results** — Print a summary of: + - Skills created (new) + - Skills renamed (old name → new name) + - Skills deleted (orphaned, no rename match) + - Skills updated (description changed) + - Skills unchanged (already in sync) + +## Important + +- Every Claude command must have exactly one corresponding OpenCode skill — no exceptions, including this `opencode-sync` command itself. +- The includes (`guidelines.md`, `logging.md`) use the `recipe-` prefix convention for their OpenCode skill names. diff --git a/.claude/commands/recipe-check.md b/.claude/commands/recipe-check.md new file mode 100644 index 0000000..cb9777c --- /dev/null +++ b/.claude/commands/recipe-check.md @@ -0,0 +1,58 @@ +--- +description: Fetch a Co-op Cloud recipe and check for available upgrades +argument-hint: +allowed-tools: [Bash, Read, Write, Glob, Grep, WebFetch, WebSearch] +--- + +# Recipe Check + +Fetch the latest version of a Co-op Cloud recipe and check for available upgrades. + +The recipe name is: $ARGUMENTS + +Read and follow the instructions in `.claude/commands/includes/logging.md`. +Read and follow the instructions in `.claude/commands/includes/guidelines.md`. + +## Steps + +1. **Fetch the recipe** — check for uncommitted local changes first (see guidelines). If clean, run `abra recipe fetch $ARGUMENTS --force`. If there are local changes, skip the fetch and note that you're using the local checkout. + +2. **Show current released versions**: + ``` + abra recipe versions $ARGUMENTS -m + ``` + +3. **Check for available upgrades** (machine-readable, non-interactive): + ``` + abra recipe upgrade $ARGUMENTS -m -n + ``` + +4. **Lint the recipe** to surface any config issues: + ``` + abra recipe lint $ARGUMENTS -C + ``` + +5. **Look up upstream release notes**: + - Check if `recipe-info/$ARGUMENTS/upstream.md` exists in the workspace. + - If it exists, read it to get the release notes URLs for each image/service. + - If it does NOT exist, try to discover the upstream project and release notes URLs: + - Read the recipe's `compose.yml` to identify all images. + - For each image, search for its GitHub repository and releases page. + - Create `recipe-info/$ARGUMENTS/upstream.md` with the discovered URLs (follow the format of existing upstream.md files in sibling recipe directories). + - Also create the `recipe-info/$ARGUMENTS/tests/` directory if it doesn't exist. + - For any services that have available upgrades, fetch the release notes page and summarise what changed between the current version and the upgrade version(s). + - Pay special attention to and explicitly call out: + - **Breaking changes** — API removals, renamed/removed config options, changed defaults, dropped support for older runtimes/dependencies + - **Required migration steps** — database migrations, data format changes, manual upgrade procedures + - **Config changes needed by the operator** — new required environment variables, changed variable names/formats, new secrets, changed ports or volume paths, deprecated settings that will stop working + - **Dependency version requirements** — e.g. "now requires PostgreSQL 15+", "minimum Redis 6.2" + - If any of the above are found, present them in a clearly marked "**Operator Action Required**" section per service, separate from the general changelog summary. Use warnings/bold to make these impossible to miss. + +6. **Summarise the results** for the user: + - Current version(s) of the recipe + - Any available image tag upgrades + - **Operator Action Required** items first (breaking changes, config changes, migrations) — prominently highlighted + - For each upgrade: a summary of what changed (from release notes), with a link to the full release notes + - Any lint warnings or errors + +7. **Suggest next steps** — if upgrades are available, suggest the user run `/recipe-upgrade-plan $ARGUMENTS` to create a detailed upgrade plan. diff --git a/.claude/commands/recipe-create-pr.md b/.claude/commands/recipe-create-pr.md new file mode 100644 index 0000000..eb9083a --- /dev/null +++ b/.claude/commands/recipe-create-pr.md @@ -0,0 +1,191 @@ +--- +description: Push local recipe commits to git.autonomic.zone and open a PR against an upstream-synced main branch +argument-hint: +allowed-tools: [Bash, Read] +--- + +# Recipe Create PR + +Take the local commits made to a recipe (e.g. during `/recipe-upgrade-apply`) and open a pull request on the Gitea instance at `git.autonomic.zone`. The Gitea repo's `main` branch is force-synced from the recipe's upstream `main` so the PR diff shows only the local changes. + +The recipe name is: $ARGUMENTS + +## Prerequisites + +Credentials are read from `test-ssh/.testenv`. The following variables must be set: + +- `GITEA_USERNAME` — bot account on the Gitea instance +- `GITEA_PASSWORD` — bot password +- `GITEA_URL` — Gitea host (e.g. `git.autonomic.zone`) + +Optional: + +- `GITEA_NAMESPACE` — owner under which to create/find repos. Defaults to `recipe-maintainers` (the org whose `recipe-maintainers` team auto-grants access to all repos within it). + +The recipe must be checked out at `~/.abra/recipes/$ARGUMENTS` with at least one commit beyond `origin/main`. + +## Steps + +Run the following script with the recipe name substituted in: + +```bash +#!/usr/bin/env bash +set -euo pipefail + +RECIPE="$ARGUMENTS" +WORKSPACE="/workspace" +RECIPE_DIR="${HOME}/.abra/recipes/${RECIPE}" +TESTENV="${WORKSPACE}/test-ssh/.testenv" + +# --- Load credentials --- +[ -f "${TESTENV}" ] || { echo "ERROR: ${TESTENV} not found"; exit 1; } +set -a; . "${TESTENV}"; set +a +: "${GITEA_USERNAME:?missing in .testenv}" +: "${GITEA_PASSWORD:?missing in .testenv}" +: "${GITEA_URL:?missing in .testenv}" +NAMESPACE="${GITEA_NAMESPACE:-recipe-maintainers}" + +PASS_ENC=$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1],safe=''))" "${GITEA_PASSWORD}") +API="https://${GITEA_URL}/api/v1" +AUTH=(-u "${GITEA_USERNAME}:${GITEA_PASSWORD}") + +# --- Validate recipe checkout --- +[ -d "${RECIPE_DIR}/.git" ] || { echo "ERROR: ${RECIPE_DIR} is not a git repo. Run 'abra recipe fetch ${RECIPE}' first."; exit 1; } +cd "${RECIPE_DIR}" + +# --- Fetch upstream main --- +echo "→ Fetching upstream main from origin..." +git fetch origin main + +# --- Find diverged commits --- +DIVERGED=$(git log --oneline origin/main..HEAD 2>/dev/null || true) +if [ -z "${DIVERGED}" ]; then + echo "ERROR: HEAD has no commits beyond origin/main. Nothing to PR." + exit 1 +fi +echo "→ Local commits to PR:" +echo "${DIVERGED}" | sed 's/^/ /' + +# --- Determine PR branch name from the most recent commit --- +LATEST_MSG=$(git log -1 --pretty=%s HEAD) +if echo "${LATEST_MSG}" | grep -qiE "upgrade to [0-9]"; then + VERSION=$(echo "${LATEST_MSG}" | grep -oiE "upgrade to [0-9][^[:space:]]+" | awk '{print $NF}') + BRANCH="upgrade-${VERSION}" +else + BRANCH="pr-$(date -u +%Y%m%d-%H%M%S)" +fi +echo "→ PR branch: ${BRANCH}" + +# --- Check / create Gitea repo --- +REPO_URL_API="${API}/repos/${NAMESPACE}/${RECIPE}" +STATUS=$(curl -s -o /dev/null -w "%{http_code}" "${AUTH[@]}" "${REPO_URL_API}") + +if [ "${STATUS}" = "404" ]; then + echo "→ Repo ${NAMESPACE}/${RECIPE} does not exist; creating..." + CREATE_BODY=$(python3 -c "import json;print(json.dumps({'name':'${RECIPE}','private':True,'default_branch':'main','auto_init':False}))") + + # Try org namespace first + CREATE_OUT=$(mktemp) + CREATE_STATUS=$(curl -s -o "${CREATE_OUT}" -w "%{http_code}" "${AUTH[@]}" \ + -H "Content-Type: application/json" \ + -X POST "${API}/orgs/${NAMESPACE}/repos" \ + -d "${CREATE_BODY}") + + if [ "${CREATE_STATUS}" != "201" ]; then + echo " ! create under org ${NAMESPACE} returned HTTP ${CREATE_STATUS} — falling back to user namespace ${GITEA_USERNAME}" + cat "${CREATE_OUT}" >&2 + echo "" >&2 + CREATE_STATUS=$(curl -s -o "${CREATE_OUT}" -w "%{http_code}" "${AUTH[@]}" \ + -H "Content-Type: application/json" \ + -X POST "${API}/user/repos" \ + -d "${CREATE_BODY}") + if [ "${CREATE_STATUS}" != "201" ]; then + echo "ERROR: failed to create repo (HTTP ${CREATE_STATUS}):" + cat "${CREATE_OUT}" + rm -f "${CREATE_OUT}" + exit 1 + fi + NAMESPACE="${GITEA_USERNAME}" + fi + rm -f "${CREATE_OUT}" + echo " ✓ created ${NAMESPACE}/${RECIPE}" +elif [ "${STATUS}" = "200" ]; then + echo "→ Repo ${NAMESPACE}/${RECIPE} already exists" +else + echo "ERROR: unexpected HTTP ${STATUS} when checking ${REPO_URL_API}" + exit 1 +fi + +# --- Set up the gitea remote with credentials embedded --- +REMOTE_URL="https://${GITEA_USERNAME}:${PASS_ENC}@${GITEA_URL}/${NAMESPACE}/${RECIPE}.git" +if git remote | grep -qx gitea; then + git remote set-url gitea "${REMOTE_URL}" +else + git remote add gitea "${REMOTE_URL}" +fi + +# --- Force-sync Gitea main with origin/main so the PR diff is clean --- +echo "→ Force-syncing gitea/main from origin/main..." +git push --force gitea "refs/remotes/origin/main:refs/heads/main" + +# --- Push local commits as the PR branch --- +echo "→ Pushing local commits as branch '${BRANCH}'..." +git push --force gitea "HEAD:refs/heads/${BRANCH}" + +# --- Create the PR --- +# NOTE: for an upgrade PR the body (passed in via RECIPE_PR_BODY) MUST link the upstream release notes +# — one explicit line per upgraded image/service, e.g. +# **Upstream release notes:** : +# pulling each URL from recipe-info//upstream.md (between the current → new version). These links +# belong in the PR body itself, NOT only in a side report, so the reviewer sees what changed upstream. +PR_TITLE="${LATEST_MSG}" +if [ -n "${RECIPE_PR_BODY:-}" ]; then + PR_BODY="${RECIPE_PR_BODY}" +else + PR_BODY=$(printf "Local commits on top of upstream main:\n\n%s\n" "$(git log origin/main..HEAD --pretty='- %h %s')") +fi +PR_BODY="${PR_BODY} + +cc @trav @notplants" +PR_PAYLOAD=$(python3 -c " +import json, sys +print(json.dumps({ + 'title': sys.argv[1], + 'body': sys.argv[2], + 'head': sys.argv[3], + 'base': 'main', + 'reviewers': ['trav', 'notplants'], +}))" "${PR_TITLE}" "${PR_BODY}" "${BRANCH}") + +PR_RESPONSE=$(mktemp) +PR_STATUS=$(curl -s -o "${PR_RESPONSE}" -w "%{http_code}" "${AUTH[@]}" \ + -H "Content-Type: application/json" \ + -X POST "${API}/repos/${NAMESPACE}/${RECIPE}/pulls" \ + -d "${PR_PAYLOAD}") + +if [ "${PR_STATUS}" = "201" ]; then + PR_URL=$(python3 -c "import json;print(json.load(open('${PR_RESPONSE}'))['html_url'])") + echo "" + echo "✓ PR created: ${PR_URL}" +elif [ "${PR_STATUS}" = "409" ] || grep -q "pull request already exists" "${PR_RESPONSE}" 2>/dev/null; then + echo "" + echo "ℹ A PR for branch '${BRANCH}' already exists. See:" + echo " https://${GITEA_URL}/${NAMESPACE}/${RECIPE}/pulls" +else + echo "ERROR: PR creation failed (HTTP ${PR_STATUS}):" + cat "${PR_RESPONSE}" + rm -f "${PR_RESPONSE}" + exit 1 +fi + +rm -f "${PR_RESPONSE}" +``` + +After the script runs, report back the PR URL (or the existing-PR list URL if Gitea returned 409). + +## Notes + +- The `gitea` remote is created/updated in `~/.abra/recipes//.git/config` with the password embedded — this is acceptable here since `.testenv` already stores the password in plaintext, but be aware the remote URL is now stored in that local file. +- Re-running the skill is safe: the script force-pushes both the synced `main` and the PR branch, and reports gracefully if a PR for that branch already exists. +- The PR branch name is derived from the most recent commit message — if it matches `upgrade to `, the branch becomes `upgrade-`; otherwise a timestamped name is used. +- **For upgrade PRs, `RECIPE_PR_BODY` must carry the upstream release-notes links** (one line per upgraded service: `**Upstream release notes:** : `, sourced from `recipe-info//upstream.md`). `/recipe-upgrade-apply` composes the body with these links already; if you invoke this command directly for an upgrade, include them in `RECIPE_PR_BODY` yourself so the reviewer sees what changed upstream. diff --git a/.claude/commands/recipe-deploy.md b/.claude/commands/recipe-deploy.md new file mode 100644 index 0000000..e27df9f --- /dev/null +++ b/.claude/commands/recipe-deploy.md @@ -0,0 +1,35 @@ +--- +description: Deploy the local recipe checkout to the test instance +argument-hint: +allowed-tools: [Bash, Read, Write, Glob, Grep] +--- + +# Recipe Deploy + +Deploy the current local recipe checkout to the configured test instance using chaos mode. + +The recipe name is: $ARGUMENTS + +Read and follow the instructions in `.claude/commands/includes/logging.md`. +Read and follow the instructions in `.claude/commands/includes/guidelines.md`. + +## Steps + +1. **Get the domain and server for this recipe**: + ``` + python3 scripts/get_test_instance.py --recipe $ARGUMENTS + ``` + This outputs DOMAIN and SERVER for the active instance. + - If the recipe has no `recipe-info/$ARGUMENTS/recipe.toml`, tell the user to run `/recipe-init $ARGUMENTS` first and stop. + +2. **Show current local recipe state** — run `abra recipe diff $ARGUMENTS` to show what local changes exist in the recipe checkout. + +3. **Deploy with chaos** — run: + ``` + abra app deploy --chaos --force --no-input + ``` + - `--chaos` deploys the local checkout as-is, ignoring uncommitted changes. + - `--force` skips confirmation prompts. + - `--no-input` ensures non-interactive mode. + +4. **Verify the deployment** — suggest running `/recipe-test $ARGUMENTS` to confirm the deployment is healthy. diff --git a/.claude/commands/recipe-init.md b/.claude/commands/recipe-init.md new file mode 100644 index 0000000..84870e2 --- /dev/null +++ b/.claude/commands/recipe-init.md @@ -0,0 +1,105 @@ +--- +description: Create a new test instance and recipe-info for a recipe +argument-hint: +allowed-tools: [Bash, Read, Write, Glob, Grep, WebFetch, WebSearch] +--- + +# Recipe Init + +Bootstrap everything needed to start working with a Co-op Cloud recipe: fetch it, create a test instance, set up the `recipe-info/` directory with upstream info and tests, and deploy. + +The recipe name is: $ARGUMENTS + +Read and follow the instructions in `.claude/commands/includes/logging.md`. +Read and follow the instructions in `.claude/commands/includes/guidelines.md`. + +## Steps + +1. **Resolve the active instance** — run `python3 scripts/get_test_instance.py` to get SERVER and INSTANCE. Use these values throughout (not hardcoded instance names). + +2. **Fetch the recipe** — check for uncommitted local changes first (see guidelines). If clean, run `abra recipe fetch $ARGUMENTS --force`. If there are local changes, skip the fetch and note that you're using the local checkout. + +3. **Read the recipe's compose.yml** to identify images and services. The recipe lives at `~/.abra/recipes/$ARGUMENTS/compose.yml`. + +4. **Read the recipe's README** at `~/.abra/recipes/$ARGUMENTS/README.md` (if it exists): + - Look for any required initial configuration steps beyond what `abra app new` handles (e.g. manual env vars, external dependencies, DNS records, post-deploy setup commands, required third-party accounts or API keys). + - Note any documented setup instructions, caveats, or prerequisites. + - If the README mentions configuration that needs operator action, include it in the summary at the end and set the relevant env vars in the `abra app new` step or in the app's env file if possible. + +5. **Identify all required domains** and ask the user to set up DNS: + - The primary domain is always `$ARGUMENTS.`. + - Check compose.yml and `.env.sample` for additional domain variables (e.g. `SANDBOX_DOMAIN`, `EXTRA_DOMAINS`). For each additional domain, propose a subdomain following the pattern `-$ARGUMENTS.`. + - Present the full list of domains to the user and tell them each one needs a DNS A/CNAME record pointing to the test server. + - **Stop and wait for the user to confirm the DNS records are in place** before continuing. TLS certificate provisioning (via Let's Encrypt / Traefik) will fail if the domains don't resolve to the server. + +6. **Create the test app instance** via: + ``` + abra app new $ARGUMENTS --server --domain $ARGUMENTS. --no-input + ``` + - Do NOT pass `--secrets` here — secrets are generated separately in step 7 so we can capture them. + - `--no-input` for non-interactive mode. + - If the app already exists (command errors with an "already exists" message), note that and skip creation. + - If the README (step 4) mentioned required env vars or configuration, set them in the app's env file before deploying. The env file is at `~/.abra/servers//$ARGUMENTS..env`. + +7. **Generate and save secrets** — Generate secrets separately so the machine-readable output can be captured and saved: + ```bash + abra app secret generate $ARGUMENTS. --all -m --no-input + ``` + - The `-m` flag produces machine-readable output with the secret names and values. + - Capture this output and save it to `recipe-info/testsecrets/$ARGUMENTS.`. Create the `testsecrets/` directory if it doesn't exist. + - Each line should be in `name=value` format. + - **Fallback**: If secrets were already generated (e.g. via a previous `abra app new --secrets`) and the values weren't saved, you can read them from the running container after deployment: + ```bash + ssh 'CID=$(docker ps -q -f name=_app); for f in $(docker exec $CID ls /run/secrets/); do echo "$f=$(docker exec $CID cat /run/secrets/$f)"; done' + ``` + +8. **Create `recipe-info/$ARGUMENTS/recipe.toml`** with the content: + ```toml + name = "$ARGUMENTS" + ``` + If the recipe has dependencies (e.g. requires keycloak or authentik for SSO), add: + ```toml + [dependencies] + requires = ["keycloak"] + + [sso] + provider = "keycloak" + setup_script = "setup/sso_integration.py" + ``` + +9. **Create `recipe-info/$ARGUMENTS/upstream.md`** — Discover the upstream project info: + - Read compose.yml to identify all images used by the recipe. + - Search for GitHub repos and release pages for each image. + - Write upstream.md following the format in `recipe-info/hedgedoc/upstream.md` as a template. + +10. **Create `recipe-info/$ARGUMENTS/setup.md`** (if it doesn't already exist) — Write a first-time setup guide based on the README and what you learned in steps 3-7: + - Use `recipe-info/hedgedoc/setup.md` as the template format. + - Use ``, `` as placeholders (not hardcoded instance names) so the guide works on any instance. + - **Prerequisites**: DNS records needed, any external dependencies (e.g. "Keycloak must be deployed first"). + - **Steps**: The exact `abra` commands to go from nothing to a working deployment — `abra app new`, `abra app secret generate`, any env file edits, `abra app deploy`, and any post-deploy commands (migrations, admin user creation, etc.). + - If the recipe needs SSO integration, add a step referencing the appropriate `setup_*_integration.py` script. + - If the README mentioned any special configuration, post-deploy hooks, or manual steps, include them. + - Keep it concise — this is a quick-reference runbook, not full documentation. + +11. **Create `recipe-info/$ARGUMENTS/test.md`** — Write a test plan: + - Target URL: `https://$ARGUMENTS.` + - List automated test scripts (at minimum, `health_check.py`). + - List manual verification steps (at minimum, open the URL in a browser and confirm it loads). + - If the README mentioned any post-deploy verification steps, include them in the manual checks. + +12. **Create `recipe-info/$ARGUMENTS/tests/health_check.py`** — A basic health check script following the pattern in `recipe-info/hedgedoc/tests/health_check.py`: + - Use `utils.tests.helpers` for HTTP checks and domain resolution. + - Check for HTTP 200 at the instance URL. + +13. **Deploy the app**: + ``` + abra app deploy $ARGUMENTS. --chaos --force --no-input + ``` + - If the README mentioned any post-deploy setup commands (e.g. running migrations, creating an admin user), run them after deployment. + - If secrets weren't saved in step 7 (fallback case), read them from the running containers now and save to `recipe-info/testsecrets/`. + +14. **Summarise** — Tell the user what was created and suggest next steps: + - Run `/recipe-test $ARGUMENTS` to verify the deployment. + - Run `/recipe-check $ARGUMENTS` to check for upgrades. + - Add more test scripts to `recipe-info/$ARGUMENTS/tests/`. + - If the README flagged any configuration that couldn't be automated (e.g. external API keys, DNS records, third-party accounts), list those as manual actions the user still needs to take. diff --git a/.claude/commands/recipe-new-tag.md b/.claude/commands/recipe-new-tag.md new file mode 100644 index 0000000..812bb1c --- /dev/null +++ b/.claude/commands/recipe-new-tag.md @@ -0,0 +1,91 @@ +--- +description: Bump the recipe version and publish the release via `abra recipe release` +argument-hint: [--patch|--minor|--major] +allowed-tools: [Bash, Read, Grep] +--- + +# Recipe New Tag + +Bump the recipe version label and publish the release using **`abra recipe release`** — the one command +that bumps the `coop-cloud.${STACK_NAME}.version` label, commits, creates the annotated tag, **and pushes +the tag upstream** (which publishes the release to the Co-op Cloud catalogue). Do **not** hand-compute the +semver or hand-edit the label — let abra do it. + +> ⚠️ **This publishes.** A real (non-`--dry-run`) `abra recipe release` pushes the tag to the recipe's git +> origin, generating a catalogue release. Only run it from a machine that has push access to the recipe's +> upstream (ssh-agent loaded with the coopcloud key), and only when you mean to publish. In the +> upgrade-PR flow this is the **final** step, run after the upstream PR merges (see `/recipe-upstream`). + +The arguments are: $ARGUMENTS + +Parse the arguments to extract the recipe name (first positional arg) and the bump type flag (`--patch`, `--minor`, or `--major`). Default to `--patch` if no flag is given. + +## Steps + +1. **Check for uncommitted changes** in the recipe directory: + +```bash +git -C ~/.abra/recipes/ status --short +``` + +If there are uncommitted changes, review the diff to understand what changed: + +```bash +git -C ~/.abra/recipes/ diff +git -C ~/.abra/recipes/ diff --cached +git -C ~/.abra/recipes/ ls-files --others --exclude-standard +``` + +Then stage everything and create a commit with a concise message summarising the changes (not just "update files" — describe what actually changed, e.g. "move WOPI startup trigger to celery worker" or "add celery-beat service for WOPI scheduling"): + +```bash +cd ~/.abra/recipes/ +git add -A +git commit -m "" +``` + +If there are no uncommitted changes, skip this step. + +2. **Run `abra recipe release`** with the parsed recipe name and the flag for the bump type. Map the bump + type to the abra flag (`major→-x`, `minor→-y`, `patch→-z`), then run the real (non-`--dry-run`) command + — it bumps the label, commits, tags, and pushes the tag (publishes): + +```bash +#!/usr/bin/env bash +set -euo pipefail + +RECIPE="" +BUMP="" + +RECIPE_DIR="${HOME}/.abra/recipes/${RECIPE}" +COMPOSE="${RECIPE_DIR}/compose.yml" + +if [ ! -f "${COMPOSE}" ]; then + echo "ERROR: ${COMPOSE} not found. Run 'abra recipe fetch ${RECIPE}' first." + exit 1 +fi + +# Current version (for the report only — abra computes the new one) +CURRENT=$(grep -oP 'coop-cloud\.\$\{STACK_NAME\}\.version=\K[^\s"]+' "${COMPOSE}" | head -1 || true) + +# Map the bump type to the abra recipe release flag +case "${BUMP}" in + major) FLAG="-x" ;; + minor) FLAG="-y" ;; + patch) FLAG="-z" ;; + *) echo "ERROR: unknown bump type '${BUMP}'"; exit 1 ;; +esac + +# Bump the label + commit + tag + PUBLISH, all in one real release (NO --dry-run). +# abra computes the correct a.b.c+x.y.z itself; do not hand-edit the label or hand-create the tag. +abra recipe release "${RECIPE}" "${FLAG}" + +# Show the resulting version label +NEW_VER=$(grep -oP 'coop-cloud\.\$\{STACK_NAME\}\.version=\K[^\s"]+' "${COMPOSE}" | head -1 || true) +echo "" +echo "Version: ${CURRENT:-?} -> ${NEW_VER:-?} (released + published)" +``` + +Substitute `` and `` with the parsed values, then run the script with `bash`. + +3. **Report the result** — show the old and new version, and confirm the release was published. diff --git a/.claude/commands/recipe-overview.md b/.claude/commands/recipe-overview.md new file mode 100644 index 0000000..4da5234 --- /dev/null +++ b/.claude/commands/recipe-overview.md @@ -0,0 +1,51 @@ +--- +description: Check all maintained recipes and recommend what to upgrade +allowed-tools: [Bash, Read, Write, Glob, Grep, WebFetch, WebSearch] +--- + +# Recipe Overview + +Check the status of recipes, initialize any that aren't set up yet, check each one for available upgrades, and recommend which recipe to focus on today. + +Read and follow the instructions in `.claude/commands/includes/logging.md`. +Read and follow the instructions in `.claude/commands/includes/guidelines.md`. + +## Arguments + +This skill accepts an optional space-separated list of recipe names as `$ARGUMENTS`. If provided, only check those recipes. If no arguments are given, read the full list from `maintained-recipes.md`. + +## Steps + +1. **Determine the recipe list**: + - If `$ARGUMENTS` is non-empty, use those recipe names as the list. + - Otherwise, read `maintained-recipes.md` from the workspace root and parse out the recipe names (lines starting with `- `). + +2. **Check initialization status** for each recipe in the list — a recipe is considered initialized if `recipe-info//recipe.toml` exists. + +3. **For every recipe** (initialized or not), check for available upgrades: + - Check for local changes first: `git -C ~/.abra/recipes/ status --short` + - If clean (or recipe not yet locally cloned), run: `abra recipe fetch --force` + - Get the latest published version: `abra recipe versions -m` (first line only) + - Check for available upgrades: `abra recipe upgrade -m -n` + - If the recipe has uncommitted local changes, note this and skip the fetch/upgrade check for that recipe. + - For initialized recipes, also read `recipe-info//upstream.md` for release notes URLs and briefly summarise any available upgrade's headline changes. + - Note any breaking changes or operator action required. + +4. **Build a summary table** across all recipes: + + | Recipe | Initialized | Current Version | Upgrades Available | Breaking Changes | Priority | + |--------|-------------|----------------|-------------------|-----------------|----------| + + For the Priority column, rank each recipe based on: + - **High**: security fixes available, or significantly behind upstream (multiple major versions) + - **Medium**: new features or minor version bumps available + - **Low**: only patch-level updates, or already up to date + - **Not initialized**: use this only if the recipe has no published versions at all (i.e. can't even be checked) + +5. **Recommend which recipe to work on today**: + - Pick the highest-priority recipe that has upgrades available. + - Explain why (security fixes, how far behind, breaking changes that will only get harder to handle later, etc.). + - For initialized recipes with upgrades, suggest running `/recipe-upgrade-plan `. + - For uninitialized recipes with high-priority upgrades pending, suggest running `/recipe-init ` first. + - If multiple recipes are high priority, list them in recommended order. + - If everything is up to date, say so and suggest running `/recipe-review` on a recipe that could use improvement instead. diff --git a/.claude/commands/recipe-review.md b/.claude/commands/recipe-review.md new file mode 100644 index 0000000..3552379 --- /dev/null +++ b/.claude/commands/recipe-review.md @@ -0,0 +1,120 @@ +--- +description: Review a recipe for Co-op Cloud best practices +argument-hint: +allowed-tools: [Bash, Read, Write, Glob, Grep, WebFetch, WebSearch] +--- + +# Recipe Review + +Review a Co-op Cloud recipe against best practices and suggest improvements. This performs a thorough audit of the recipe's configuration, security, and operational readiness. + +The recipe name is: $ARGUMENTS + +Read and follow the instructions in `.claude/commands/includes/logging.md`. +Read and follow the instructions in `.claude/commands/includes/guidelines.md`. + +## Steps + +1. **Fetch the recipe** — check for uncommitted local changes first (see guidelines). If clean, run `abra recipe fetch $ARGUMENTS --force`. If there are local changes, skip the fetch and note that you're using the local checkout. + The recipe lives at `~/.abra/recipes/$ARGUMENTS/`. + +2. **Read all recipe files** — read every file in the recipe directory: + - `compose.yml` (and any `compose.*.yml` overlay files) + - `.env.sample` + - `abra.sh` + - `README.md` + - Any config templates (`.tmpl`, `.conf`, `.ini`, `.js`, etc.) + - Any entrypoint scripts (`entrypoint*.sh`) + - `release/` directory contents (if present) + - `MAINTENANCE.md` (if present) + +3. **Run the linter** (if available): + ``` + abra recipe lint $ARGUMENTS + ``` + Note any warnings or errors. + +4. **Check the recipe against each best practice area below.** For each area, note whether the recipe follows the practice, partially follows it, or is missing it entirely. Suggest specific fixes where applicable. + +### Checklist + +#### Compose structure +- [ ] Uses compose version `3.8` +- [ ] Services use named volumes (not bind mounts) +- [ ] No `ports:` definitions — routing is handled by Traefik labels +- [ ] No `env_file:` directives — environment is managed by abra +- [ ] Internal services (databases, caches) are on an `internal` network, not `proxy` +- [ ] The main web-facing service is on both `proxy` (external) and an internal network + +#### Version label +- [ ] The `app` service has a `coop-cloud.${STACK_NAME}.version` deploy label +- [ ] The version follows the `X.Y.Z+upstream_version` format + +#### Traefik labels +- [ ] `traefik.enable=true` on the web-facing service +- [ ] `traefik.http.routers.${STACK_NAME}.rule=Host(...)` is set +- [ ] `traefik.http.routers.${STACK_NAME}.tls=true` or `.tls.certresolver` is set +- [ ] `traefik.http.routers.${STACK_NAME}.entrypoints=web-secure` is set +- [ ] Traefik network is specified: `traefik.docker.network=proxy` + +#### Deploy configuration +- [ ] `restart_policy` is configured (ideally with `max_attempts` to prevent infinite restarts) +- [ ] `update_config` with `failure_action: rollback` and `order: start-first` (recommended) +- [ ] `rollback_config` with `order: start-first` (recommended) + +#### Healthchecks +- [ ] At least one service has a healthcheck defined +- [ ] Database/cache services have appropriate healthchecks (e.g. `redis-cli ping`, `pg_isready`) +- [ ] If healthchecks are absent, note this as an improvement area + +#### Backups + +Consult the README of the `backup-bot-two` recipe (`~/.abra/recipes/backup-bot-two/README.md`) for details on how to configure backup labels in Co-op Cloud recipes. Fetch it first if needed (`abra recipe fetch backup-bot-two`). + +- [ ] `backupbot.backup=true` label is present on each service that has volumes to back up (can be on multiple services) +- [ ] By default all volumes on a labelled service are backed up — only use `backupbot.backup.volumes..path` or `backupbot.backup.volumes.: false` when you need to limit or exclude specific volumes +- [ ] For database services: `backupbot.backup.pre-hook` dumps the database, `backupbot.restore.post-hook` restores it +- [ ] If backups are not configured, suggest adding `backupbot.backup` labels based on the backup-bot-two README + +#### Secrets +- [ ] Sensitive values (passwords, API keys, tokens) use Docker secrets, not plain-text env vars +- [ ] Secret names are < 12 characters (to avoid the 64-char Docker limit after stack name + version are appended) +- [ ] `.env.sample` has `SECRET_*_VERSION=v1` entries for each secret +- [ ] `abra.sh` has secret generation hints (length, charset) if applicable + +#### Environment variables +- [ ] `.env.sample` exists with all required variables +- [ ] Optional env vars are commented out in `.env.sample` +- [ ] Domain templating uses `.example.com` pattern (auto-replaced by `abra app new`) +- [ ] New/optional env vars in compose.yml use defaults: `${MY_VAR:-default_value}` +- [ ] `STACK_NAME` is exposed in `environment:` for any service that needs it in config templates + +#### Config management +- [ ] If the recipe uses Docker configs, versions are exported in `abra.sh` +- [ ] Config naming is consistent through the chain: configs stanza name, `name:` interpolation, and `abra.sh` export +- [ ] Config templates use `template_driver: golang` when Go template interpolation is needed +- [ ] Secrets in templates accessed via `{{ secret "name" }}`, env vars via `{{ env "NAME" }}` + +#### README metadata +- [ ] README has the `` ... `` section +- [ ] All fields are present: Category, Status, Image, Healthcheck, Backups, Email, Tests, SSO +- [ ] Scores are accurate and reflect the current state of the recipe +- [ ] At least one named maintainer is listed (required for "stable" status) + +#### Optional features +- [ ] SMTP/email support via `compose.smtp.yml` (if the app can send email) +- [ ] SSO/OIDC support via `compose.oidc.yml` (if the app supports it) +- [ ] Release notes in `release/` directory for each version + +#### Resource limits +- [ ] Consider whether `deploy.resources.limits` should be set (memory/CPU) to prevent OOM issues on shared servers + +5. **Summarise the review** with three sections: + + **What's good** — things the recipe already does well. + + **Recommended fixes** — issues that should be addressed (missing backup config, missing healthchecks, security concerns, incorrect labels, etc.). For each, provide the specific change needed (ideally a code snippet showing what to add/change in compose.yml or other files). + + **Nice-to-haves** — optional improvements that would raise the recipe's quality/status score (SMTP support, SSO, better deploy config, resource limits, etc.). + + Update the README metadata scores if they don't reflect reality (e.g. if backups are configured but README says "No"). diff --git a/.claude/commands/recipe-test-all.md b/.claude/commands/recipe-test-all.md new file mode 100644 index 0000000..b7c0a35 --- /dev/null +++ b/.claude/commands/recipe-test-all.md @@ -0,0 +1,110 @@ +--- +description: Run tests for all maintained recipes, deploying each one at a time +allowed-tools: [Bash, Read, Write, Edit, Glob, Grep, WebFetch] +--- + +# Recipe Test All + +Deploy and test every maintained recipe on the active test instance, one at a time. For each recipe, free server memory by undeploying unrelated apps, deploy the recipe and its dependencies, run all its tests, then move on to the next. + +Read and follow the instructions in `.claude/commands/includes/logging.md`. +Read and follow the instructions in `.claude/commands/includes/guidelines.md`. + +## Steps + +### 1. Determine the active instance + +Run `python3 scripts/get_test_instance.py` to get SERVER and INSTANCE. Read `settings.toml` to get `domain_suffix`. + +### 2. Discover all recipes and build deployment tiers + +Glob `recipe-info/*/recipe.toml`. For each, read the file to get: +- `name` — the recipe name +- `[dependencies].requires` — list of recipe dependencies (may be empty or absent) + +Group into tiers: +- **Tier 1** (no dependencies): recipes with empty or missing `requires` +- **Tier 2** (depends on tier 1): recipes whose `requires` list only contains tier 1 recipes + +Present the test order to the user and **confirm before proceeding**. + +### 3. Test each recipe in dependency order + +Process recipes **one at a time** within each tier. For each recipe: + +#### a. Context reset + +Free memory by undeploying everything except infrastructure and this recipe's dependencies: +```bash +python3 scripts/context_reset.py --recipe +``` + +#### b. Deploy dependencies and test dependencies + +If the recipe has `[dependencies].requires` (runtime deps), deploy each one: +```bash +abra app deploy --chaos --force --no-input +``` + +If the recipe has `[dependencies].test_requires` (test-only deps), deploy those too. These are domain prefixes (e.g. `lasuite-docs`, `ld2`) — the full domain is `.`. Note: `test_requires` are NOT transitive — only the target recipe's test_requires are deployed, not those of its dependencies. + +Wait for each dependency to become healthy before continuing. + +#### c. Deploy this recipe + +```bash +abra app deploy . --chaos --force --no-input +``` + +If the deploy reports failure but services are running (check via SSH `docker service ls`), treat it as a success — abra's convergence checker times out on complex multi-service stacks. + +If the deploy truly fails (services not starting, missing secrets, etc.), log FAIL for this recipe and skip to the next one. + +#### d. Run tests + +Discover all test scripts: +```bash +ls recipe-info//tests/*.py +``` + +Run each test script in sequence: +```bash +python3 recipe-info//tests/