diff --git a/.woodpecker/pr-build.yaml b/.woodpecker/pr-build.yaml new file mode 100644 index 0000000..4354f8d --- /dev/null +++ b/.woodpecker/pr-build.yaml @@ -0,0 +1,26 @@ +# Build validation for pull requests (and pushes to main). +# +# Builds the full multi-arch image but does NOT push it anywhere — it only +# proves the Dockerfile still builds for every supported architecture. This is +# the gate Renovate automerge waits on: a dependency bump that breaks the build +# fails this check and will NOT be automerged (and therefore never reaches +# :stable or the routers). +# +# Reports pass/fail status back to Gitea, so it shows up as a required check on +# the PR. + +when: + - event: pull_request + - event: push + branch: main + +steps: + - name: Build all arches (no push) + image: woodpeckerci/plugin-docker-buildx:5.2.2 + privileged: true + settings: + repo: mikrotik-tailscale + platforms: linux/amd64,linux/arm64,linux/arm/v7 + dry-run: true + build_args: + - OCI_VERSION=ci-${CI_COMMIT_SHA} diff --git a/docs/DESIGN.md b/docs/DESIGN.md index daf9a13..b9ce258 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -324,14 +324,35 @@ run **self-hosted** from a Woodpecker cron pipeline (Woodpecker has no native Renovate support): - `renovate.json` — repository rules. All dependencies follow the latest - upstream releases (including major versions); each bump arrives as its own PR - that the multi-arch build validates before you merge. Base image tags also - get their `@sha256` digests refreshed via `pinDigests`. The one special rule: + upstream releases; each bump arrives as its own PR. Base image tags also get + their `@sha256` digests refreshed via `pinDigests`. Notable rules: - `tailscale` only follows **stable** releases — Tailscale uses even minor versions for stable (`v1.98.x`) and odd for unstable (`v1.99.x`), so the rule filters to even minors. - `.woodpecker/renovate.yaml` — the scheduled job that runs `renovate/renovate` against this repo. +- `.woodpecker/pr-build.yaml` — builds all three arches (no push) on every PR + and reports status to Gitea. This is the gate for automerge. + +### Automerge policy + +These updates **automerge** once the PR build passes — they reach `:stable` +(and the routers) without manual review: + +| Update | Automerge? | Why | +|---|---|---| +| Tailscale stable (patch **and** minor) | ✅ | the point of the project; the PR build catches breakage | +| Go / Alpine / busybox **patch** | ✅ | bugfix-only, build-internal | +| Base-image **digest** refresh (same tag) | ✅ | content refresh, no version change | +| Go / Alpine / busybox **minor/major** | ❌ manual | larger toolchain/base changes warrant review | +| Renovate runner, syntax frontend | ❌ manual | tooling — review deliberately | + +**Important:** automerge depends on the PR build being a **required status +check** in Gitea branch protection. The PR build only proves the image *builds* +for all arches — it does not run the daemon, so a runtime regression in a new +Tailscale release could still be automerged. That is an accepted trade-off for +the convenience of unattended Tailscale updates; if a release misbehaves, roll +back by re-tagging the previous `v…-mt.N` (the immutable tags are kept). Validate the configs locally: diff --git a/renovate.json b/renovate.json index a98185e..c3d6ab3 100644 --- a/renovate.json +++ b/renovate.json @@ -7,10 +7,12 @@ ], "labels": ["dependencies"], "rebaseWhen": "behind-base-branch", - "dockerfile": { - "pinDigests": true - }, "packageRules": [ + { + "matchManagers": ["dockerfile"], + "description": "Keep base-image tags pinned to a digest.", + "pinDigests": true + }, { "matchDatasources": ["github-releases"], "matchPackageNames": ["tailscale/tailscale"], @@ -18,6 +20,28 @@ "extractVersion": "^v(?\\d+\\.\\d+\\.\\d+)$", "allowedVersions": "/^\\d+\\.\\d*[02468]\\.\\d+$/", "ignoreUnstable": true + }, + { + "matchDatasources": ["github-releases"], + "matchPackageNames": ["tailscale/tailscale"], + "description": "Automerge all stable Tailscale releases (patch AND minor) once the PR build passes.", + "matchUpdateTypes": ["minor", "patch"], + "automerge": true + }, + { + "matchManagers": ["dockerfile"], + "matchPackageNames": ["golang", "alpine", "busybox"], + "description": "Automerge PATCH-only bumps of build components (Go/Alpine/busybox) once the PR build passes; review minor/major manually.", + "matchUpdateTypes": ["patch"], + "automerge": true + }, + { + "matchManagers": ["dockerfile"], + "matchUpdateTypes": ["digest", "pinDigest"], + "description": "Automerge base-image digest refreshes (same tag, new sha256) once the PR build passes.", + "automerge": true } - ] + ], + "automergeType": "pr", + "platformAutomerge": true }