The shipped binary reports version "dev": no -X ldflags in the Makefile or Dockerfile #253

Closed
opened 2026-08-24 00:56:09 +02:00 by clawbot · 2 comments
Collaborator

cmd/webhooker/main.go:61 declares version = "dev" with the comment "Build-time variables set via -ldflags", but nothing sets them. Makefile's build target and Dockerfile:71 (go build -ldflags '-extldflags "-static"') both omit -X.

Verified by execution during the deployability audit: the built container answers /.well-known/healthcheck with "version":"dev", and the UI footer reads Webhooker by @sneak | dev.

This is a release-integrity defect, not a cosmetic one. Tagging v1.0.0 produces an artifact that cannot identify itself as v1.0.0, and the documented upgrade procedure asks the operator to confirm the new build is live — which nothing on the healthcheck or in the UI currently allows them to do. It also makes any future bug report ambiguous about which build it came from.

Definition of done

  • make build and the Dockerfile both stamp version, and the value is derived from git (tag when the build is at a tag, otherwise a describe-style identifier including the short SHA and a dirty marker). A build from a clean checkout at a tag must report exactly that tag.
  • /.well-known/healthcheck and the UI footer report the stamped value.
  • The stamp survives the static relink at Dockerfile:71.
  • A build from a tree with no git metadata (a source tarball, or a Docker build with no .git) must still build and report something honest rather than failing or claiming a tag it is not. State what it reports.
  • Do not break reproducibility: two builds of the same commit must still produce an identical binary, which the current gate asserts by sha256 in #111. If stamping a timestamp would break that, do not stamp a timestamp.
  • Document the stamping in the README alongside the upgrade procedure, so the operator knows what to compare.

Verification

  • make check green.
  • Evidence in the PR body showing the healthcheck reporting a real version from a make build, and from a docker build.
  • Evidence that two builds of the same commit still produce a byte-identical binary.
`cmd/webhooker/main.go:61` declares `version = "dev"` with the comment "Build-time variables set via -ldflags", but nothing sets them. `Makefile`'s `build` target and `Dockerfile:71` (`go build -ldflags '-extldflags "-static"'`) both omit `-X`. Verified by execution during the deployability audit: the built container answers `/.well-known/healthcheck` with `"version":"dev"`, and the UI footer reads `Webhooker by @sneak | dev`. This is a release-integrity defect, not a cosmetic one. Tagging `v1.0.0` produces an artifact that cannot identify itself as `v1.0.0`, and the documented upgrade procedure asks the operator to confirm the new build is live — which nothing on the healthcheck or in the UI currently allows them to do. It also makes any future bug report ambiguous about which build it came from. ## Definition of done - `make build` and the `Dockerfile` both stamp version, and the value is derived from git (tag when the build is at a tag, otherwise a describe-style identifier including the short SHA and a dirty marker). A build from a clean checkout at a tag must report exactly that tag. - `/.well-known/healthcheck` and the UI footer report the stamped value. - The stamp survives the static relink at `Dockerfile:71`. - A build from a tree with no git metadata (a source tarball, or a Docker build with no `.git`) must still build and report something honest rather than failing or claiming a tag it is not. State what it reports. - Do not break reproducibility: two builds of the same commit must still produce an identical binary, which the current gate asserts by sha256 in https://git.eeqj.de/sneak/webhooker/pulls/111. If stamping a timestamp would break that, do not stamp a timestamp. - Document the stamping in the README alongside the upgrade procedure, so the operator knows what to compare. ## Verification - `make check` green. - Evidence in the PR body showing the healthcheck reporting a real version from a `make build`, and from a `docker build`. - Evidence that two builds of the same commit still produce a byte-identical binary.
clawbot added this to the 1.0.0 milestone 2026-08-24 00:56:17 +02:00
Author
Collaborator

Plan:

  • New script/version as the single source of the version string: VERSION from the environment if set and non-empty, else git describe --tags --always --dirty when a git work tree with metadata is present, else unknown. No timestamp, no hostname — nothing that varies between two builds of the same commit.
  • Makefile: VERSION ?= $(shell script/version) and a GO_LDFLAGS hook, so build compiles with -ldflags '-X main.version=$(VERSION) $(GO_LDFLAGS)'. Both the plain build and the static relink compose flags through that one place rather than each spelling out its own.
  • .dockerignore excludes .git/, so the image has no git metadata and cannot derive a version itself. The version therefore enters as ARG VERSION (default unknown, honest for a bare docker build .), and script/docker passes --build-arg VERSION="$(script/version)" from the host, where .git does exist. The static relink at Dockerfile:71 becomes make build VERSION=... GO_LDFLAGS='-extldflags "-static"', so it cannot drop the stamp by replacing the flags.
  • Tests in a new internal/versionscript package, mirroring internal/ciscript: throwaway git repos covering clean-at-a-tag (exact tag, no suffix), untagged, dirty, no-git, and VERSION override.
  • README: document the stamping next to the upgrade procedure so the operator knows what /.well-known/healthcheck should read after step 3.

