# s3-gateway Downstream build pipeline that produces **de-branded [versitygw](https://github.com/versity/versitygw) releases** — the upstream Versity S3 Gateway with Versity trademarks and logos removed from the admin web UI. Same pattern as [`scoutfs-build`](../scoutfs-build): pull an upstream release **tag**, apply our **patch series** scoped to that tag, build with upstream's GoReleaser, and publish to **Gitea Releases**. Artifact names are intentionally **unchanged** — the binary stays `versitygw` and the deb/rpm packages stay `versitygw`, so builds are drop-in compatible with existing configs, systemd units, and the `versitygw` CLI. Only the embedded web UI is rebranded to "S3 Gateway". The v1.11.0 series also carries one behavioural patch, **owner-scoped bucket access**: a bucket's ACL and policy may name only the owner's own keys, public access is read-only, and deleting a bucket or changing its versioning, object lock or ownership controls is left to the gateway's root and admins. See [docs/owner-scoped-access.md](docs/owner-scoped-access.md). ``` upstream tag (vX.Y.Z) this repo github.com/versity/versitygw patches//*.patch │ │ ▼ ▼ ci/clone-source.sh ───► ci/apply-patches.sh ───► ci/build.sh ───► ci/publish-release.sh (shallow clone tag) (git am --3way) (goreleaser) (Gitea release assets) ``` `ci/support-matrix.yaml` records which upstream versions have a curated patch set and maps each to its patch directory. ## Layout ``` s3-gateway/ ├── ci/ │ ├── support-matrix.yaml # upstream tag -> patch directory (registry) │ ├── clone-source.sh # shallow-clone upstream versitygw at a tag │ ├── apply-patches.sh # git am --3way patches//*.patch │ ├── build.sh # goreleaser release (binaries + deb/rpm) │ ├── publish-release.sh # upload dist/* to a Gitea release │ └── run.sh # local end-to-end: clone + patch + build one tag ├── patches/ │ ├── v1.11.0/ # the supported series │ │ ├── 0001-webui-debrand.patch │ │ ├── 0002-webui-dark-theme.patch │ │ └── 0003-owner-scoped-access.patch │ └── archive/ # superseded; kept, not built │ ├── v1.10.0/ │ ├── v1.8.0/ │ ├── v1.7.0/ │ ├── v1.6.0/ │ └── v1.5.0/ ├── tests/owner-scope/ # leil-rig proof test for 0003 (proof.sh, s3req.py) ├── .gitea/workflows/build.yml └── docs/ ├── multi-version-pipeline.md └── owner-scoped-access.md # what 0003 does, and its proof test ``` ## Build locally Prerequisites: `git`, `go`, and [`goreleaser`](https://goreleaser.com). ```bash # Build one upstream tag end-to-end → artifacts in src/dist/ VGW_TAG=v1.11.0 bash ci/run.sh # Fast offline build against a local upstream clone: VGW_REPO_URL="/path/to/versitygw" VGW_TAG=v1.11.0 bash ci/run.sh ``` Or run the steps individually: ```bash VGW_TAG=v1.11.0 bash ci/clone-source.sh # → src/ VGW_TAG=v1.11.0 bash ci/apply-patches.sh # git am the series onto src/ VGW_TAG=v1.11.0 bash ci/build.sh # goreleaser → src/dist/ ``` `src/` and `src/dist/` are gitignored. ## Patches Patch sets live in `patches//` as `git format-patch` files and are 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.11.0` | `v1.11.0/` | `0001-webui-debrand.patch`, `0002-webui-dark-theme.patch`, `0003-owner-scoped-access.patch` | **v1.11.0 is the only supported version.** It supersedes `v1.10.0` with 42 further upstream fixes, so the older series — `v1.10.0`, `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. Note that protection against CVE-2026-111943 (the Go `crypto/x509` eMudhra distrust, announced in upstream's v1.10.0 release notes) comes from the **Go toolchain** these binaries are built with, not from the upstream tag. Build with Go 1.26.10 / 1.27.3 or later. ### What the de-brand patch does - Sidebar/login logos → a plain **"S3 Gateway"** wordmark. - Page titles: "VersityGW Dashboard" → "Dashboard" (Buckets, Users, Explorer and the v1.8.0 IAM pages too). - Browser tab titles → "S3 Gateway – …". - Removes the sidebar **Resources** section (external `github.com/versity` links). - Replaces the Versity favicon with a neutral SVG; deletes the Versity logo PNGs. - Removes the visible "© Versity Software Inc." footer from the login page. Deliberately **kept** (license compliance): the per-file Apache-2.0 license headers — which carry the upstream copyright notice as an HTML comment at the top of each file — and the `NOTICE` file. Upstream is Apache-2.0; the de-brand strips the visible Versity *trademarks* (logos, wordmarks, and the login footer) while preserving the *copyright* notice in the source headers. ### What the dark-theme patch does `0002-webui-dark-theme.patch` re-skins the admin UI to match the **kanrisha** web app — a near-black palette with indigo/blue accents — without touching the page markup. It works entirely through the two shared asset files plus bundled fonts: - `tailwind-config.js` — remaps the named color tokens (`surface`, `charcoal`, `primary`, `accent`) onto the dark palette and switches the sans/mono fonts to Inter / JetBrains Mono. - `theme.css` — sets the dark body, restyles the bespoke components (sidebar active state, dropdowns, toggles, checkboxes, file rows, login) in indigo, and adds an override layer that flips the built-in Tailwind white/gray/status utilities the markup uses. - `fonts.css` + `assets/fonts/*.woff2` — self-hosts Inter and JetBrains Mono (variable, latin + latin-ext) so the UI stays fully offline, matching the existing self-hosted Roboto. The only markup change is the login page's light background gradient, which becomes a solid dark surface. It is part of the supported `v1.11.0` series, and of every archived set except `v1.5.0`. ### What the owner-scoped access patch does `0003-owner-scoped-access.patch` changes authorization in `auth/` (plus one call in `s3api/controllers/bucket-put.go`). A bucket's owner is the account its ACL names; the owner's *keys* are every account with that account's `UserID`. - **Only the owner's keys.** `PutBucketPolicy`, `PutBucketAcl` (body and grant headers) and `CreateBucket`'s grant headers — so also `versitygw admin create-bucket --grant-*` — refuse any account whose `UserID` is not the owner's. Root is refused too. - **Public means read.** `all-users` gets `READ` only; a policy may allow `"*"` only `s3:GetObject` and `s3:ListBucket`. `public-read-write` and every other public grant are refused. - **A policy adds to the ACL.** An `Allow` from either is enough and a policy `Deny` wins — except that the owner's key is never denied. - **Root/admin only:** `DeleteBucket`, `PutBucketVersioning`, `PutObjectLockConfiguration`, `PutBucketOwnershipControls` (and `DeleteBucketOwnershipControls`). - An ACL holding the `all-users` `READ` grant can be written back unchanged (upstream answers `500`). It is the series' only patch outside `webui/`, and it has unit tests: after a rebase run `go test ./auth/ ./s3api/...` (on Linux — on Windows upstream's standalone-IAM, posix and CLI tests fail with or without the series) and the leil-rig proof test in [docs/owner-scoped-access.md](docs/owner-scoped-access.md#proving-the-patch-is-in-on-leil-rig). ## Adding / refreshing a version When upstream cuts a new release you want a build of, you rebase the patch series onto it and add a patch directory. See [docs/multi-version-pipeline.md](docs/multi-version-pipeline.md) for the full recipe. In short: ```bash # Rebase the series onto the new tag, re-export into patches/vX.Y.Z/ git clone https://github.com/versity/versitygw && cd versitygw git -c core.autocrlf=false checkout -b debrand vX.Y.Z git am --3way /path/to/s3-gateway/patches//*.patch # resolve any conflicts git format-patch --binary vX.Y.Z..HEAD -o /path/to/s3-gateway/patches/vX.Y.Z/ ``` Then add the tag to `ci/support-matrix.yaml`, commit, and build it (see below). ## CI `.gitea/workflows/build.yml` builds **one version per run** and publishes it to a Gitea release in this repo. It triggers two ways — no scheduled/cron builds: - **By new tag** — push a `vX.Y.Z` tag to this repo; CI builds upstream versitygw `vX.Y.Z` with `patches/vX.Y.Z/`. - **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 toolchain comes from the `golang` container, so the runner only needs Docker. - **Secret**: `TOKEN_GITEA` — a Gitea PAT with **write:repository** (clones this repo + creates releases / uploads assets). - **Variable (optional)**: `VGW_REPO_URL` to override the upstream URL. ## License Apache-2.0, matching versitygw upstream. Upstream copyright and `NOTICE` are retained in every build.