From 454be36becf2c33361cc5434caf4c523122e48e0 Mon Sep 17 00:00:00 2001 From: h Date: Thu, 10 Sep 2026 16:01:09 +0000 Subject: [PATCH] feat(gitea): create Renovate labels for a new repo --- README.md | 26 +++++++++++++++++++++++++ shell.sh | 1 + templates.py | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+) diff --git a/README.md b/README.md index 625e2bc..3fef43d 100644 --- a/README.md +++ b/README.md @@ -17,4 +17,30 @@ newproject ~/projects/my-thing --parts backend,frontend,caddy --backend python - One part alone is rendered at the root of the project; two parts live in `backend/` and `frontend/`. After rendering, `make env` creates `.env` and every `docker-compose.override.yml` from their examples. `updateproject` needs a clean working tree and commits each part's update separately, so the next part starts from a clean tree again; conflicts stay in those commits for you to resolve. Templates are cloned from `https://git.kotikot.com/templates` unless `TEMPLATES_DIR` points at a directory with local checkouts; `TEMPLATES_REF=HEAD` renders their working trees instead of the latest tag. +## Renovate + +A new repo needs the `renovate`, `wait-3d` and `deps-code` labels before the bot +ever runs against it: Renovate fails on a label that does not exist, and until +today these were created by hand for every repo. + +```sh +giteaproject ms-agents/my-thing +``` + +Then drop a `renovate.jsonc` into the repo root with nothing but the shared +preset: + +```json +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["local>templates/renovate"] +} +``` + +**Extending the preset does not subscribe the repo to anything.** The bot walks +an explicit list, so the repo also has to be added to `repositories` in +`projects/personal/renovate/renovate-config.js` of `infra/komodo`, and the +`renovate` team of its organization needs write access to it. Miss that step and +the config sits there doing nothing, silently. + Requires `uv`; `bun`, `docker` and `pre-commit` are used by the generated projects. diff --git a/shell.sh b/shell.sh index d2279e3..b98b220 100644 --- a/shell.sh +++ b/shell.sh @@ -6,3 +6,4 @@ fi newproject() { uv run --script "$TEMPLATES_HOME/templates.py" new "$@"; } updateproject() { uv run --script "$TEMPLATES_HOME/templates.py" update "$@"; } +giteaproject() { uv run --script "$TEMPLATES_HOME/templates.py" gitea "$@"; } diff --git a/templates.py b/templates.py index 63fda66..4bd1552 100644 --- a/templates.py +++ b/templates.py @@ -112,6 +112,58 @@ def update(args: argparse.Namespace) -> None: print(f"committed {file.stem} update") +GITEA = os.environ.get("GITEA_URL", "https://git.kotikot.com").rstrip("/") +RENOVATE_LABELS = [ + ("renovate", "1f6feb", "Pull request from the Renovate bot"), + ("wait-3d", "d29922", "Waits three days: the registry has no release timestamp, the age gate merges it"), + ("deps-code", "7d8590", "Code dependencies: pull requests are collected, nothing merges itself"), +] + + +def gitea_token() -> str: + token = os.environ.get("GITEA_TOKEN", "") + if token: + return token + path = Path.home() / ".config/gitea/token" + if path.exists(): + return path.read_text(encoding="utf-8").strip() + sys.exit("no Gitea token: set GITEA_TOKEN or write ~/.config/gitea/token") + + +def gitea(args: argparse.Namespace) -> None: + """Prepare a Gitea repo for Renovate: the labels it fails without.""" + import json + import urllib.error + import urllib.request + + token = gitea_token() + for name, color, description in RENOVATE_LABELS: + body = json.dumps({"name": name, "color": color, "description": description}).encode() + request = urllib.request.Request( + f"{GITEA}/api/v1/repos/{args.repo}/labels", + data=body, + method="POST", + headers={"Authorization": f"token {token}", "Content-Type": "application/json"}, + ) + try: + urllib.request.urlopen(request, timeout=30) + print(f"label {name}: created") + except urllib.error.HTTPError as error: + if error.code in (409, 422): + print(f"label {name}: already there") + else: + sys.exit(f"label {name}: {error.code} {error.read()[:200]!r}") + + print( + "\nLabels done. Two things are still manual, and neither happens by itself:\n" + f" 1. renovate.jsonc in {args.repo} with" + ' {"extends": ["local>templates/renovate"]}\n' + " 2. the repo added to `repositories` in renovate-config.js of infra/komodo,\n" + " plus the bot given write access to it. Extending the preset subscribes\n" + " nothing: the bot only visits repos named in that list." + ) + + def main() -> None: parser = argparse.ArgumentParser(prog="templates") sub = parser.add_subparsers(dest="command", required=True) @@ -130,6 +182,9 @@ def main() -> None: u.add_argument("--ask", action="store_true", help="ask every question again") u.add_argument("--defaults", action="store_true") u.set_defaults(func=update) + g = sub.add_parser("gitea", help="Create the Renovate labels a repo needs") + g.add_argument("repo", help="org/name on Gitea") + g.set_defaults(func=gitea) args = parser.parse_args() args.func(args)