No change to the reporting sites — main.version already reaches globals.Version, the healthcheck and the footer.

Plan: - New `script/version` as the single source of the version string: `VERSION` from the environment if set and non-empty, else `git describe --tags --always --dirty` when a git work tree with metadata is present, else `unknown`. No timestamp, no hostname — nothing that varies between two builds of the same commit. - `Makefile`: `VERSION ?= $(shell script/version)` and a `GO_LDFLAGS` hook, so `build` compiles with `-ldflags '-X main.version=$(VERSION) $(GO_LDFLAGS)'`. Both the plain build and the static relink compose flags through that one place rather than each spelling out its own. - `.dockerignore` excludes `.git/`, so the image has no git metadata and cannot derive a version itself. The version therefore enters as `ARG VERSION` (default `unknown`, honest for a bare `docker build .`), and `script/docker` passes `--build-arg VERSION="$(script/version)"` from the host, where `.git` does exist. The static relink at `Dockerfile:71` becomes `make build VERSION=... GO_LDFLAGS='-extldflags "-static"'`, so it cannot drop the stamp by replacing the flags. - Tests in a new `internal/versionscript` package, mirroring `internal/ciscript`: throwaway git repos covering clean-at-a-tag (exact tag, no suffix), untagged, dirty, no-git, and `VERSION` override. - README: document the stamping next to the upgrade procedure so the operator knows what `/.well-known/healthcheck` should read after step 3. No change to the reporting sites — `main.version` already reaches `globals.Version`, the healthcheck and the footer.
Author
Collaborator

Implemented in #260 (branch issue-253-version-stamping, base next).

Built as planned, with one addition the plan did not anticipate: stamping the binary alone did not fix the footer. base.html renders .Version with a literal dev fallback, and nothing ever put Version into the template data — so the footer read dev even from a correctly stamped binary. renderTemplate now supplies it.

Verified (full output and evidence in the PR body):

  • make check green with GOFLAGS=-count=1 — 21 packages, 572 tests, 0 issues. from the containerized linter.
  • Throwaway local tag v0.0.0-impl253, deleted before pushing: a make build binary answers /.well-known/healthcheck with "version":"v0.0.0-impl253" — exactly the tag, no suffix.
  • A make docker image at the same tag reports the same on the healthcheck, in the footer (<span>v0.0.0-impl253</span>) and in the startup log. Read off the running binary, so the stamp survives the static relink; it is running on Alpine, which a non-static binary could not.
  • Two make build runs of one commit are byte-identical (dc38ae7e...4349 twice). Nothing time- or host-dependent is stamped.
  • No git metadata: a git archive tarball and a bare docker build . both build, run, and report "version":"unknown".

Tests: new internal/versionscript package (eleven cases over throwaway repositories, plus guards that the Makefile keeps composing both halves of the linker flags and that no raw go build returns to the Dockerfile), and TestFooterReportsStampedVersion in internal/handlers.

Implemented in https://git.eeqj.de/sneak/webhooker/pulls/260 (branch `issue-253-version-stamping`, base `next`). Built as planned, with one addition the plan did not anticipate: stamping the binary alone did not fix the footer. `base.html` renders `.Version` with a literal `dev` fallback, and nothing ever put `Version` into the template data — so the footer read `dev` even from a correctly stamped binary. `renderTemplate` now supplies it. Verified (full output and evidence in the PR body): - `make check` green with `GOFLAGS=-count=1` — 21 packages, 572 tests, `0 issues.` from the containerized linter. - Throwaway local tag `v0.0.0-impl253`, deleted before pushing: a `make build` binary answers `/.well-known/healthcheck` with `"version":"v0.0.0-impl253"` — exactly the tag, no suffix. - A `make docker` image at the same tag reports the same on the healthcheck, in the footer (`<span>v0.0.0-impl253</span>`) and in the startup log. Read off the running binary, so the stamp survives the static relink; it is running on Alpine, which a non-static binary could not. - Two `make build` runs of one commit are byte-identical (`dc38ae7e...4349` twice). Nothing time- or host-dependent is stamped. - No git metadata: a `git archive` tarball and a bare `docker build .` both build, run, and report `"version":"unknown"`. Tests: new `internal/versionscript` package (eleven cases over throwaway repositories, plus guards that the `Makefile` keeps composing both halves of the linker flags and that no raw `go build` returns to the `Dockerfile`), and `TestFooterReportsStampedVersion` in `internal/handlers`.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/webhooker#253