From ea40b31e50102c7cdd927cd149638e009ee951a6 Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Mon, 5 Oct 2026 23:28:36 +0000 Subject: [PATCH] Re-sync script/docker and script/cibuild with the model scripts (closes #265) script/docker and script/cibuild are again byte-identical to the shared model scripts. Both compute the version on the host with git describe, pass it as the VERSION build arg and build with --no-cache; script/cibuild runs script/bootstrap and script/check first. The CI build now stamps a real version instead of unknown. make build compiles with -trimpath and -s -w, as the model Dockerfile does. script/version stays: the Makefile and the Dockerfile still use it. The README describes both scripts as they now are. Model: opus-5-5 --- Makefile | 5 ++++- README.md | 41 ++++++++++++++++++++++++++--------------- script/cibuild | 26 ++++++++++++++++++++------ script/docker | 19 ++++++++++++------- 4 files changed, 62 insertions(+), 29 deletions(-) diff --git a/Makefile b/Makefile index 59afeeb..cb148ba 100644 --- a/Makefile +++ b/Makefile @@ -48,8 +48,11 @@ fmt-check: check: @script/check +# -trimpath keeps the build directory out of the binary, so two builds of one +# commit match wherever they ran; -s -w drop the symbol table and debug info, +# which panics and stack traces do not use. build: assets - go build -ldflags '$(strip -X main.version=$(VERSION) $(GO_LDFLAGS))' -o bin/webhooker ./cmd/webhooker + go build -trimpath -ldflags '$(strip -s -w -X main.version=$(VERSION) $(GO_LDFLAGS))' -o bin/webhooker ./cmd/webhooker run: build ./bin/webhooker diff --git a/README.md b/README.md index d97a78f..081fea0 100644 --- a/README.md +++ b/README.md @@ -1095,7 +1095,8 @@ field), in the UI footer, and in the startup log line (`msg=starting`, The value is stamped in at build time by the linker; it is not read from a file at runtime, so it identifies the build itself. -`script/version` produces the value and both build paths use it: +`script/version` produces the value for `make build`, from +`git describe --tags --always --dirty`: | Build | What it reports | | ----------------------- | --------------------------------------------------- | @@ -1118,7 +1119,8 @@ https://git.eeqj.de/sneak/upaas/issues/274. git in the build reads the checkout whoever owns its files, since a context sent as a tar archive keeps the sender's owners and git otherwise refuses a checkout owned by another user. A `VERSION` build arg (`--build-arg VERSION=...`) takes precedence; `script/docker` (and so -`make docker`) passes the one `script/version` resolves on the host. The image +`make docker`) and `script/cibuild` always pass one, from the same +`git describe` run on the host, or `unknown` when that gives nothing. The image build fails if its context carries `.git` and the version still comes out `unknown`, which means git is missing from the build or could not read the checkout. @@ -1134,8 +1136,9 @@ to a commit. `""`, and resolves the way an absent one does. Nothing that varies between two builds of the same commit is stamped — no -timestamp, no hostname, no builder identity — so two builds of one commit still -produce a byte-identical binary. +timestamp, no hostname, no builder identity — and `make build` compiles with +`-trimpath`, which keeps the build directory out of the binary, so two builds of +one commit still produce a byte-identical binary wherever they ran. ### Backups contain secrets @@ -1240,10 +1243,13 @@ We provide: - `script/check` — run test, lint, fmt-check, and css-check - `script/version` — output the version to stamp into the binary (see [Version stamping](#version-stamping)) -- `script/docker` — build the Docker image tagged via `script/projectname`, - passing `script/version`'s output in as the `VERSION` build arg -- `script/cibuild` — CI entrypoint: `docker build .` (the Dockerfile runs the - checks, so a green build implies a green repo) +- `script/docker` — build the Docker image tagged via `script/projectname`, with + `--no-cache` so no check is replayed from cache, passing the version + `git describe --tags --always --dirty` gives on the host (`unknown` if none) + in as the `VERSION` build arg +- `script/cibuild` — CI entrypoint: run `script/bootstrap` and `script/check` on + the host, then build the image exactly as `script/docker` does (the Dockerfile + runs the checks again, so a green build implies a green repo) - `script/ci-mark-superseded` — CI helper: mark the commits whose run a newer push cancelled (see [CI gate honesty](#ci-gate-honesty)) - `script/precommit` — pre-commit checks (`go mod tidy` guard, then @@ -1251,6 +1257,11 @@ We provide: - `script/install-precommit` — install the git pre-commit hook that runs `script/precommit` +`script/docker` and `script/cibuild` are byte-identical copies of the shared +model scripts at +`https://git.eeqj.de/sneak/prompts/raw/branch/main/script/`. A change to +either is made there and copied here, never made here alone. + ## Third-party browser assets The web UI serves one third-party script, Alpine.js, in its CSP build: the npm @@ -3439,13 +3450,13 @@ The lint and builder stages use Debian rather than Alpine because not compile against musl. Only the final binary is statically linked, which is what lets it run on the Alpine runtime image. -`script/cibuild` — `docker build .` — is the CI gate: the checks run inside the -image, so a build that succeeds is a repo that is formatted, linted, tested and -compiled, with a current stylesheet. `script/lint` also uses Docker -(`Dockerfile.lint` and the `js-lint` stage, see Linting above), so `make lint` -and `make check` run the same pinned linter versions the gate does; of the steps -`make check` runs, only `script/test` and the `gofmt` check in -`script/fmt-check` run on the host. +`script/cibuild` is the CI gate: it runs `script/check`, then builds the image +with `--no-cache`, and the checks run again inside the image, so a build that +succeeds is a repo that is formatted, linted, tested and compiled, with a +current stylesheet. `script/lint` also uses Docker (`Dockerfile.lint` and the +`js-lint` stage, see Linting above), so `make lint` and `make check` run the +same pinned linter versions the gate does; of the steps `make check` runs, only +`script/test` and the `gofmt` check in `script/fmt-check` run on the host. #### CI gate honesty diff --git a/script/cibuild b/script/cibuild index ae14eec..688299f 100755 --- a/script/cibuild +++ b/script/cibuild @@ -1,15 +1,29 @@ #!/bin/sh -# script/cibuild: run the CI build. The Dockerfile runs the checks (the -# gofmt check, golangci-lint, the stylesheet check, ESLint, the Markdown -# check, make test), so a successful build implies a green repo. Generic: -# needs no adaptation. The Gitea workflow runs this on push. +# script/cibuild: run the CI build. It bootstraps first: a CI runner +# checks out and runs this and nothing else, and script/fmt-check runs +# the formatter on the host, which a pristine checkout cannot do. +# --no-cache for the same reason as script/docker: the gate phases the +# final stage depends on are RUN steps, and a cached one is a check that +# did not run. set -eu -ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)" +ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)" main() { cd "$ROOT" - docker build . + "$SCRIPT_DIR/bootstrap" + "$SCRIPT_DIR/check" + # Own line: a failing command substitution inside an argument does + # not trip `set -e`, so the inline form degrades silently to an + # empty constant. VERSION is computed here because .dockerignore + # excludes .git, so `git describe` in a build stage yields an empty + # version without failing. + version="$(git describe --tags --always --dirty 2>/dev/null || true)" + [ -n "$version" ] || version="unknown" + docker build --no-cache \ + --build-arg VERSION="$version" \ + -t "$("$SCRIPT_DIR/projectname")" . } main "$@" diff --git a/script/docker b/script/docker index ba8a836..c4688e8 100755 --- a/script/docker +++ b/script/docker @@ -1,10 +1,8 @@ #!/bin/sh # script/docker: build the Docker image tagged with the project name. -# The tag comes from script/projectname. -# -# The version script/version resolves here goes in as the VERSION build -# arg, which takes precedence over what the build would derive from the -# .git in its context. +# Identical in all repos; the tag comes from script/projectname. +# --no-cache because the gate phases the final stage depends on are RUN +# steps, and a cached one is a check that did not run. set -eu SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)" @@ -12,8 +10,15 @@ ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)" main() { cd "$ROOT" - docker build \ - --build-arg VERSION="$("$SCRIPT_DIR/version")" \ + # Own line: a failing command substitution inside an argument does + # not trip `set -e`, so the inline form degrades silently to an + # empty constant. VERSION is computed here because .dockerignore + # excludes .git, so `git describe` in a build stage yields an empty + # version without failing. + version="$(git describe --tags --always --dirty 2>/dev/null || true)" + [ -n "$version" ] || version="unknown" + docker build --no-cache \ + --build-arg VERSION="$version" \ -t "$("$SCRIPT_DIR/projectname")" . }