diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index b62fe3a..fd81376 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -10,7 +10,7 @@ on: workflow_dispatch: inputs: tag: - description: "Upstream versitygw tag to build (e.g. v1.5.0)" + description: "Upstream versitygw tag to build (must be listed in ci/support-matrix.yaml, e.g. v1.10.0)" required: true jobs: diff --git a/README.md b/README.md index 4755e35..30a09b4 100644 --- a/README.md +++ b/README.md @@ -38,11 +38,14 @@ s3-gateway/ │ ├── publish-release.sh # upload dist/* to a Gitea release │ └── run.sh # local end-to-end: clone + patch + build one tag ├── patches/ -│ ├── v1.6.0/ +│ ├── v1.10.0/ # the supported series │ │ ├── 0001-webui-debrand.patch │ │ └── 0002-webui-dark-theme.patch -│ └── v1.5.0/ -│ └── 0001-webui-debrand.patch +│ └── archive/ # superseded; kept, not built +│ ├── v1.8.0/ +│ ├── v1.7.0/ +│ ├── v1.6.0/ +│ └── v1.5.0/ ├── .gitea/workflows/build.yml └── docs/multi-version-pipeline.md ``` @@ -53,18 +56,18 @@ Prerequisites: `git`, `go`, and [`goreleaser`](https://goreleaser.com). ```bash # Build one upstream tag end-to-end → artifacts in src/dist/ -VGW_TAG=v1.5.0 bash ci/run.sh +VGW_TAG=v1.10.0 bash ci/run.sh # Fast offline build against a local upstream clone: -VGW_REPO_URL="/path/to/versitygw" VGW_TAG=v1.5.0 bash ci/run.sh +VGW_REPO_URL="/path/to/versitygw" VGW_TAG=v1.10.0 bash ci/run.sh ``` Or run the steps individually: ```bash -VGW_TAG=v1.5.0 bash ci/clone-source.sh # → src/ -VGW_TAG=v1.5.0 bash ci/apply-patches.sh # git am the series onto src/ -VGW_TAG=v1.5.0 bash ci/build.sh # goreleaser → src/dist/ +VGW_TAG=v1.10.0 bash ci/clone-source.sh # → src/ +VGW_TAG=v1.10.0 bash ci/apply-patches.sh # git am the series onto src/ +VGW_TAG=v1.10.0 bash ci/build.sh # goreleaser → src/dist/ ``` `src/` and `src/dist/` are gitignored. @@ -76,15 +79,21 @@ applied with `git am --3way`, in filename order. The directory for a tag comes from the `patches:` field in `ci/support-matrix.yaml` (defaults to the tag name). +That matrix is an **allowlist**: `ci/apply-patches.sh` refuses any tag +it does not list, so a stray tag push or a mistyped dispatch can't +publish a build of a superseded release. + Currently maintained: | Upstream tag | Patch set | Contents | |--------------|---------------|-----------------------------------------------------------| | `v1.10.0` | `v1.10.0/` | `0001-webui-debrand.patch`, `0002-webui-dark-theme.patch` | -| `v1.8.0` | `v1.8.0/` | `0001-webui-debrand.patch`, `0002-webui-dark-theme.patch` | -| `v1.7.0` | `v1.7.0/` | `0001-webui-debrand.patch`, `0002-webui-dark-theme.patch` | -| `v1.6.0` | `v1.6.0/` | `0001-webui-debrand.patch`, `0002-webui-dark-theme.patch` | -| `v1.5.0` | `v1.5.0/` | `0001-webui-debrand.patch` | + +**v1.10.0 is the only supported version.** It carries upstream CVE +fixes, so the older series — `v1.8.0`, `v1.7.0`, `v1.6.0` and `v1.5.0` +— are no longer built. Their patches are kept under +[`patches/archive/`](patches/archive/) for reference and for a +deliberate rebuild; see that directory's README. ### What the de-brand patch does @@ -122,8 +131,8 @@ shared asset files plus bundled fonts: matching the existing self-hosted Roboto. The only markup change is the login page's light background gradient, -which becomes a solid dark surface. Applied to every maintained patch -set except `v1.5.0`. +which becomes a solid dark surface. It is part of the supported +`v1.10.0` series, and of every archived set except `v1.5.0`. ## Adding / refreshing a version @@ -154,6 +163,10 @@ scheduled/cron builds: - **One-shot** — run the **build** workflow manually (workflow_dispatch) and enter the upstream tag to build. +Either way the tag must appear in `ci/support-matrix.yaml`; if it does +not, the patch step fails the run rather than publishing an +un-de-branded or superseded build. + Configuration: - **Runner**: any Linux runner (`runs-on: ubuntu-latest`). The Go diff --git a/ci/apply-patches.sh b/ci/apply-patches.sh index 70c249d..3ab29fe 100644 --- a/ci/apply-patches.sh +++ b/ci/apply-patches.sh @@ -7,10 +7,17 @@ # to the tag name). Patches are git-format-patch files applied with # `git am --3way` in filename order — same model as scoutfs-notify. # +# The matrix is an ALLOWLIST: a tag that is not listed is refused, so a +# stray tag push or a mistyped dispatch cannot publish a build of a +# superseded (and possibly vulnerable) upstream release. Superseded +# series stay on disk under patches/archive// and can be rebuilt +# deliberately by passing PATCH_DIR. +# # Environment: # VGW_SRC_DIR source tree (default: src; set by clone-source.sh) # VGW_TAG upstream tag being built (required) -# PATCH_DIR override the patch directory (optional) +# PATCH_DIR build this directory instead of consulting the matrix; +# the escape hatch for patches/archive/ (optional) set -euo pipefail VGW_SRC_DIR="${VGW_SRC_DIR:-src}" @@ -19,19 +26,55 @@ VGW_SRC_DIR="${VGW_SRC_DIR:-src}" REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" SRC="$(cd "${VGW_SRC_DIR}" && pwd)" -# Resolve the patch directory: explicit override, else matrix lookup, -# else the tag name. -if [[ -z "${PATCH_DIR:-}" ]]; then - PATCH_NAME="" +MATRIX="${REPO_ROOT}/ci/support-matrix.yaml" + +# Emit " " for every row of the support matrix. Prefer +# yq, but fall back to a small awk parse of the fixed shape this repo +# writes: the stock golang CI container ships no yq, and the matrix is +# the allowlist, so it has to be readable either way. +matrix_entries() { if command -v yq >/dev/null 2>&1; then - PATCH_NAME="$(yq -r \ - ".versitygw[] | select(.tag == \"${VGW_TAG}\") | .patches // \"\"" \ - "${REPO_ROOT}/ci/support-matrix.yaml" 2>/dev/null || true)" + yq -r '.versitygw[] | "\(.tag) \(.patches // .tag)"' "${MATRIX}" 2>/dev/null && return + fi + sed 's/#.*//' "${MATRIX}" | awk ' + /^[[:space:]]*-[[:space:]]*tag:[[:space:]]*[^[:space:]]/ { + if (tag != "") print tag, (pat != "" ? pat : tag) + tag = $NF; pat = ""; next + } + /^[[:space:]]*patches:[[:space:]]*[^[:space:]]/ { if (tag != "") pat = $NF } + END { if (tag != "") print tag, (pat != "" ? pat : tag) } + ' +} + +# An explicit PATCH_DIR is a deliberate override (rebuilding something +# out of patches/archive/). Otherwise the matrix decides, and a tag it +# does not list is refused — never silently built as vanilla upstream. +if [[ -z "${PATCH_DIR:-}" ]]; then + PATCH_NAME="$(matrix_entries | awk -v t="${VGW_TAG}" '$1 == t { print $2; exit }')" + if [[ -z "${PATCH_NAME}" ]]; then + { + echo "ERROR: ${VGW_TAG} is not listed in ci/support-matrix.yaml." + echo " Supported: $(matrix_entries | awk '{print $1}' | tr '\n' ' ')" + echo " Superseded series are archived under patches/archive/ and" + echo " are deliberately not rebuilt — they predate the fixes in" + echo " the supported release. To build one anyway, pass it" + echo " explicitly:" + echo " PATCH_DIR=patches/archive/${VGW_TAG} bash ci/apply-patches.sh" + } >&2 + exit 1 fi - [[ -n "${PATCH_NAME}" && "${PATCH_NAME}" != "null" ]] || PATCH_NAME="${VGW_TAG}" PATCH_DIR="${REPO_ROOT}/patches/${PATCH_NAME}" fi +# git am runs from inside the source tree, so the patch paths have to be +# absolute: a relative PATCH_DIR (the documented archive invocation is +# one) would still glob here but no longer resolve after the cd below. +if [[ ! -d "${PATCH_DIR}" ]]; then + echo "ERROR: patch directory ${PATCH_DIR} does not exist." >&2 + exit 1 +fi +PATCH_DIR="$(cd "${PATCH_DIR}" && pwd)" + echo ">>> target versitygw tag: ${VGW_TAG}" echo ">>> patch directory: ${PATCH_DIR}" @@ -40,8 +83,10 @@ patches=( "${PATCH_DIR}"/*.patch ) shopt -u nullglob if (( ${#patches[@]} == 0 )); then - echo ">>> WARNING: no patches in ${PATCH_DIR} — building vanilla upstream" - exit 0 + echo "ERROR: no *.patch files in ${PATCH_DIR}." >&2 + echo " Refusing to continue — that would publish an un-de-branded" >&2 + echo " vanilla upstream build under this project's release name." >&2 + exit 1 fi echo ">>> applying ${#patches[@]} patch(es) with git am --3way:" diff --git a/ci/run.sh b/ci/run.sh index 84abd00..c544f4a 100644 --- a/ci/run.sh +++ b/ci/run.sh @@ -3,12 +3,12 @@ # Runs the same ci/ steps CI does, in a single shell so VGW_* env flows # between them. # -# VGW_TAG=v1.5.0 bash ci/run.sh +# VGW_TAG=v1.10.0 bash ci/run.sh # # Optional: point VGW_REPO_URL at a local clone for fast offline builds. set -euo pipefail -: "${VGW_TAG:?set VGW_TAG, e.g. VGW_TAG=v1.5.0 bash ci/run.sh}" +: "${VGW_TAG:?set VGW_TAG, e.g. VGW_TAG=v1.10.0 bash ci/run.sh}" export VGW_TAG export VGW_SRC_DIR="${VGW_SRC_DIR:-src}" diff --git a/ci/support-matrix.yaml b/ci/support-matrix.yaml index f524121..dd5288c 100644 --- a/ci/support-matrix.yaml +++ b/ci/support-matrix.yaml @@ -1,5 +1,9 @@ # s3-gateway build support matrix — single source of truth for CI. # +# This is an ALLOWLIST. ci/apply-patches.sh refuses to build any tag +# that is not listed here, so a stale or vulnerable upstream release +# cannot be published by a stray tag push or a mistyped dispatch. +# # Each entry maps an upstream versitygw release tag to the patch set # (a directory under patches/) applied on top of that tag before # building. Add a row when upstream cuts a release you want a @@ -8,17 +12,12 @@ # # The highest-version entry is treated as "latest". # +# Superseded series are kept under patches/archive// rather than +# deleted — see that directory's README to rebuild one deliberately. +# # tag upstream versitygw git tag (github.com/versity/versitygw) # patches directory under patches/ to apply (defaults to `tag`) versitygw: - tag: v1.10.0 patches: v1.10.0 - - tag: v1.8.0 - patches: v1.8.0 - - tag: v1.7.0 - patches: v1.7.0 - - tag: v1.6.0 - patches: v1.6.0 - - tag: v1.5.0 - patches: v1.5.0 diff --git a/docs/multi-version-pipeline.md b/docs/multi-version-pipeline.md index d4400cd..eb57d9a 100644 --- a/docs/multi-version-pipeline.md +++ b/docs/multi-version-pipeline.md @@ -24,7 +24,10 @@ Each build is four steps, run for a single upstream tag: from `ci/support-matrix.yaml` (`patches:` field, default = tag name) and applies `patches//*.patch` with `git am --whitespace=nowarn --3way`, in filename order. The `--3way` fallback is what lets a - series rebase across minor upstream drift. + series rebase across minor upstream drift. A tag the matrix does not + list is **refused** (exit 1), as is a patch directory that turns out + to be empty — either would otherwise publish an un-de-branded or + superseded build. 3. **`ci/build.sh`** — runs `goreleaser release --clean --skip=validate,publish` in `src/`, producing binaries + deb/rpm in @@ -42,8 +45,8 @@ Each build is four steps, run for a single upstream tag: ```yaml versitygw: - - tag: v1.5.0 - patches: v1.5.0 + - tag: v1.10.0 + patches: v1.10.0 ``` - `tag` — upstream git tag. @@ -55,6 +58,15 @@ versitygw: being built; with the default `patches//` naming it doubles as a human-readable record of which versions are maintained. +It is also an **allowlist**, which is the mechanism that keeps +superseded versions from being rebuilt: a tag absent from the matrix +makes `apply-patches.sh` exit 1. Only `PATCH_DIR` bypasses it, and only +explicitly — that is how an archived series is rebuilt on purpose. + +The matrix is read with `yq` when it is installed and with a small +`awk` parse otherwise, so the allowlist still applies in the stock +`golang` CI container, which has no `yq`. + ## Adding a new upstream version When upstream releases `vX.Y.Z`: @@ -80,10 +92,12 @@ When upstream releases `vX.Y.Z`: versitygw: - tag: vX.Y.Z patches: vX.Y.Z - - tag: v1.5.0 - patches: v1.5.0 ``` + Leave the previous entry in place only if you still want builds of + it. If the new release supersedes it — a security fix, say — retire + the old one at the same time (see *Retiring a version* below). + 3. **Commit and push** `ci/support-matrix.yaml` + `patches/vX.Y.Z/`. 4. **Build it** — either push a `vX.Y.Z` tag to this repo, or run the @@ -98,11 +112,31 @@ VGW_REPO_URL="/path/to/local/versitygw" VGW_TAG=vX.Y.Z bash ci/run.sh A clean `git am` plus a populated `src/dist/` means the series is good. -## Removing a version +## Retiring a version -Delete its `patches//` directory and its `ci/support-matrix.yaml` -entry. Releases already published in this repo stay until you delete -them by hand. +Drop its `ci/support-matrix.yaml` entry — that alone stops it being +built, since the matrix is the allowlist — and `git mv` its directory +into `patches/archive//` rather than deleting it, so the series +stays available for a deliberate rebuild: + +```bash +git mv patches/v1.8.0 patches/archive/v1.8.0 +# then remove the v1.8.0 entry from ci/support-matrix.yaml +``` + +Update the table in [`patches/archive/README.md`](../patches/archive/README.md) +and the supported-versions table in the top-level README to match. + +Releases already published in this repo are **not** affected: retiring +the patches stops future builds but leaves existing release assets +downloadable until you delete them by hand in Gitea. + +To rebuild an archived version deliberately, pass `PATCH_DIR` instead +of re-adding it to the matrix: + +```bash +PATCH_DIR=patches/archive/v1.8.0 VGW_TAG=v1.8.0 bash ci/apply-patches.sh +``` ## CI configuration @@ -122,6 +156,6 @@ them by hand. ## Release tagging Releases are created in this repo with the **upstream** tag name -(`v1.5.0`). If you need to ship a rebuild of the same upstream version +(`v1.10.0`). If you need to ship a rebuild of the same upstream version (e.g. a patch fix), set `RELEASE_TAG` in `ci/publish-release.sh`'s -environment to something like `v1.5.0-acg2` to avoid clobbering. +environment to something like `v1.10.0-acg2` to avoid clobbering. diff --git a/patches/archive/README.md b/patches/archive/README.md new file mode 100644 index 0000000..807f8a3 --- /dev/null +++ b/patches/archive/README.md @@ -0,0 +1,47 @@ +# Archived patch series + +Patch sets for upstream versitygw releases this project **no longer +builds**. They are kept for reference and reproducibility, not because +they are safe to ship. + +| Patch set | Contents | +|-----------|-----------------------------------------------------------| +| `v1.8.0/` | `0001-webui-debrand.patch`, `0002-webui-dark-theme.patch` | +| `v1.7.0/` | `0001-webui-debrand.patch`, `0002-webui-dark-theme.patch` | +| `v1.6.0/` | `0001-webui-debrand.patch`, `0002-webui-dark-theme.patch` | +| `v1.5.0/` | `0001-webui-debrand.patch` | + +## Why these are not built + +`v1.10.0` carries upstream CVE fixes that these releases predate. +Building them would publish known-vulnerable gateways under this +project's release name, so their tags were removed from +`ci/support-matrix.yaml`. That matrix is an allowlist — +`ci/apply-patches.sh` refuses a tag it does not list, so neither a tag +push nor a workflow dispatch can rebuild one by accident. + +Releases already published for these versions are **not** removed by +archiving the patches. Delete them by hand in Gitea if they should no +longer be downloadable. + +## Rebuilding one anyway + +If you genuinely need an archived build — reproducing an old report, +bisecting a regression — point `PATCH_DIR` at it instead of re-adding +it to the matrix: + +```bash +VGW_TAG=v1.8.0 bash ci/clone-source.sh +PATCH_DIR=patches/archive/v1.8.0 VGW_TAG=v1.8.0 bash ci/apply-patches.sh +VGW_TAG=v1.8.0 bash ci/build.sh +``` + +Don't publish the result to a release unless you mean to ship a +vulnerable build. + +## Un-archiving + +To return a version to the supported set, `git mv` its directory back +to `patches//` and add its row to `ci/support-matrix.yaml`. Rebase +the series onto the tag first if upstream has moved — see +[../../docs/multi-version-pipeline.md](../../docs/multi-version-pipeline.md). diff --git a/patches/v1.5.0/0001-webui-debrand.patch b/patches/archive/v1.5.0/0001-webui-debrand.patch similarity index 100% rename from patches/v1.5.0/0001-webui-debrand.patch rename to patches/archive/v1.5.0/0001-webui-debrand.patch diff --git a/patches/v1.6.0/0001-webui-debrand.patch b/patches/archive/v1.6.0/0001-webui-debrand.patch similarity index 100% rename from patches/v1.6.0/0001-webui-debrand.patch rename to patches/archive/v1.6.0/0001-webui-debrand.patch diff --git a/patches/v1.6.0/0002-webui-dark-theme.patch b/patches/archive/v1.6.0/0002-webui-dark-theme.patch similarity index 100% rename from patches/v1.6.0/0002-webui-dark-theme.patch rename to patches/archive/v1.6.0/0002-webui-dark-theme.patch diff --git a/patches/v1.7.0/0001-webui-debrand.patch b/patches/archive/v1.7.0/0001-webui-debrand.patch similarity index 100% rename from patches/v1.7.0/0001-webui-debrand.patch rename to patches/archive/v1.7.0/0001-webui-debrand.patch diff --git a/patches/v1.7.0/0002-webui-dark-theme.patch b/patches/archive/v1.7.0/0002-webui-dark-theme.patch similarity index 100% rename from patches/v1.7.0/0002-webui-dark-theme.patch rename to patches/archive/v1.7.0/0002-webui-dark-theme.patch diff --git a/patches/v1.8.0/0001-webui-debrand.patch b/patches/archive/v1.8.0/0001-webui-debrand.patch similarity index 100% rename from patches/v1.8.0/0001-webui-debrand.patch rename to patches/archive/v1.8.0/0001-webui-debrand.patch diff --git a/patches/v1.8.0/0002-webui-dark-theme.patch b/patches/archive/v1.8.0/0002-webui-dark-theme.patch similarity index 100% rename from patches/v1.8.0/0002-webui-dark-theme.patch rename to patches/archive/v1.8.0/0002-webui-dark-theme.patch