From 7bf9b2da4fce25d38baf368a20a4e3ad2bb443b0 Mon Sep 17 00:00:00 2001 From: Lumpiasty Date: Fri, 29 May 2026 00:51:18 +0200 Subject: [PATCH] add releases --- .woodpecker/release-tag.yaml | 63 ++++++++++++++++++++++++++++++++++++ .woodpecker/release.yaml | 58 +++++++++++++++++++++++++++++++++ Dockerfile | 14 ++++++++ README.md | 50 ++++++++++++++++++++++++++++ 4 files changed, 185 insertions(+) create mode 100644 .woodpecker/release-tag.yaml create mode 100644 .woodpecker/release.yaml diff --git a/.woodpecker/release-tag.yaml b/.woodpecker/release-tag.yaml new file mode 100644 index 0000000..cd3b659 --- /dev/null +++ b/.woodpecker/release-tag.yaml @@ -0,0 +1,63 @@ +# Auto-create the release tag when Tailscale is bumped. +# +# Policy: a new Tailscale version (merged by Renovate, which edits +# ARG TAILSCALE_VERSION in the Dockerfile) gets released as v-mt.1. +# This job runs on every push to main, reads TAILSCALE_VERSION from the +# Dockerfile, and — if no v-mt.* tag exists yet — creates and pushes +# v-mt.1. Pushing that tag triggers .woodpecker/release.yaml. +# +# Follow-up releases (mt.2, mt.3, ... for manual fixes/changes) are tagged +# BY HAND; this job never creates them (it only ever creates -mt.1). +# +# Dependency-only bumps (Go/Alpine/busybox/dockerfile) leave TAILSCALE_VERSION +# unchanged, so no tag is created and nothing is released — they ride along +# with the next Tailscale bump or manual tag. + +when: + - event: push + branch: main + +steps: + - name: Get git token from OpenBao + image: quay.io/openbao/openbao:2.5.4 + environment: + VAULT_ADDR: https://openbao.lumpiasty.xyz:8200 + ROLE_ID: + from_secret: renovate_role_id + SECRET_ID: + from_secret: renovate_secret_id + commands: + - bao write -field token auth/approle/login + role_id=$ROLE_ID + secret_id=$SECRET_ID > /woodpecker/.vault_id + - export VAULT_TOKEN=$(cat /woodpecker/.vault_id) + - bao kv get -mount secret -field RENOVATE_TOKEN renovate > /woodpecker/git_token + + - name: Auto-tag mt.1 on Tailscale bump + image: alpine/git:2.49.1 + environment: + CI_REPO_URL: https://gitea.lumpiasty.xyz/lumpiasty/mikrotik-tailscale.git + commands: + # Read the Tailscale version that's about to be (or was) built. + - TS=$(sed -n 's/^ARG TAILSCALE_VERSION=//p' Dockerfile) + - 'echo "Tailscale version in Dockerfile: $TS"' + - test -n "$TS" || { echo "could not parse TAILSCALE_VERSION"; exit 1; } + - TAG="$TS-mt.1" + # Make sure we have all tags locally (clone may be shallow / partial). + - git fetch --tags --quiet + # If ANY release tag already exists for this Tailscale version, the + # automatic mt.1 has already happened (or a manual mt.N supersedes it): + # do nothing. Only the FIRST sighting of a new Tailscale version tags. + - | + if git tag --list "$TS-mt.*" | grep -q .; then + echo "Release tag(s) already exist for $TS; nothing to auto-tag." + exit 0 + fi + - echo "No release tag for $TS yet; creating $TAG" + - git config user.name "Woodpecker CI" + - git config user.email "ci@lumpiasty.xyz" + - GIT_TOKEN=$(cat /woodpecker/git_token) + # Annotated tag at the current commit. + - git tag -a "$TAG" -m "Automated release for Tailscale $TS" + - git push "https://woodpecker:$GIT_TOKEN@gitea.lumpiasty.xyz/lumpiasty/mikrotik-tailscale.git" "$TAG" + - echo "Pushed $TAG" diff --git a/.woodpecker/release.yaml b/.woodpecker/release.yaml new file mode 100644 index 0000000..2ca58e4 --- /dev/null +++ b/.woodpecker/release.yaml @@ -0,0 +1,58 @@ +# Build and publish a multi-arch release to the Gitea container registry. +# +# Triggered by pushing a v-mt. tag: +# - v-mt.1 is created automatically by .woodpecker/release-tag.yaml on a +# Tailscale bump. +# - v-mt.2, mt.3, ... are created manually for fixes/changes. +# +# Publishes a SINGLE multi-arch manifest (amd64 + arm64 + arm/v7) so RouterOS +# pulls the right arch automatically. Tags pushed: +# : e.g. v1.98.3-mt.1 (immutable, for rollback/audit) +# :stable (moving; what the router tracks) +# +# The image is stamped with org.opencontainers.image.version= via the +# OCI_VERSION build arg; the router compares that label to decide updates. +# +# Registry credentials live in OpenBao (secret/container-registry, keys +# REGISTRY_USERNAME / REGISTRY_PASSWORD). The first step fetches them with the +# same AppRole used by Renovate and writes them as PLUGIN_USERNAME / +# PLUGIN_PASSWORD into an env file that the buildx plugin loads via env_file. +# This keeps all secrets in OpenBao (no Woodpecker secret duplication). + +when: + - event: tag + ref: refs/tags/v*-mt.* + +steps: + - name: Get registry creds from OpenBao + image: quay.io/openbao/openbao:2.5.4 + environment: + VAULT_ADDR: https://openbao.lumpiasty.xyz:8200 + ROLE_ID: + from_secret: renovate_role_id + SECRET_ID: + from_secret: renovate_secret_id + commands: + - bao write -field token auth/approle/login + role_id=$ROLE_ID + secret_id=$SECRET_ID > /woodpecker/.vault_id + - export VAULT_TOKEN=$(cat /woodpecker/.vault_id) + # Write creds in the env-file format the buildx plugin understands. + # PLUGIN_USERNAME / PLUGIN_PASSWORD map to the plugin's username/password. + - 'printf "PLUGIN_USERNAME=%s\n" "$(bao kv get -mount secret -field REGISTRY_USERNAME container-registry)" > /woodpecker/registry.env' + - 'printf "PLUGIN_PASSWORD=%s\n" "$(bao kv get -mount secret -field REGISTRY_PASSWORD container-registry)" >> /woodpecker/registry.env' + + - name: Build and push multi-arch image + image: woodpeckerci/plugin-docker-buildx:5.2.2 + privileged: true + settings: + registry: gitea.lumpiasty.xyz + repo: gitea.lumpiasty.xyz/lumpiasty/mikrotik-tailscale + platforms: linux/amd64,linux/arm64,linux/arm/v7 + tags: + - ${CI_COMMIT_TAG} + - stable + build_args: + - OCI_VERSION=${CI_COMMIT_TAG} + # Credentials (PLUGIN_USERNAME / PLUGIN_PASSWORD) come from OpenBao. + env_file: /woodpecker/registry.env diff --git a/Dockerfile b/Dockerfile index 15ea05c..47c28a9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -191,6 +191,20 @@ RUN mkdir -p /rootfs/bin && \ # ============================================================================= FROM scratch +# Release version (the git tag, e.g. v1.98.3-mt.1), injected by CI at build +# time. This is the value the MikroTik update cronjob compares against the +# registry to decide whether to recreate the container: it changes ONLY on a +# meaningful release (Tailscale bump -> mt.1, or a manual mt.N), never on a +# build-system-only rebuild. Defaults to "dev" for local builds. +ARG OCI_VERSION=dev + +# OCI image annotations. org.opencontainers.image.version is the canonical place +# for the release version and is what the router reads back from the registry. +LABEL org.opencontainers.image.title="mikrotik-tailscale" \ + org.opencontainers.image.description="Minimal Tailscale image for MikroTik RouterOS Container" \ + org.opencontainers.image.source="https://gitea.lumpiasty.xyz/lumpiasty/mikrotik-tailscale" \ + org.opencontainers.image.version="${OCI_VERSION}" + # Custom static busybox + applet symlinks (provides /bin/sh and utilities) COPY --from=busybox /rootfs/ / diff --git a/README.md b/README.md index cabcb62..8177df9 100644 --- a/README.md +++ b/README.md @@ -287,6 +287,56 @@ docker buildx build --platform linux/arm64 \ --load -t mikrotik-tailscale:arm64 . ``` +## Versioning & releases + +Released images are versioned as: + +``` +v-mt. +``` + +e.g. `v1.98.3-mt.1`. The two parts mean: + +- **`v`** — the bundled Tailscale version (the "what's + inside" identifier), taken from `ARG TAILSCALE_VERSION` in the Dockerfile. +- **`mt.`** — the local revision. It only changes on a *meaningful* release, + never on a build-system-only rebuild. + +### When a release happens + +| Trigger | Result | +|---|---| +| Renovate bumps `TAILSCALE_VERSION` (merged to `main`) | CI **auto-creates** git tag `v-mt.1` → image published | +| You make a meaningful fix/change on the current Tailscale version | **You** create the next tag manually (`v-mt.2`, `mt.3`, …) → image published | +| Dependency-only bump (Go / Alpine / busybox / Dockerfile syntax) | **No release.** Rides along with the next Tailscale bump or manual tag | + +So routers only ever see a new release for Tailscale bumps or your deliberate +fixes — build-system churn doesn't trigger updates. + +Each published image is stamped with `org.opencontainers.image.version` equal to +its full tag; this is the value the MikroTik update job compares against the +registry to decide whether to recreate the container. + +### How it's wired (Woodpecker) + +- **`.woodpecker/release-tag.yaml`** — on push to `main`, parses + `TAILSCALE_VERSION`; if no `v-mt.*` tag exists yet, creates and pushes + `v-mt.1` (using the Gitea token from OpenBao). It never creates `mt.2+`. +- **`.woodpecker/release.yaml`** — on a `v*-mt.*` tag push, builds the + multi-arch manifest (amd64 + arm64 + arm/v7) and pushes it to + `gitea.lumpiasty.xyz/lumpiasty/mikrotik-tailscale` as both `:` and + `:stable`. Registry creds come from OpenBao (`secret/container-registry`). + +### Cutting a manual release + +```sh +# fix something, commit to main, then: +git tag -a v1.98.3-mt.2 -m "Fix X" +git push origin v1.98.3-mt.2 +``` + +The tag push triggers the build+publish automatically. + ## Dependency pinning & automated updates All upstream dependencies are version-pinned for reproducible builds: