Stamp the git tag or short commit into the binary (closes #4)
check / check (pull_request) Blocked by required conditions

A plain `docker build .` now stamps the version into bsdaily: the
VERSION build argument when one is given, otherwise `git describe
--tags --always` of the .git in the build context. The build fails if
the context carries .git and no version comes out. A host `make` build
stamps the same `git describe` value. bsdaily prints it with --version.

The new .dockerignore is the canonical copy, which keeps .git/config
out of the build context. script/docker is replaced with the canonical
copy, which passes the version it derives on the host.

Model: opus-5-5
This commit is contained in:
2026-10-02 07:59:16 +00:00
parent e2dd090309
commit 613817c4fd
6 changed files with 103 additions and 11 deletions
+60
View File
@@ -0,0 +1,60 @@
# .dockerignore does NOT use .gitignore semantics. Docker matches with
# moby/patternmatcher: filepath.Match plus `**`, so `*` does not cross
# `/` and an unprefixed pattern is anchored at the context root. Every
# depth-independent pattern therefore needs `**/`, or `config/.env` and
# `certs/server.key` still ship while this file reads as solved. Only
# genuinely root-anchored entries go unprefixed. Never transplant these
# into .gitignore, where `**/` is wrong.
#
# Matching is case-sensitive, so secrets use character ranges rather
# than an ALL-CAPS twin, which would still miss `Server.Key`.
#
# Extend with this repo's own host-built artifacts, written anchored:
# `/myapp`, never `**/myapp`, which also matches `cmd/myapp/` and
# deletes the package directory from the context.
# .git is sent without its config. Without a VERSION build argument the
# stage that compiles runs `git describe --tags --always` on .git, which
# does not need .git/config; that file can hold a credential, such as a
# password in a remote URL or the token the CI checkout step stores there.
.git/config
# Agent scratch: one full checkout of the repo per in-flight agent.
# Anchored because it occurs once where agents run at the repo root.
# KNOWN GAP: a repo running agents in subdirectories still ships
# `services/api/.claude/` and must add its own anchored entry.
.claude
# Environment files. `*.env` covers bare `.env` and the `prod.env`
# convention. Re-include a committed template with a negation if the
# build needs one: `!docs/example.env`.
**/*.[eE][nN][vV]
**/.[eE][nN][vV].*
**/.[eE][nN][vV][rR][cC]
# Private keys and the bundles carrying them. Public certificates
# (*.crt, *.cer) are deliberately absent: they are legitimate inputs.
**/*.[pP][eE][mM]
**/*.[kK][eE][yY]
**/*.[pP]12
**/*.[pP][fF][xX]
**/[iI][dD]_[rR][sS][aA]
**/[iI][dD]_[dD][sS][aA]
**/[iI][dD]_[eE][cC][dD][sS][aA]
**/[iI][dD]_[eE][dD]25519
# Dependencies: restored inside the image, never copied in.
**/node_modules
# OS metadata.
**/.DS_Store
**/Thumbs.db
# Editor state: never a build input, and it churns COPY.
**/*.swp
**/*.swo
**/*~
**/*.bak
**/.idea
**/.vscode
**/*.sublime-*
+19 -6
View File
@@ -24,10 +24,9 @@ FROM golang:1.26.4-alpine@sha256:3ad57304ad93bbec8548a0437ad9e06a455660655d9af01
# Depend on lint stage passing
COPY --from=lint /src/go.sum /dev/null
ARG VERSION=dev
# Install build deps plus the sqlite3 and zstd CLIs the tests/tool shell out to
RUN apk add --no-cache make build-base sqlite zstd
# Install build deps plus the sqlite3 and zstd CLIs the tests/tool shell out
# to, and git, which the build step below derives the version with
RUN apk add --no-cache make build-base sqlite zstd git
WORKDIR /src
@@ -41,8 +40,22 @@ COPY . .
# Run tests
RUN make test
# Build (pure Go, no CGO required since we use modernc.org/sqlite)
RUN CGO_ENABLED=0 go build -o /bsdaily ./cmd/bsdaily
# Build (pure Go, no CGO required since we use modernc.org/sqlite).
# The version stamped into the binary: the VERSION build argument when one is
# given, otherwise `git describe --tags --always` of the .git the build context
# carries: the tag on a tagged commit, tag-N-gHASH on a commit after one, the
# short commit when no tag is reachable. A context that carries .git and still
# yields no version fails the build. With neither, as from a source tarball,
# the binary reports dev.
ARG VERSION
RUN version="${VERSION:-$(git describe --tags --always)}"; \
if [ -e .git ] && { [ -z "$version" ] || [ "$version" = dev ] || \
[ "$version" = unknown ]; }; then \
echo "no version could be derived although the build context carries .git" >&2; \
exit 1; \
fi; \
CGO_ENABLED=0 go build -ldflags="-X main.Version=${version:-dev}" \
-o /bsdaily ./cmd/bsdaily
# Runtime stage
# alpine:3.21
+4 -3
View File
@@ -1,7 +1,8 @@
.PHONY: all bootstrap setup check test lint fmt fmt-check build clean deps test-coverage test-integration install release release-snapshot docker hooks
# Version number
VERSION := 0.1.0-dev
# Stamped into the binary: the same `git describe` the Dockerfile runs, so a
# host build reports the same version as the image.
VERSION ?= $(shell git describe --tags --always)
# Default target
all: bsdaily
@@ -36,7 +37,7 @@ lint:
# Build binary (pure Go; no CGO required since we use modernc.org/sqlite).
bsdaily: internal/*/*.go cmd/bsdaily/*.go
CGO_ENABLED=0 go build -o $@ ./cmd/bsdaily
CGO_ENABLED=0 go build -ldflags "-X main.Version=$(VERSION)" -o $@ ./cmd/bsdaily
# Clean build artifacts.
clean:
+3
View File
@@ -20,6 +20,9 @@ green with the new lint config.
# Completed Steps
- 2026-10-02: A plain `docker build .` and a host `make` build stamp the git tag
or short commit into the binary, which `bsdaily --version` prints; added the
canonical `.dockerignore`, which keeps `.git/config` out of the build context.
- 2026-07-07 Adopted scripts-to-rule-them-all: `script/` entrypoints,
Makefile shims, README Entrypoints section
- 2026-06-28: Fixed errcheck lint failures; added compilation smoke test;
+6
View File
@@ -10,6 +10,11 @@ import (
"github.com/spf13/cobra"
)
// Version is the git tag or short commit, set at link time with -X by the
// Dockerfile and the Makefile, and printed by --version. Builds that do not set
// it report dev.
var Version = "dev"
func main() {
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: slog.LevelInfo,
@@ -23,6 +28,7 @@ func main() {
rootCmd := &cobra.Command{
Use: "bsdaily",
Short: "Extract a single day's data from the latest daily snapshot",
Version: Version,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
hasDate := dateFlag != ""
+11 -2
View File
@@ -1,7 +1,8 @@
#!/bin/sh
# script/docker: build the Docker image tagged with the project name.
# Identical in all repos; the tag comes from script/projectname.
# Generic: needs no adaptation.
# --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)"
@@ -9,7 +10,15 @@ ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
main() {
cd "$ROOT"
docker build -t "$("$SCRIPT_DIR/projectname")" .
# Own line: a failing command substitution inside an argument does
# not trip `set -e`, so the inline form degrades silently to an
# empty constant. The VERSION build argument takes precedence over
# the version a build stage derives from the .git in the context.
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 "$@"