Make the tagged-release path actually work on Gitea #65

Closed
opened 2026-08-09 03:41:28 +02:00 by clawbot · 2 comments
Collaborator

vaultik cannot currently cut a tagged release. Three independent problems.

1. goreleaser is configured for GitHub, but the repo is on Gitea

git remote -v is git@git.eeqj.de:sneak/vaultik.git. .goreleaser.yaml
has a release: block (line 54) with no gitea_urls: section, so
goreleaser defaults to the GitHub API and goreleaser release will fail
or publish to the wrong place. This is the hardest blocker on the release
path.

2. No tags exist, and the version string is contradictory

git tag -l is empty, so goreleaser cannot derive {{.Version}} and
only --snapshot mode can run today. Meanwhile four sources disagree:

Source Value
Makefile:4 VERSION := 1.0.0-rc.1
TODO.md:54 "cut v0.1.0"
TODO.md:13 status "pre-1.0"
internal/globals/globals.go:10-19 defaults to dev

make vaultik bakes 1.0.0-rc.1 into every local build regardless of git
state, so a dev binary misreports its own version.

3. No release automation

.gitea/workflows/ contains only check.yml. Releases would be cut by
hand from a laptop. goreleaser is also not installed by
script/bootstrap.

Definition of done

  1. .goreleaser.yaml has a correct gitea_urls: block pointing at
    https://git.eeqj.de/api/v1 (and the matching download URL), so
    goreleaser release publishes to the Gitea release API.
  2. Version derives from the git tag rather than a hardcoded Makefile
    constant. An untagged build reports something honest (dev plus commit
    sha), not a fabricated 1.0.0-rc.1. vaultik version on a tagged
    build prints that tag.
  3. TODO.md, the Makefile, and internal/globals no longer contradict
    each other about the version.
  4. A tag-triggered .gitea/workflows/release.yml runs goreleaser with a
    scoped Gitea token, so releases are reproducible from CI rather than a
    workstation. Document the required secret.
  5. script/bootstrap installs goreleaser, and make release /
    make release-snapshot become thin script/ shims like every other
    target, per the scripts-to-rule-them-all pattern the README describes
    at :559-587. They are currently the only Makefile targets that are not
    shims.
  6. make release-snapshot succeeds end to end, producing archives for
    linux,darwin x amd64,arm64 with checksums.
  7. make check green.

Explicitly out of scope

Artifact signing, SBOM generation, and reproducible-build settings — file
separately if wanted for 1.0. Shipping shell completions and a man page in
the release archive is covered by its own issue.

vaultik cannot currently cut a tagged release. Three independent problems. ## 1. goreleaser is configured for GitHub, but the repo is on Gitea `git remote -v` is `git@git.eeqj.de:sneak/vaultik.git`. `.goreleaser.yaml` has a `release:` block (line 54) with **no `gitea_urls:` section**, so goreleaser defaults to the GitHub API and `goreleaser release` will fail or publish to the wrong place. This is the hardest blocker on the release path. ## 2. No tags exist, and the version string is contradictory `git tag -l` is empty, so goreleaser cannot derive `{{.Version}}` and only `--snapshot` mode can run today. Meanwhile four sources disagree: | Source | Value | | --- | --- | | `Makefile:4` | `VERSION := 1.0.0-rc.1` | | `TODO.md:54` | "cut v0.1.0" | | `TODO.md:13` | status "pre-1.0" | | `internal/globals/globals.go:10-19` | defaults to `dev` | `make vaultik` bakes `1.0.0-rc.1` into every local build regardless of git state, so a dev binary misreports its own version. ## 3. No release automation `.gitea/workflows/` contains only `check.yml`. Releases would be cut by hand from a laptop. `goreleaser` is also not installed by `script/bootstrap`. ## Definition of done 1. `.goreleaser.yaml` has a correct `gitea_urls:` block pointing at `https://git.eeqj.de/api/v1` (and the matching download URL), so `goreleaser release` publishes to the Gitea release API. 2. Version derives from the git tag rather than a hardcoded `Makefile` constant. An untagged build reports something honest (`dev` plus commit sha), not a fabricated `1.0.0-rc.1`. `vaultik version` on a tagged build prints that tag. 3. `TODO.md`, the `Makefile`, and `internal/globals` no longer contradict each other about the version. 4. A tag-triggered `.gitea/workflows/release.yml` runs goreleaser with a scoped Gitea token, so releases are reproducible from CI rather than a workstation. Document the required secret. 5. `script/bootstrap` installs `goreleaser`, and `make release` / `make release-snapshot` become thin `script/` shims like every other target, per the scripts-to-rule-them-all pattern the README describes at :559-587. They are currently the only Makefile targets that are not shims. 6. `make release-snapshot` succeeds end to end, producing archives for `linux,darwin x amd64,arm64` with checksums. 7. `make check` green. ## Explicitly out of scope Artifact signing, SBOM generation, and reproducible-build settings — file separately if wanted for 1.0. Shipping shell completions and a man page in the release archive is covered by its own issue.
clawbot added this to the 1.0.0 milestone 2026-08-09 03:41:28 +02:00
Author
Collaborator

