Make the tagged-release path work on Gitea (closes #65) #104
@@ -3,6 +3,8 @@
|
|||||||
*.md
|
*.md
|
||||||
LICENSE
|
LICENSE
|
||||||
vaultik
|
vaultik
|
||||||
|
dist
|
||||||
|
.tool
|
||||||
coverage.out
|
coverage.out
|
||||||
coverage.html
|
coverage.html
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
60
.gitea/workflows/release.yml
Normal file
60
.gitea/workflows/release.yml
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
name: release
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ["v*"]
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
# actions/checkout v4, 2024-09-16
|
||||||
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
||||||
|
with:
|
||||||
|
# goreleaser needs the tags and the full history: the version
|
||||||
|
# it stamps comes from the tag, and the changelog comes from
|
||||||
|
# the commits since the previous one. A shallow checkout
|
||||||
|
# silently produces a mislabelled release.
|
||||||
|
fetch-depth: 0
|
||||||
|
# goreleaser is not a compiler: it shells out to `go` for the
|
||||||
|
# `before:` hook and for every one of the four cross-compiles.
|
||||||
|
# Nothing else in this repo puts a Go toolchain on the runner --
|
||||||
|
# check.yml runs script/cibuild, which does all of its work inside
|
||||||
|
# the digest-pinned Dockerfile images -- so without this step the
|
||||||
|
# release either fails at the before-hook or, worse, ships binaries
|
||||||
|
# built by whatever unpinned Go the runner happens to carry.
|
||||||
|
# REPO_POLICIES.md requires every external reference to be pinned,
|
||||||
|
# and script/release already refuses a goreleaser that is not the
|
||||||
|
# pinned build; the compiler that actually produces the artifacts
|
||||||
|
# is the last thing that should be exempt from that.
|
||||||
|
#
|
||||||
|
# go-version-file rather than a literal: go.mod's `go 1.26.1` is
|
||||||
|
# the single source of truth for the toolchain, the same way the
|
||||||
|
# Dockerfile FROM line is the single source of truth for the
|
||||||
|
# linter version that script/lint enforces. It is a three-component
|
||||||
|
# version, so setup-go resolves it exactly -- no silent drift onto
|
||||||
|
# a newer patch release.
|
||||||
|
#
|
||||||
|
# actions/setup-go v5.6.0, 2025-12-15. Pinned by commit sha, like
|
||||||
|
# the checkout above. v5.x is a node20 action, matching the node20
|
||||||
|
# actions/checkout v4 already in use here; the v6/v7 line requires
|
||||||
|
# a node24 runner, which this Gitea runner has never been asked
|
||||||
|
# for and cannot be assumed to provide.
|
||||||
|
- name: Install Go
|
||||||
|
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff
|
||||||
|
with:
|
||||||
|
go-version-file: go.mod
|
||||||
|
# setup-go's module cache needs a runner-side cache backend.
|
||||||
|
# A release is cut rarely and a cold module download costs
|
||||||
|
# seconds; a release failing because a cache service is absent
|
||||||
|
# costs a re-tag. Off, deliberately.
|
||||||
|
cache: false
|
||||||
|
- name: Install goreleaser
|
||||||
|
run: script/install-goreleaser
|
||||||
|
- name: Release
|
||||||
|
run: script/release
|
||||||
|
env:
|
||||||
|
# RELEASE_TOKEN is a repository Actions secret: a Gitea access
|
||||||
|
# token with write access to this repository's releases (scope
|
||||||
|
# write:repository), owned by an account that can publish here.
|
||||||
|
# It is deliberately not the runner's automatic token, which is
|
||||||
|
# not guaranteed to carry that scope.
|
||||||
|
GITEA_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
||||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1,6 +1,12 @@
|
|||||||
# Binary
|
# Binary
|
||||||
/vaultik
|
/vaultik
|
||||||
|
|
||||||
|
# goreleaser output
|
||||||
|
/dist/
|
||||||
|
|
||||||
|
# Locally installed pinned tools (script/install-goreleaser)
|
||||||
|
/.tool/
|
||||||
|
|
||||||
# Test artifacts
|
# Test artifacts
|
||||||
*.out
|
*.out
|
||||||
*.test
|
*.test
|
||||||
|
|||||||
@@ -2,6 +2,13 @@ version: 2
|
|||||||
|
|
||||||
project_name: vaultik
|
project_name: vaultik
|
||||||
|
|
||||||
|
# This repo lives on Gitea, not GitHub. Without this block goreleaser
|
||||||
|
# talks to the GitHub API by default and a `goreleaser release` either
|
||||||
|
# fails outright or publishes somewhere nobody is looking.
|
||||||
|
gitea_urls:
|
||||||
|
api: https://git.eeqj.de/api/v1
|
||||||
|
download: https://git.eeqj.de
|
||||||
|
|
||||||
before:
|
before:
|
||||||
hooks:
|
hooks:
|
||||||
- go mod tidy
|
- go mod tidy
|
||||||
@@ -37,8 +44,14 @@ checksum:
|
|||||||
name_template: "checksums.txt"
|
name_template: "checksums.txt"
|
||||||
algorithm: sha256
|
algorithm: sha256
|
||||||
|
|
||||||
|
# A snapshot is not a release and must not name itself like one. The
|
||||||
|
# previous `{{ incpatch .Version }}-next` derived a plausible-looking
|
||||||
|
# release number from the last tag -- and with no tags in the repo at
|
||||||
|
# all, from goreleaser's fabricated v0.0.0. This produces the same
|
||||||
|
# string script/version produces for an untagged build, so a snapshot
|
||||||
|
# binary and a `make vaultik` binary of the same clean commit agree.
|
||||||
snapshot:
|
snapshot:
|
||||||
version_template: "{{ incpatch .Version }}-next"
|
version_template: "dev-{{ slice .FullCommit 0 12 }}"
|
||||||
|
|
||||||
changelog:
|
changelog:
|
||||||
sort: asc
|
sort: asc
|
||||||
|
|||||||
21
Makefile
21
Makefile
@@ -1,7 +1,20 @@
|
|||||||
.PHONY: all bootstrap setup check test lint lint-fix fmt fmt-check build clean deps test-coverage local install release release-snapshot docker hooks
|
.PHONY: all bootstrap setup check test lint lint-fix fmt fmt-check build clean deps test-coverage local install release release-snapshot docker hooks
|
||||||
|
|
||||||
# Version number
|
# Version number, derived from git by script/version -- the tag when
|
||||||
VERSION := 1.0.0-rc.1
|
# HEAD is on one, otherwise dev-<sha>. This used to be a hardcoded
|
||||||
|
# constant, which meant every local build claimed to be a release that
|
||||||
|
# had never been tagged.
|
||||||
|
VERSION := $(shell script/version)
|
||||||
|
|
||||||
|
# $(shell) discards exit status, so a script/version that is missing,
|
||||||
|
# non-executable or broken would otherwise leave VERSION empty and every
|
||||||
|
# binary built here would print "vaultik " with no version at all. A
|
||||||
|
# build that cannot determine what it is must not produce an artifact.
|
||||||
|
ifeq ($(strip $(VERSION)),)
|
||||||
|
$(error script/version produced no version string; a build that cannot \
|
||||||
|
determine its version will not be made. Check that script/version exists \
|
||||||
|
and is executable)
|
||||||
|
endif
|
||||||
|
|
||||||
# Build variables
|
# Build variables
|
||||||
GIT_REVISION := $(shell git rev-parse HEAD 2>/dev/null || echo "unknown")
|
GIT_REVISION := $(shell git rev-parse HEAD 2>/dev/null || echo "unknown")
|
||||||
@@ -87,11 +100,11 @@ install: vaultik
|
|||||||
|
|
||||||
# Build and publish release artifacts (linux/darwin × amd64/arm64) via goreleaser.
|
# Build and publish release artifacts (linux/darwin × amd64/arm64) via goreleaser.
|
||||||
release:
|
release:
|
||||||
goreleaser release --clean
|
@script/release
|
||||||
|
|
||||||
# Dry-run a release build without publishing or tagging.
|
# Dry-run a release build without publishing or tagging.
|
||||||
release-snapshot:
|
release-snapshot:
|
||||||
goreleaser release --clean --snapshot
|
@script/release-snapshot
|
||||||
|
|
||||||
# Build Docker image.
|
# Build Docker image.
|
||||||
docker:
|
docker:
|
||||||
|
|||||||
85
README.md
85
README.md
@@ -606,6 +606,19 @@ them. We provide:
|
|||||||
`script/bootstrap`, then `script/install-precommit`
|
`script/bootstrap`, then `script/install-precommit`
|
||||||
* `script/projectname` — print the project name (used for the Docker
|
* `script/projectname` — print the project name (used for the Docker
|
||||||
image tag)
|
image tag)
|
||||||
|
* `script/version` — print the version string to bake into the binary.
|
||||||
|
The `Makefile`'s `LDFLAGS` call this; it is the single source of truth
|
||||||
|
for the version. See [releasing](#releasing) for the rules.
|
||||||
|
* `script/install-goreleaser` — install the pinned `goreleaser` into
|
||||||
|
`.tool/bin` from a sha256-verified release archive. Idempotent, and
|
||||||
|
called by `script/bootstrap`; the release workflow calls it directly
|
||||||
|
because it needs `goreleaser` but not the Docker daemon
|
||||||
|
`script/bootstrap` insists on.
|
||||||
|
* `script/release` — cross-compile and publish the release artifacts
|
||||||
|
with the pinned `goreleaser`. Refuses a `goreleaser` on `PATH` whose
|
||||||
|
version is not the pinned one, on the same reasoning as `script/lint`.
|
||||||
|
* `script/release-snapshot` — the same build with no publishing and no
|
||||||
|
tagging, into `./dist`
|
||||||
* `script/test` — run the test suite (verbose rerun on failure). This
|
* `script/test` — run the test suite (verbose rerun on failure). This
|
||||||
runs *everything*: there is no separate integration target and no
|
runs *everything*: there is no separate integration target and no
|
||||||
build-tagged subset held back, so the full round-trip tests in
|
build-tagged subset held back, so the full round-trip tests in
|
||||||
@@ -669,6 +682,78 @@ them. We provide:
|
|||||||
* `script/install-precommit` — install the git pre-commit hook that
|
* `script/install-precommit` — install the git pre-commit hook that
|
||||||
runs `script/precommit`
|
runs `script/precommit`
|
||||||
|
|
||||||
|
## releasing
|
||||||
|
|
||||||
|
### version numbers
|
||||||
|
|
||||||
|
The version a binary reports comes from git, not from a constant in a
|
||||||
|
file. `script/version` decides it, and everything that stamps a binary
|
||||||
|
agrees with it:
|
||||||
|
|
||||||
|
* `HEAD` is exactly on a tag → that tag with a leading `v` stripped, so
|
||||||
|
the tag `v1.0.0` produces `vaultik 1.0.0`, matching the archive name
|
||||||
|
`vaultik_1.0.0_linux_amd64.tar.gz`. `goreleaser` strips the prefix the
|
||||||
|
same way.
|
||||||
|
* anything else → `dev-<12 chars of the commit sha>`.
|
||||||
|
* either, with uncommitted changes to tracked files → a `-dirty`
|
||||||
|
suffix, because a modified checkout of a tag is not that tag.
|
||||||
|
|
||||||
|
A build that is not a release never names itself like one. `vaultik
|
||||||
|
version` says so in as many words on a development build, and
|
||||||
|
`goreleaser --snapshot` stamps the same `dev-<sha>` string rather than
|
||||||
|
inventing the next patch number. If `script/version` cannot be run at
|
||||||
|
all, `make` stops with an error instead of building an unversioned
|
||||||
|
binary, and a binary that somehow carries an empty version string still
|
||||||
|
reports itself as a development build.
|
||||||
|
|
||||||
|
### cutting a release
|
||||||
|
|
||||||
|
Releases are cut by CI from a tag, not from a workstation:
|
||||||
|
|
||||||
|
```
|
||||||
|
git tag -a v1.2.3 -m 'v1.2.3'
|
||||||
|
git push origin v1.2.3
|
||||||
|
```
|
||||||
|
|
||||||
|
`.gitea/workflows/release.yml` triggers on `v*` tags, installs a Go
|
||||||
|
toolchain and the pinned `goreleaser`, and runs `script/release`, which
|
||||||
|
builds
|
||||||
|
`linux,darwin × amd64,arm64` archives plus `checksums.txt` and publishes
|
||||||
|
them to this repository's Gitea releases as a draft. `.goreleaser.yaml`
|
||||||
|
has a `gitea_urls:` block pointing at `https://git.eeqj.de/api/v1`;
|
||||||
|
without it `goreleaser` would talk to the GitHub API.
|
||||||
|
|
||||||
|
The workflow needs one repository Actions secret:
|
||||||
|
|
||||||
|
| Secret | What it is |
|
||||||
|
| --------------- | ------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `RELEASE_TOKEN` | A Gitea access token with `write:repository` scope, owned by an account that can publish releases here. |
|
||||||
|
|
||||||
|
It is passed to `goreleaser` as `GITEA_TOKEN`. The runner's automatic
|
||||||
|
token is deliberately not used: it is not guaranteed to carry release
|
||||||
|
write access.
|
||||||
|
|
||||||
|
The Go toolchain that compiles the released binaries comes from an
|
||||||
|
`actions/setup-go` step pinned by commit sha, reading its version from
|
||||||
|
`go.mod` (currently `1.26.1`, the same version the `Dockerfile` builder
|
||||||
|
stage pins by digest). `goreleaser` shells out to `go` for every
|
||||||
|
cross-compile, so without that step the release would either fail
|
||||||
|
outright or ship binaries built by whatever unpinned toolchain the
|
||||||
|
runner happened to carry — the one unpinned thing in an otherwise
|
||||||
|
hash-pinned release path.
|
||||||
|
|
||||||
|
To rehearse the whole build without publishing or tagging anything:
|
||||||
|
|
||||||
|
```
|
||||||
|
make release-snapshot
|
||||||
|
```
|
||||||
|
|
||||||
|
Artifacts land in `./dist`, which is gitignored.
|
||||||
|
|
||||||
|
Release artifacts are not signed, carry no SBOM, and are not built
|
||||||
|
reproducibly; the archives contain the binary, `LICENSE`, and
|
||||||
|
`README.md` only (no shell completions or man page).
|
||||||
|
|
||||||
## license
|
## license
|
||||||
|
|
||||||
[MIT](https://opensource.org/license/mit/)
|
[MIT](https://opensource.org/license/mit/)
|
||||||
|
|||||||
65
TODO.md
65
TODO.md
@@ -14,10 +14,73 @@ pre-1.0
|
|||||||
|
|
||||||
# Next Step
|
# Next Step
|
||||||
|
|
||||||
Define remaining scope for a first tagged release and cut v0.1.0.
|
Define the remaining scope for the first tagged release under the 1.0.0
|
||||||
|
milestone, then cut that tag. The mechanism to cut it now exists and is
|
||||||
|
exercised; what is left is the scope decision, which is the owner's.
|
||||||
|
This step deliberately names one version number: it previously said
|
||||||
|
"cut v0.1.0" while the `Makefile` baked in `1.0.0-rc.1` and the issue
|
||||||
|
milestone said 1.0.0, and three different answers to "what is the next
|
||||||
|
release" is exactly the contradiction
|
||||||
|
[issue #65](https://git.eeqj.de/sneak/vaultik/issues/65) was filed over.
|
||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-08-09: Made the tagged-release path actually work on Gitea
|
||||||
|
([issue #65](https://git.eeqj.de/sneak/vaultik/issues/65)). Three
|
||||||
|
independent blockers, one of which was the whole
|
||||||
|
release: `.goreleaser.yaml` had no `gitea_urls:` block, so goreleaser
|
||||||
|
defaulted to the GitHub API and a `goreleaser release` from this repo
|
||||||
|
would have failed or published where nobody is looking. It now points
|
||||||
|
at `https://git.eeqj.de/api/v1`. The version is the second: it was a
|
||||||
|
hardcoded `VERSION := 1.0.0-rc.1` in the `Makefile`, so every local
|
||||||
|
build claimed to be a release candidate that had never been tagged and
|
||||||
|
did not exist, while `git tag -l` was empty and `internal/globals`
|
||||||
|
defaulted to `dev`. Version now comes from git via the new
|
||||||
|
`script/version` — the exact tag with a leading `v` stripped (so a
|
||||||
|
`make` build and a goreleaser build of one commit report the same
|
||||||
|
string, and it matches the archive names), otherwise `dev-<12-char
|
||||||
|
sha>`, with `-dirty` appended in either case when tracked files are
|
||||||
|
modified. Untracked files are deliberately not counted, matching
|
||||||
|
`git describe --dirty`. The same honesty was owed by the snapshot
|
||||||
|
path: `snapshot.version_template` was `{{ incpatch .Version }}-next`,
|
||||||
|
which manufactures a release number from the last tag and, with no
|
||||||
|
tags at all, from goreleaser's fabricated `v0.0.0`; it now emits the
|
||||||
|
same `dev-<sha>`. The one non-obvious consequence is that
|
||||||
|
`internal/cli/version.go` gated its "this is a development build"
|
||||||
|
notice on the version being exactly `dev`, so the moment untagged
|
||||||
|
builds began carrying a commit sha that notice would have gone silent
|
||||||
|
and an unreleased binary would have read as a release — the gate is
|
||||||
|
now `globals.IsDevVersion`, which is a predicate over a string rather
|
||||||
|
than a comparison against a global precisely so it can be tested, and
|
||||||
|
it is tested at the boundary (`1.0.0-dev` is a release, `dev-<sha>`
|
||||||
|
is not). Release automation is the third blocker: a tag-triggered
|
||||||
|
`.gitea/workflows/release.yml` runs the build in CI rather than from
|
||||||
|
a laptop, with `fetch-depth: 0` because a shallow checkout has no
|
||||||
|
tags and would silently mislabel the release, and with the
|
||||||
|
`RELEASE_TOKEN` repository secret passed as `GITEA_TOKEN` (documented
|
||||||
|
in `README.md`; the runner's automatic token is not used because it
|
||||||
|
is not guaranteed to carry release write scope). `script/release`
|
||||||
|
unsets any `GITHUB_TOKEN`/`GITLAB_TOKEN` it finds, since goreleaser
|
||||||
|
chooses its forge from whichever token variable is set and refuses to
|
||||||
|
run when it sees more than one — a runner-provided token must not get
|
||||||
|
to decide where these artifacts are published. `make release` and
|
||||||
|
`make release-snapshot`, the last two Makefile targets that were not
|
||||||
|
shims, now call `script/release` and `script/release-snapshot`, which
|
||||||
|
resolve goreleaser exactly the way `script/lint` resolves the linter:
|
||||||
|
a `PATH` binary is used only at the pinned version, never as a silent
|
||||||
|
fallback. `script/bootstrap` installs it, from a sha256-verified
|
||||||
|
GitHub release archive per `REPO_POLICIES.md`, via a separate
|
||||||
|
`script/install-goreleaser` — separate because `script/bootstrap`
|
||||||
|
hard-fails without a usable Docker daemon by design, and the release
|
||||||
|
runner needs goreleaser without needing Docker. Verified by running
|
||||||
|
the thing rather than reading it: `make release-snapshot` produced
|
||||||
|
four archives and `checksums.txt`, and the linux/amd64 binary from
|
||||||
|
`dist/` reports `dev-<sha>` with the development-build notice. Tag
|
||||||
|
handling was exercised in a throwaway repository rather than by
|
||||||
|
tagging this one; no tag was created here, since that is the owner's
|
||||||
|
call. Signing, SBOM, reproducible builds, completions and a man page
|
||||||
|
are out of scope by the issue.
|
||||||
|
|
||||||
- 2026-08-09: Isolated the lint cache per worktree and context-gated the
|
- 2026-08-09: Isolated the lint cache per worktree and context-gated the
|
||||||
native lint path (issues #99, #80). One defect seen twice:
|
native lint path (issues #99, #80). One defect seen twice:
|
||||||
`script/lint` decided whether it could skip the pinned image by asking
|
`script/lint` decided whether it could skip the pinned image by asking
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"io"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -16,28 +16,35 @@ func NewVersionCommand() *cobra.Command {
|
|||||||
Short: "Print version information",
|
Short: "Print version information",
|
||||||
Long: `Print version, git commit, and build information for vaultik.`,
|
Long: `Print version, git commit, and build information for vaultik.`,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
Run: func(_ *cobra.Command, _ []string) {
|
Run: func(cmd *cobra.Command, _ []string) {
|
||||||
_, _ = fmt.Fprintf(os.Stdout, "vaultik %s\n", globals.Version)
|
writeVersion(cmd.OutOrStdout())
|
||||||
_, _ = fmt.Fprintf(os.Stdout, " commit: %s\n", globals.Commit)
|
|
||||||
_, _ = fmt.Fprintf(os.Stdout, " build date: %s\n", globals.CommitDate)
|
|
||||||
_, _ = fmt.Fprintf(os.Stdout, " go: %s\n", runtime.Version())
|
|
||||||
_, _ = fmt.Fprintf(os.Stdout, " os/arch: %s/%s\n",
|
|
||||||
runtime.GOOS, runtime.GOARCH)
|
|
||||||
_, _ = fmt.Fprintf(os.Stdout, " author: %s\n", globals.Author)
|
|
||||||
_, _ = fmt.Fprintf(os.Stdout, " homepage: %s\n", globals.Homepage)
|
|
||||||
_, _ = fmt.Fprintf(os.Stdout, " license: %s\n", globals.License)
|
|
||||||
|
|
||||||
if globals.Version == "dev" {
|
|
||||||
_, _ = fmt.Fprintln(os.Stdout)
|
|
||||||
_, _ = fmt.Fprintln(os.Stdout,
|
|
||||||
"This is a development build (no version information embedded).")
|
|
||||||
_, _ = fmt.Fprintln(os.Stdout,
|
|
||||||
"Build a release binary with 'make vaultik' or download from")
|
|
||||||
_, _ = fmt.Fprintln(os.Stdout,
|
|
||||||
"https://sneak.berlin/go/vaultik for embedded version metadata.")
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// writeVersion prints the version report. It takes a writer rather than
|
||||||
|
// using os.Stdout directly so the output can be asserted on in tests.
|
||||||
|
func writeVersion(w io.Writer) {
|
||||||
|
_, _ = fmt.Fprintf(w, "vaultik %s\n", globals.Version)
|
||||||
|
_, _ = fmt.Fprintf(w, " commit: %s\n", globals.Commit)
|
||||||
|
_, _ = fmt.Fprintf(w, " build date: %s\n", globals.CommitDate)
|
||||||
|
_, _ = fmt.Fprintf(w, " go: %s\n", runtime.Version())
|
||||||
|
_, _ = fmt.Fprintf(w, " os/arch: %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
||||||
|
_, _ = fmt.Fprintf(w, " author: %s\n", globals.Author)
|
||||||
|
_, _ = fmt.Fprintf(w, " homepage: %s\n", globals.Homepage)
|
||||||
|
_, _ = fmt.Fprintf(w, " license: %s\n", globals.License)
|
||||||
|
|
||||||
|
if globals.IsDevVersion(globals.Version) {
|
||||||
|
_, _ = fmt.Fprintln(w)
|
||||||
|
_, _ = fmt.Fprintln(w,
|
||||||
|
"This is a development build: it was not built from a tagged")
|
||||||
|
_, _ = fmt.Fprintln(w,
|
||||||
|
"commit, so it carries no release version. Released binaries")
|
||||||
|
_, _ = fmt.Fprintf(w,
|
||||||
|
"are published at %s\n", globals.ReleasesURL)
|
||||||
|
_, _ = fmt.Fprintln(w,
|
||||||
|
"and report their tag on the first line above.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
77
internal/cli/version_test.go
Normal file
77
internal/cli/version_test.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package cli_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sneak.berlin/go/vaultik/internal/cli"
|
||||||
|
"sneak.berlin/go/vaultik/internal/globals"
|
||||||
|
)
|
||||||
|
|
||||||
|
// runVersionCommand executes `vaultik version` with its output
|
||||||
|
// captured, and returns what it printed.
|
||||||
|
func runVersionCommand(t *testing.T) string {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
cmd := cli.NewVersionCommand()
|
||||||
|
|
||||||
|
var out bytes.Buffer
|
||||||
|
|
||||||
|
cmd.SetOut(&out)
|
||||||
|
cmd.SetErr(&out)
|
||||||
|
cmd.SetArgs([]string{})
|
||||||
|
|
||||||
|
err := cmd.Execute()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("version command failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return out.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestVersionCommandReportsBuildVersion checks that the first line of
|
||||||
|
// the report is the version the binary was actually built with. The
|
||||||
|
// test binary carries no -ldflags, so that is the "dev" default -- the
|
||||||
|
// same string an untagged `make vaultik` build stamps a prefix of.
|
||||||
|
func TestVersionCommandReportsBuildVersion(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
out := runVersionCommand(t)
|
||||||
|
|
||||||
|
wantFirst := "vaultik " + globals.Version
|
||||||
|
if first, _, _ := strings.Cut(out, "\n"); first != wantFirst {
|
||||||
|
t.Errorf("first line = %q, want %q", first, wantFirst)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(out, "commit:") {
|
||||||
|
t.Error("output does not report the commit")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestVersionCommandFlagsDevelopmentBuild is the regression test for
|
||||||
|
// the thing this command exists to prevent: a build that is not a
|
||||||
|
// release must say so. The notice used to be gated on the version
|
||||||
|
// being exactly "dev", so once untagged builds started carrying their
|
||||||
|
// commit sha it would have gone silent and an unreleased binary would
|
||||||
|
// have looked like a release.
|
||||||
|
func TestVersionCommandFlagsDevelopmentBuild(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if !globals.IsDevVersion(globals.Version) {
|
||||||
|
t.Skipf("test binary was stamped with release version %q",
|
||||||
|
globals.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := runVersionCommand(t)
|
||||||
|
|
||||||
|
if !strings.Contains(out, "development build") {
|
||||||
|
t.Errorf("dev build did not print the development-build notice:\n%s",
|
||||||
|
out)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(out, globals.ReleasesURL) {
|
||||||
|
t.Errorf("development-build notice does not point at %s:\n%s",
|
||||||
|
globals.ReleasesURL, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,14 +3,23 @@
|
|||||||
package globals
|
package globals
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Appname is the application name, populated from main().
|
// Appname is the application name, populated from main().
|
||||||
var Appname = "vaultik" //nolint:gochecknoglobals // set via -ldflags at build time
|
var Appname = "vaultik" //nolint:gochecknoglobals // set via -ldflags at build time
|
||||||
|
|
||||||
|
// DevVersion is the version a binary reports when it was not built
|
||||||
|
// from a tagged commit. script/version emits either this exact string
|
||||||
|
// (outside a git checkout) or this string followed by "-" and the
|
||||||
|
// commit it was built from, and goreleaser's snapshot template matches
|
||||||
|
// that shape. It is deliberately not a number: a build that is not a
|
||||||
|
// release must not name itself like one.
|
||||||
|
const DevVersion = "dev"
|
||||||
|
|
||||||
// Version is the application version, populated from main().
|
// Version is the application version, populated from main().
|
||||||
var Version = "dev" //nolint:gochecknoglobals // set via -ldflags at build time
|
var Version = DevVersion //nolint:gochecknoglobals // set via -ldflags at build time
|
||||||
|
|
||||||
// Commit is the git commit hash, populated from main().
|
// Commit is the git commit hash, populated from main().
|
||||||
var Commit = "unknown" //nolint:gochecknoglobals // set via -ldflags at build time
|
var Commit = "unknown" //nolint:gochecknoglobals // set via -ldflags at build time
|
||||||
@@ -24,6 +33,9 @@ const Author = "Jeffrey Paul <sneak@sneak.berlin>"
|
|||||||
// Homepage is the canonical URL for vaultik.
|
// Homepage is the canonical URL for vaultik.
|
||||||
const Homepage = "https://sneak.berlin/go/vaultik"
|
const Homepage = "https://sneak.berlin/go/vaultik"
|
||||||
|
|
||||||
|
// ReleasesURL is where tagged release artifacts are published.
|
||||||
|
const ReleasesURL = "https://git.eeqj.de/sneak/vaultik/releases"
|
||||||
|
|
||||||
// License is the SPDX identifier for the project license.
|
// License is the SPDX identifier for the project license.
|
||||||
const License = "MIT"
|
const License = "MIT"
|
||||||
|
|
||||||
@@ -47,6 +59,21 @@ func New() (*Globals, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsDevVersion reports whether v names a development build rather than
|
||||||
|
// a release. Both "dev" and "dev-<sha>" (and its "-dirty" variant)
|
||||||
|
// count: a caller that compares against "dev" exactly would treat every
|
||||||
|
// commit-stamped development build as a release.
|
||||||
|
//
|
||||||
|
// The empty string counts too. Nothing that knows its version reports
|
||||||
|
// no version, so an empty Version means the stamping failed, and the
|
||||||
|
// safe reading of "we could not establish that this is a release" is
|
||||||
|
// that it is not one. The Makefile refuses to build at all in that
|
||||||
|
// case; this is the second line of defence, for a binary linked by
|
||||||
|
// something other than the Makefile.
|
||||||
|
func IsDevVersion(v string) bool {
|
||||||
|
return v == "" || v == DevVersion || strings.HasPrefix(v, DevVersion+"-")
|
||||||
|
}
|
||||||
|
|
||||||
// shortCommitLen is the number of commit-hash characters ShortCommit keeps.
|
// shortCommitLen is the number of commit-hash characters ShortCommit keeps.
|
||||||
const shortCommitLen = 12
|
const shortCommitLen = 12
|
||||||
|
|
||||||
|
|||||||
@@ -32,3 +32,56 @@ func TestGlobalsNew(t *testing.T) {
|
|||||||
t.Error("Commit should not be empty")
|
t.Error("Commit should not be empty")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestIsDevVersion covers the boundary that matters: everything
|
||||||
|
// script/version and goreleaser's snapshot template can emit for an
|
||||||
|
// untagged build must be recognised as a development build, and a real
|
||||||
|
// tag must not be. A plain equality check against "dev" used to decide
|
||||||
|
// this, which classified every commit-stamped dev build as a release.
|
||||||
|
func TestIsDevVersion(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
version string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
// What an untagged build produces.
|
||||||
|
{"dev", true},
|
||||||
|
{"dev-b6e4a218a39e", true},
|
||||||
|
{"dev-b6e4a218a39e-dirty", true},
|
||||||
|
// What a tagged build produces (script/version strips the
|
||||||
|
// leading "v", matching goreleaser's .Version).
|
||||||
|
{"1.0.0", false},
|
||||||
|
{"0.1.0", false},
|
||||||
|
{"1.0.0-rc.1", false},
|
||||||
|
{"v1.0.0", false},
|
||||||
|
// A release must not be mistaken for a dev build just because
|
||||||
|
// the string happens to contain "dev".
|
||||||
|
{"1.0.0-dev", false},
|
||||||
|
{"developer", false},
|
||||||
|
// A binary with no version string at all did not get stamped,
|
||||||
|
// which is a build failure, not a release. It must never print
|
||||||
|
// as one. The Makefile refuses to build when script/version
|
||||||
|
// yields nothing; this covers a binary linked some other way.
|
||||||
|
{"", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
if got := globals.IsDevVersion(tc.version); got != tc.want {
|
||||||
|
t.Errorf("IsDevVersion(%q) = %v, want %v", tc.version, got, tc.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestDefaultVersionIsDev pins the linker-flag contract: an unstamped
|
||||||
|
// binary (no -ldflags at all, which is what `go build ./...` and `go
|
||||||
|
// install` produce) must report itself as a development build rather
|
||||||
|
// than as some default release number.
|
||||||
|
func TestDefaultVersionIsDev(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
if !globals.IsDevVersion(globals.DevVersion) {
|
||||||
|
t.Errorf("DevVersion %q is not recognised as a dev version",
|
||||||
|
globals.DevVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -114,6 +114,14 @@ main() {
|
|||||||
# sqlite3 CLI: the test suite shells out to it (VACUUM).
|
# sqlite3 CLI: the test suite shells out to it (VACUUM).
|
||||||
if missing sqlite3; then pkg_install sqlite sqlite3 sqlite sqlite; fi
|
if missing sqlite3; then pkg_install sqlite sqlite3 sqlite sqlite; fi
|
||||||
|
|
||||||
|
# goreleaser, at the version pinned by script/install-goreleaser and
|
||||||
|
# verified against a hardcoded sha256. Package managers are not used
|
||||||
|
# for it: they ship whatever version they happen to carry, and the
|
||||||
|
# tool that builds a release has to be a known one. The install is
|
||||||
|
# its own script because the release workflow needs goreleaser
|
||||||
|
# without needing the Docker requirement below.
|
||||||
|
"$ROOT/script/install-goreleaser"
|
||||||
|
|
||||||
go mod download
|
go mod download
|
||||||
|
|
||||||
# Last, so that everything installable is installed before the one
|
# Last, so that everything installable is installed before the one
|
||||||
|
|||||||
144
script/install-goreleaser
Executable file
144
script/install-goreleaser
Executable file
@@ -0,0 +1,144 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# script/install-goreleaser: install the pinned goreleaser into the
|
||||||
|
# repo-local tool directory. Our own extension to
|
||||||
|
# scripts-to-rule-them-all. Idempotent: exits immediately when the
|
||||||
|
# pinned version is already available.
|
||||||
|
#
|
||||||
|
# script/bootstrap calls this, and so does .gitea/workflows/release.yml.
|
||||||
|
# It is a separate script rather than an inline block in bootstrap
|
||||||
|
# because bootstrap deliberately hard-fails on a machine without a
|
||||||
|
# usable Docker daemon (Docker gates script/lint, and therefore
|
||||||
|
# script/check), while the release runner needs goreleaser and does not
|
||||||
|
# need Docker. One script, two callers, no duplicated pin.
|
||||||
|
#
|
||||||
|
# The install is a specific GitHub release archive verified against the
|
||||||
|
# sha256 hardcoded below, per REPO_POLICIES.md: no `curl | sh`, no
|
||||||
|
# `@latest`, no version tag that a server can move. Bumping goreleaser
|
||||||
|
# means editing GORELEASER_VERSION *and* the four checksums, which are
|
||||||
|
# taken from the checksums.txt published with that release.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||||
|
|
||||||
|
# goreleaser v2.17.1, 2026-08-05. Checksums are from
|
||||||
|
# https://github.com/goreleaser/goreleaser/releases/download/v2.17.1/checksums.txt
|
||||||
|
GORELEASER_VERSION="2.17.1"
|
||||||
|
SHA256_LINUX_X86_64="a99bbc7ae0d8d897b07c4c497a9b62f222558804715ef219d1af05a7e417bc80"
|
||||||
|
SHA256_LINUX_ARM64="702f03769ac8bcb0e47839c82243cc614ae995633599a98c63062e13ea85f829"
|
||||||
|
SHA256_DARWIN_X86_64="a92a68c61a6833ff67748f532cbebc7b8e49ba30de062ab463b221211ee6368f"
|
||||||
|
SHA256_DARWIN_ARM64="b65624885c25da9a677b7ad11cf86a02123cc5a56af66f6b4ebb574658eada2e"
|
||||||
|
|
||||||
|
TOOLBIN="$ROOT/.tool/bin"
|
||||||
|
|
||||||
|
# Print the version of the goreleaser at $1, or nothing if it is not
|
||||||
|
# usable. `goreleaser --version` prints a multi-line banner; the version
|
||||||
|
# is on the line beginning "GitVersion:".
|
||||||
|
goreleaser_version() {
|
||||||
|
[ -x "$1" ] || return 0
|
||||||
|
"$1" --version 2>/dev/null |
|
||||||
|
sed -n 's/^ *GitVersion: *//p' |
|
||||||
|
head -n 1
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_sha256() {
|
||||||
|
file="$1"
|
||||||
|
want="$2"
|
||||||
|
if command -v sha256sum >/dev/null 2>&1; then
|
||||||
|
got="$(sha256sum "$file" | cut -d' ' -f1)"
|
||||||
|
elif command -v shasum >/dev/null 2>&1; then
|
||||||
|
got="$(shasum -a 256 "$file" | cut -d' ' -f1)"
|
||||||
|
else
|
||||||
|
echo "install-goreleaser: no sha256sum or shasum available" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [ "$got" != "$want" ]; then
|
||||||
|
echo "install-goreleaser: checksum mismatch for $file" >&2
|
||||||
|
echo " expected: $want" >&2
|
||||||
|
echo " actual: $got" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
cd "$ROOT"
|
||||||
|
|
||||||
|
# Already have it, either on PATH or from a previous run? Then stop.
|
||||||
|
# An arbitrary PATH goreleaser is NOT accepted: the config uses
|
||||||
|
# version-2 schema features, and the whole point of pinning is that
|
||||||
|
# a release is cut by a known build of a known tool.
|
||||||
|
if [ "$(goreleaser_version "$(command -v goreleaser || true)")" \
|
||||||
|
= "$GORELEASER_VERSION" ]; then
|
||||||
|
echo "goreleaser $GORELEASER_VERSION already on PATH"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ "$(goreleaser_version "$TOOLBIN/goreleaser")" \
|
||||||
|
= "$GORELEASER_VERSION" ]; then
|
||||||
|
echo "goreleaser $GORELEASER_VERSION already installed in .tool/bin"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
os="$(uname -s)"
|
||||||
|
arch="$(uname -m)"
|
||||||
|
case "$os" in
|
||||||
|
Linux) ;;
|
||||||
|
Darwin) ;;
|
||||||
|
*)
|
||||||
|
echo "install-goreleaser: unsupported OS $os" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
case "$arch" in
|
||||||
|
x86_64 | amd64) arch="x86_64" ;;
|
||||||
|
arm64 | aarch64) arch="arm64" ;;
|
||||||
|
*)
|
||||||
|
echo "install-goreleaser: unsupported architecture $arch" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "${os}_${arch}" in
|
||||||
|
Linux_x86_64) sum="$SHA256_LINUX_X86_64" ;;
|
||||||
|
Linux_arm64) sum="$SHA256_LINUX_ARM64" ;;
|
||||||
|
Darwin_x86_64) sum="$SHA256_DARWIN_X86_64" ;;
|
||||||
|
Darwin_arm64) sum="$SHA256_DARWIN_ARM64" ;;
|
||||||
|
*)
|
||||||
|
echo "install-goreleaser: no pinned checksum for ${os}_${arch}" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
archive="goreleaser_${os}_${arch}.tar.gz"
|
||||||
|
url="https://github.com/goreleaser/goreleaser/releases/download/v${GORELEASER_VERSION}/${archive}"
|
||||||
|
|
||||||
|
if ! command -v curl >/dev/null 2>&1; then
|
||||||
|
echo "install-goreleaser: curl is required" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
tmp="$(mktemp -d)"
|
||||||
|
# shellcheck disable=SC2064 # expand $tmp now, not at trap time
|
||||||
|
trap "rm -rf '$tmp'" EXIT INT TERM
|
||||||
|
|
||||||
|
echo "installing goreleaser $GORELEASER_VERSION for ${os}_${arch}"
|
||||||
|
curl -fsSL --retry 3 -o "$tmp/$archive" "$url"
|
||||||
|
verify_sha256 "$tmp/$archive" "$sum"
|
||||||
|
|
||||||
|
tar -xzf "$tmp/$archive" -C "$tmp" goreleaser
|
||||||
|
mkdir -p "$TOOLBIN"
|
||||||
|
# Move into place via a temp name in the destination directory so a
|
||||||
|
# concurrent run never observes a half-written binary.
|
||||||
|
mv "$tmp/goreleaser" "$TOOLBIN/.goreleaser.$$"
|
||||||
|
chmod 0755 "$TOOLBIN/.goreleaser.$$"
|
||||||
|
mv "$TOOLBIN/.goreleaser.$$" "$TOOLBIN/goreleaser"
|
||||||
|
|
||||||
|
installed="$(goreleaser_version "$TOOLBIN/goreleaser")"
|
||||||
|
if [ "$installed" != "$GORELEASER_VERSION" ]; then
|
||||||
|
echo "install-goreleaser: installed binary reports '$installed'," \
|
||||||
|
"expected '$GORELEASER_VERSION'" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "goreleaser $GORELEASER_VERSION installed to .tool/bin"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
92
script/release
Executable file
92
script/release
Executable file
@@ -0,0 +1,92 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# script/release: build and publish the release artifacts with the
|
||||||
|
# pinned goreleaser. Our own extension to scripts-to-rule-them-all.
|
||||||
|
#
|
||||||
|
# Normally invoked by a tag push through .gitea/workflows/release.yml,
|
||||||
|
# not by hand: a release cut from a workstation is a release nobody can
|
||||||
|
# reproduce. Any arguments are passed through to `goreleaser release`,
|
||||||
|
# which is how script/release-snapshot adds --snapshot.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
||||||
|
ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
|
||||||
|
|
||||||
|
# Keep in sync with script/install-goreleaser, which owns the pin.
|
||||||
|
GORELEASER_VERSION="2.17.1"
|
||||||
|
|
||||||
|
goreleaser_version() {
|
||||||
|
[ -x "$1" ] || return 0
|
||||||
|
"$1" --version 2>/dev/null |
|
||||||
|
sed -n 's/^ *GitVersion: *//p' |
|
||||||
|
head -n 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Resolve the goreleaser to run, on the same rule script/lint uses for
|
||||||
|
# golangci-lint: a binary on PATH is accepted only when it is exactly
|
||||||
|
# the pinned version, because a differently versioned tool would
|
||||||
|
# produce a differently built release from the same tag. Anything else
|
||||||
|
# comes from .tool/bin, and a missing one is a loud failure naming the
|
||||||
|
# script that installs it rather than a silent fallback.
|
||||||
|
resolve_goreleaser() {
|
||||||
|
path_bin="$(command -v goreleaser || true)"
|
||||||
|
if [ -n "$path_bin" ] &&
|
||||||
|
[ "$(goreleaser_version "$path_bin")" = "$GORELEASER_VERSION" ]; then
|
||||||
|
echo "$path_bin"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if [ "$(goreleaser_version "$ROOT/.tool/bin/goreleaser")" \
|
||||||
|
= "$GORELEASER_VERSION" ]; then
|
||||||
|
echo "$ROOT/.tool/bin/goreleaser"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
cd "$ROOT"
|
||||||
|
|
||||||
|
if ! bin="$(resolve_goreleaser)"; then
|
||||||
|
cat >&2 <<EOF
|
||||||
|
release: goreleaser $GORELEASER_VERSION is not available.
|
||||||
|
|
||||||
|
Run script/bootstrap (or script/install-goreleaser directly) to install
|
||||||
|
it. A goreleaser already on PATH is used only when it reports exactly
|
||||||
|
$GORELEASER_VERSION; any other version is refused rather than used,
|
||||||
|
because the released binaries must come from a known build of a known
|
||||||
|
tool.
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
snapshot=0
|
||||||
|
for arg in "$@"; do
|
||||||
|
[ "$arg" = "--snapshot" ] && snapshot=1
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$snapshot" -eq 0 ]; then
|
||||||
|
# Publishing needs a Gitea token. Check it here so the failure
|
||||||
|
# names the secret, rather than after several minutes of
|
||||||
|
# cross-compiling.
|
||||||
|
if [ -z "${GITEA_TOKEN:-}" ]; then
|
||||||
|
cat >&2 <<'EOF'
|
||||||
|
release: GITEA_TOKEN is not set.
|
||||||
|
|
||||||
|
Publishing needs a Gitea API token with write access to this
|
||||||
|
repository's releases. In CI it comes from the RELEASE_TOKEN repository
|
||||||
|
secret (see .gitea/workflows/release.yml and the Releasing section of
|
||||||
|
README.md). To build without publishing, use script/release-snapshot.
|
||||||
|
EOF
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# goreleaser picks its forge from whichever token variable is
|
||||||
|
# set and refuses to run when it finds more than one. A CI
|
||||||
|
# runner may export a GITHUB_TOKEN of its own; this repo lives
|
||||||
|
# on Gitea and releases only there, so an unrelated token must
|
||||||
|
# not be allowed to decide where the artifacts are published.
|
||||||
|
unset GITHUB_TOKEN GITLAB_TOKEN
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$bin" release --clean "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
18
script/release-snapshot
Executable file
18
script/release-snapshot
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# script/release-snapshot: build the full set of release artifacts
|
||||||
|
# without publishing or tagging anything. Our own extension to
|
||||||
|
# scripts-to-rule-them-all.
|
||||||
|
#
|
||||||
|
# This is the dry run for script/release: same goreleaser, same config,
|
||||||
|
# same cross-compile matrix and checksums, into ./dist. The version it
|
||||||
|
# stamps is the honest dev-<shortcommit> string rather than an invented
|
||||||
|
# release number, so a snapshot binary cannot be mistaken for one.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
|
||||||
|
|
||||||
|
main() {
|
||||||
|
exec "$SCRIPT_DIR/release" --snapshot "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
73
script/version
Executable file
73
script/version
Executable file
@@ -0,0 +1,73 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# script/version: output the version string to bake into the binary.
|
||||||
|
# Our own extension to scripts-to-rule-them-all, and the single source
|
||||||
|
# of truth for the version: the Makefile's LDFLAGS call this rather
|
||||||
|
# than carrying a hardcoded constant, which is what used to make every
|
||||||
|
# local build claim to be 1.0.0-rc.1 regardless of git state.
|
||||||
|
#
|
||||||
|
# The rules, in order:
|
||||||
|
#
|
||||||
|
# HEAD is exactly on an annotated or lightweight tag
|
||||||
|
# -> that tag, with a leading "v" stripped
|
||||||
|
# anything else
|
||||||
|
# -> "dev-<12 chars of HEAD>"
|
||||||
|
# not a git checkout at all (release tarball, `go install`)
|
||||||
|
# -> "dev"
|
||||||
|
#
|
||||||
|
# Either of the first two gains a "-dirty" suffix when tracked files
|
||||||
|
# have uncommitted changes, because a modified checkout of v1.0.0 is
|
||||||
|
# not v1.0.0. Untracked files are ignored, matching `git describe
|
||||||
|
# --dirty`: a stray scratch file does not change what was compiled.
|
||||||
|
#
|
||||||
|
# The "v" is stripped so that a `make` build and a goreleaser build of
|
||||||
|
# the same tagged commit report the *same* string: goreleaser's
|
||||||
|
# {{ .Version }} is the tag without the prefix, and the release archive
|
||||||
|
# names are built from it. A tag named `v1.0.0` therefore produces
|
||||||
|
# `vaultik 1.0.0`, matching `vaultik_1.0.0_linux_amd64.tar.gz`.
|
||||||
|
#
|
||||||
|
# Nothing here ever invents a version number. An untagged build says so
|
||||||
|
# and names the commit it was built from; it does not round up to the
|
||||||
|
# nearest plausible release.
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
|
||||||
|
|
||||||
|
# Length of the commit prefix in a dev version. Matches
|
||||||
|
# globals.ShortCommit, so `vaultik version` shows the same 12 chars in
|
||||||
|
# its version line and its commit line.
|
||||||
|
SHORT_LEN=12
|
||||||
|
|
||||||
|
main() {
|
||||||
|
cd "$ROOT"
|
||||||
|
|
||||||
|
if ! git rev-parse --git-dir >/dev/null 2>&1; then
|
||||||
|
echo "dev"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
dirty=""
|
||||||
|
if [ -n "$(git status --porcelain --untracked-files=no 2>/dev/null)" ]; then
|
||||||
|
dirty="-dirty"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --exact-match so a *descendant* of a tag is not reported as that
|
||||||
|
# tag. Plain `git describe --tags` would call a commit 40 patches
|
||||||
|
# past v1.0.0 "v1.0.0-40-gabc1234", and the leading token of that is
|
||||||
|
# a released version the build is not.
|
||||||
|
tag="$(git describe --tags --exact-match HEAD 2>/dev/null || true)"
|
||||||
|
if [ -n "$tag" ]; then
|
||||||
|
echo "${tag#v}${dirty}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
sha="$(git rev-parse "--short=$SHORT_LEN" HEAD 2>/dev/null || true)"
|
||||||
|
if [ -z "$sha" ]; then
|
||||||
|
# A repo with no commits at all.
|
||||||
|
echo "dev"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "dev-${sha}${dirty}"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
Reference in New Issue
Block a user