diff --git a/.dockerignore b/.dockerignore index 416281f..033ab7b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,3 +4,6 @@ tmp .DS_Store *.log .claude + +# .git is sent so the build can stamp the version, without its config. +.git/config diff --git a/Dockerfile b/Dockerfile index d13e53e..4203274 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,7 @@ RUN make lint # golang:1.25-alpine (2026-02-27) FROM golang:1.25-alpine@sha256:f6751d823c26342f9506c03797d2527668d095b0a15f1862cddb4d927a7a4ced AS builder -RUN apk add --no-cache make +RUN apk add --no-cache git make WORKDIR /src @@ -37,11 +37,24 @@ RUN make test # make build is a shim around backend/script/build, the one definition # of the build command: -# CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -X main.Version=... -X main.Buildarch=..." +# CGO_ENABLED=0 go build -trimpath -ldflags "-s -w -X main.Version=..." # That script reads VERSION from the environment, so it is handed over # there rather than as a make variable. -ARG VERSION=dev -RUN VERSION="${VERSION}" make build +# +# The version is the VERSION build argument when one is given, otherwise +# `git describe --tags --always` of the repo's .git: the tag on a tagged +# commit, tag-N-gHASH on a commit after one, the short commit when no +# tag is reachable. A version that still comes out empty, dev or unknown +# fails the build. .git goes to /git, not /src/.git, where go build would +# find it and record VCS details of a work tree holding only backend/. +COPY .git /git +ARG VERSION +RUN version="${VERSION:-$(git --git-dir=/git describe --tags --always)}"; \ + case "$version" in ""|dev|unknown) \ + echo "version is '$version' although .git is present" >&2; \ + exit 1 ;; \ + esac; \ + VERSION="$version" make build # Frontend stage # node:22-alpine as of 2026-02-22 diff --git a/backend/README.md b/backend/README.md index 9914eb0..82ca05e 100644 --- a/backend/README.md +++ b/backend/README.md @@ -32,7 +32,7 @@ pattern as the repo root: the targets in `backend/Makefile` are thin shims over `test`, `fmt` and `fmt-check`: - `script/build` — compile the static `netwatch-server` binary with its version - and architecture stamped in. The version is `VERSION` from the environment; + stamped in. The version is `VERSION` from the environment; when that is unset or empty, it falls back to `git describe` inside a git checkout, then to `dev` - `script/test` — run the Go tests under a 30-second timeout diff --git a/backend/cmd/netwatch-server/main.go b/backend/cmd/netwatch-server/main.go index 01c77a4..7410367 100644 --- a/backend/cmd/netwatch-server/main.go +++ b/backend/cmd/netwatch-server/main.go @@ -19,9 +19,8 @@ import ( //nolint:gochecknoglobals // set via ldflags at build time var ( - Appname = "netwatch-server" - Version string - Buildarch string + Appname = "netwatch-server" + Version string ) func main() { @@ -40,7 +39,6 @@ func main() { globals.Appname = Appname globals.Version = Version - globals.Buildarch = Buildarch fx.New( fx.Provide( diff --git a/backend/internal/globals/globals.go b/backend/internal/globals/globals.go index 0e3807b..c6a48a2 100644 --- a/backend/internal/globals/globals.go +++ b/backend/internal/globals/globals.go @@ -10,22 +10,18 @@ var ( Appname string // Version is the git version tag. Version string - // Buildarch is the build architecture. - Buildarch string ) // Globals holds build-time metadata for the application. type Globals struct { - Appname string - Version string - Buildarch string + Appname string + Version string } // New creates a Globals instance from package-level variables. func New(_ fx.Lifecycle) (*Globals, error) { return &Globals{ - Appname: Appname, - Buildarch: Buildarch, - Version: Version, + Appname: Appname, + Version: Version, }, nil } diff --git a/backend/internal/logger/logger.go b/backend/internal/logger/logger.go index de6c884..fb31eeb 100644 --- a/backend/internal/logger/logger.go +++ b/backend/internal/logger/logger.go @@ -5,6 +5,7 @@ package logger import ( "log/slog" "os" + "runtime" "sneak.berlin/go/netwatch/internal/globals" @@ -95,6 +96,6 @@ func (l *Logger) Identify() { l.log.Info("starting", "appname", l.params.Globals.Appname, "version", l.params.Globals.Version, - "buildarch", l.params.Globals.Buildarch, + "arch", runtime.GOARCH, ) } diff --git a/backend/internal/server/http.go b/backend/internal/server/http.go index 46f4a67..df66092 100644 --- a/backend/internal/server/http.go +++ b/backend/internal/server/http.go @@ -4,6 +4,7 @@ import ( "errors" "net" "net/http" + "runtime" "strconv" "time" @@ -53,7 +54,7 @@ func (s *Server) listenAndServe() { s.log.Info("http begin listen", "listenaddr", s.httpServer.Addr, "version", s.params.Globals.Version, - "buildarch", s.params.Globals.Buildarch, + "arch", runtime.GOARCH, ) err := s.httpServer.ListenAndServe() diff --git a/backend/script/build b/backend/script/build index 40d4d01..4f30155 100755 --- a/backend/script/build +++ b/backend/script/build @@ -1,6 +1,6 @@ #!/bin/sh # script/build: compile the static netwatch-server binary into the -# backend project root, with its version and architecture stamped in. +# backend project root, with its version stamped in. set -eu ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" @@ -14,7 +14,7 @@ main() { version="${VERSION:-$(git describe --always --dirty 2>/dev/null || echo dev)}" CGO_ENABLED=0 go build -trimpath \ - -ldflags "-s -w -X main.Version=$version -X main.Buildarch=$(uname -m)" \ + -ldflags "-s -w -X main.Version=$version" \ -o netwatch-server ./cmd/netwatch-server/ } diff --git a/script/cibuild b/script/cibuild index 688299f..d8d3200 100755 --- a/script/cibuild +++ b/script/cibuild @@ -16,9 +16,8 @@ main() { "$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. + # 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 \ diff --git a/script/docker b/script/docker index c4688e8..07b626c 100755 --- a/script/docker +++ b/script/docker @@ -12,9 +12,8 @@ main() { cd "$ROOT" # 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. + # 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 \