Implementation plan (branch fix-release-pipeline, PR against main):

  1. .goreleaser.yaml — add a top-level gitea_urls: block (api: https://git.eeqj.de/api/v1, download: https://git.eeqj.de) so the release publishes through the Gitea release API instead of defaulting to GitHub.

  2. Version from git, not a constant — new script/version (a sibling of script/projectname) is the single source of the version string:

    • HEAD is exactly on a tag: print that tag with a leading v stripped, so a make build and a goreleaser build of the same commit report the identical string (goreleaser's .Version strips it, and the archive names use it).
    • otherwise: dev-<12-char sha>, plus -dirty when the tree is dirty. Nothing fabricated.

    Makefile:4 VERSION := 1.0.0-rc.1 becomes VERSION := $(shell script/version). internal/cli/version.go currently gates its "this is a development build" notice on globals.Version == "dev" exactly; that becomes a globals.IsDevBuild() predicate so the notice still fires for dev-<sha>. Goreleaser's snapshot.version_template is changed from {{ incpatch .Version }}-next (which invents a version number from a tag that does not exist) to the same honest dev-<shortcommit> shape.

  3. Consistency — with the Makefile deriving from git, the remaining contradiction is TODO.md, which says both "cut v0.1.0" and "pre-1.0" while the issue milestone is 1.0.0. TODO.md gets updated in the same commit per its own Workflow section, and its next step will name the milestone rather than a second, different version number. internal/globals keeps dev/unknown defaults, which are already honest.

  4. .gitea/workflows/release.yml — triggered on push: of v* tags, actions/checkout pinned by commit sha with fetch-depth: 0 (goreleaser needs full history and tags), then script/release with GITEA_TOKEN from a repo secret. The required secret name will be documented in README.md along with the token scopes it needs.

  5. script/bootstrap installs goreleaser; make release / make release-snapshot become shims. Per REPO_POLICIES.md, the install is a specific GitHub release archive verified against a hardcoded sha256 — never curl | sh, never @latest. script/bootstrap hard-fails without Docker (by design, since it gates script/lint), and the release runner should not need Docker, so the installer lives in its own idempotent script/install-goreleaser that script/bootstrap calls and the release workflow calls directly. script/release and script/release-snapshot resolve the binary the same way script/lint resolves its linter: an exact-pinned-version goreleaser on PATH is used, otherwise the repo-local installed copy, otherwise a loud failure naming script/bootstrap. /dist/ gets gitignored.

  6. Verificationmake release-snapshot end to end (4 archives + checksums.txt), vaultik version from an untagged build, make check and script/cibuild.

Out of scope per the issue and confirmed with the requester: signing, SBOM, reproducible-build settings, completions, man page. No git tag will be created or pushed; tagging is the owner's call, so version derivation is exercised via --snapshot and by inspecting the resolved ldflags.

Implementation plan (branch `fix-release-pipeline`, PR against `main`): 1. **`.goreleaser.yaml`** — add a top-level `gitea_urls:` block (`api: https://git.eeqj.de/api/v1`, `download: https://git.eeqj.de`) so the release publishes through the Gitea release API instead of defaulting to GitHub. 2. **Version from git, not a constant** — new `script/version` (a sibling of `script/projectname`) is the single source of the version string: - `HEAD` is exactly on a tag: print that tag with a leading `v` stripped, so a `make` build and a goreleaser build of the same commit report the identical string (goreleaser's `.Version` strips it, and the archive names use it). - otherwise: `dev-<12-char sha>`, plus `-dirty` when the tree is dirty. Nothing fabricated. `Makefile:4` `VERSION := 1.0.0-rc.1` becomes `VERSION := $(shell script/version)`. `internal/cli/version.go` currently gates its "this is a development build" notice on `globals.Version == "dev"` exactly; that becomes a `globals.IsDevBuild()` predicate so the notice still fires for `dev-<sha>`. Goreleaser's `snapshot.version_template` is changed from `{{ incpatch .Version }}-next` (which invents a version number from a tag that does not exist) to the same honest `dev-<shortcommit>` shape. 3. **Consistency** — with the `Makefile` deriving from git, the remaining contradiction is `TODO.md`, which says both "cut v0.1.0" and "pre-1.0" while the issue milestone is 1.0.0. `TODO.md` gets updated in the same commit per its own Workflow section, and its next step will name the milestone rather than a second, different version number. `internal/globals` keeps `dev`/`unknown` defaults, which are already honest. 4. **`.gitea/workflows/release.yml`** — triggered on `push:` of `v*` tags, `actions/checkout` pinned by commit sha with `fetch-depth: 0` (goreleaser needs full history and tags), then `script/release` with `GITEA_TOKEN` from a repo secret. The required secret name will be documented in `README.md` along with the token scopes it needs. 5. **`script/bootstrap` installs goreleaser; `make release` / `make release-snapshot` become shims.** Per `REPO_POLICIES.md`, the install is a specific GitHub release archive verified against a hardcoded sha256 — never `curl | sh`, never `@latest`. `script/bootstrap` hard-fails without Docker (by design, since it gates `script/lint`), and the release runner should not need Docker, so the installer lives in its own idempotent `script/install-goreleaser` that `script/bootstrap` calls and the release workflow calls directly. `script/release` and `script/release-snapshot` resolve the binary the same way `script/lint` resolves its linter: an exact-pinned-version `goreleaser` on `PATH` is used, otherwise the repo-local installed copy, otherwise a loud failure naming `script/bootstrap`. `/dist/` gets gitignored. 6. **Verification** — `make release-snapshot` end to end (4 archives + `checksums.txt`), `vaultik version` from an untagged build, `make check` and `script/cibuild`. Out of scope per the issue and confirmed with the requester: signing, SBOM, reproducible-build settings, completions, man page. No git tag will be created or pushed; tagging is the owner's call, so version derivation is exercised via `--snapshot` and by inspecting the resolved ldflags.
Author
Collaborator

Implemented in PR #104 (branch fix-release-pipeline, one commit).

All seven definition-of-done items are done. The full writeup is on the PR; the short version:

  1. .goreleaser.yaml has gitea_urls: (api: https://git.eeqj.de/api/v1, download: https://git.eeqj.de).
  2. New script/version derives the version from git — exact tag with a leading v stripped, else dev-<12-char sha>, with -dirty on modified tracked files. Makefile:4 now calls it. goreleaser's snapshot template stopped inventing a release number too ({{ incpatch .Version }}-next became the same dev-<sha>).
  3. Makefile, internal/globals and TODO.md now agree; TODO.md's next step names one version target instead of a third one.
  4. .gitea/workflows/release.yml triggers on v* tags with fetch-depth: 0, and needs one repository Actions secret: RELEASE_TOKEN, a Gitea token with write:repository scope, passed to goreleaser as GITEA_TOKEN. Documented in the new README.md "releasing" section.
  5. script/install-goreleaser installs the pinned v2.17.1 from a sha256-verified archive and is called by script/bootstrap; make release / make release-snapshot are now shims to script/release / script/release-snapshot.
  6. make release-snapshot succeeds: four archives (linux,darwin x amd64,arm64) plus checksums.txt.
  7. make check green (14 ok, none cached, 0 issues.), and script/cibuild exits 0 with the three check layers actually executing.

One consequence found while doing this and worth a look on review: internal/cli/version.go gated its development-build notice on the version being exactly dev, so as soon as untagged builds carried a commit sha the notice would have gone quiet and an unreleased binary would have read as a release. That gate is now a tested predicate.

No tag was created or pushed — tag handling was exercised in a throwaway repository instead, since cutting the tag is yours to do.

Implemented in [PR #104](https://git.eeqj.de/sneak/vaultik/pulls/104) (branch `fix-release-pipeline`, one commit). All seven definition-of-done items are done. The full writeup is on the PR; the short version: 1. `.goreleaser.yaml` has `gitea_urls:` (`api: https://git.eeqj.de/api/v1`, `download: https://git.eeqj.de`). 2. New `script/version` derives the version from git — exact tag with a leading `v` stripped, else `dev-<12-char sha>`, with `-dirty` on modified tracked files. `Makefile:4` now calls it. goreleaser's snapshot template stopped inventing a release number too (`{{ incpatch .Version }}-next` became the same `dev-<sha>`). 3. `Makefile`, `internal/globals` and `TODO.md` now agree; `TODO.md`'s next step names one version target instead of a third one. 4. `.gitea/workflows/release.yml` triggers on `v*` tags with `fetch-depth: 0`, and needs one repository Actions secret: **`RELEASE_TOKEN`**, a Gitea token with `write:repository` scope, passed to goreleaser as `GITEA_TOKEN`. Documented in the new `README.md` "releasing" section. 5. `script/install-goreleaser` installs the pinned v2.17.1 from a sha256-verified archive and is called by `script/bootstrap`; `make release` / `make release-snapshot` are now shims to `script/release` / `script/release-snapshot`. 6. `make release-snapshot` succeeds: four archives (`linux,darwin` x `amd64,arm64`) plus `checksums.txt`. 7. `make check` green (14 `ok`, none cached, `0 issues.`), and `script/cibuild` exits 0 with the three check layers actually executing. One consequence found while doing this and worth a look on review: `internal/cli/version.go` gated its development-build notice on the version being exactly `dev`, so as soon as untagged builds carried a commit sha the notice would have gone quiet and an unreleased binary would have read as a release. That gate is now a tested predicate. No tag was created or pushed — tag handling was exercised in a throwaway repository instead, since cutting the tag is yours to do.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/vaultik#65