diff --git a/.dockerignore b/.dockerignore index 3224cdb..90beabd 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,64 @@ +# .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-* + +# This repo's own host-built archives (Makefile). *.tmp *.dockerimage -.git -node_modules diff --git a/Dockerfile b/Dockerfile index 82c49a1..4a67bac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -48,7 +48,22 @@ COPY . . RUN touch mfer/mf.pb.go RUN make test -RUN cd cmd/mfer && go build -tags urfave_cli_no_docs -o /mfer . + +# The revision `mfer version` prints, stamped into main.Gitrev: the VERSION +# build argument when one is given (script/docker passes one), 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. git ships in this base image. A context that +# carries .git and still yields no version fails the build. +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; \ + cd cmd/mfer && \ + go build -tags urfave_cli_no_docs -ldflags "-X main.Gitrev=$version" -o /mfer . FROM scratch COPY --from=builder /mfer /mfer diff --git a/TODO.md b/TODO.md index 35bbf76..c8cb9f2 100644 --- a/TODO.md +++ b/TODO.md @@ -24,6 +24,12 @@ only thing left of the `chore/align-repo-policies` branch is the list below. # Completed Steps +- 2026-10-02: a plain `docker build .` of a clone now stamps the tag or short + commit into `mfer version` instead of nothing: `.dockerignore` sends `.git` + (not `.git/config`), and the build stage takes the `VERSION` build argument, + otherwise `git describe --tags --always`, failing if `.git` is present and no + version comes out. `script/docker` is the canonical copy, which passes + `VERSION`; `bin/gitrev.sh` uses `--tags` too (#112) - 2026-09-21: validate manifest entry paths on deserialize so untrusted `.mf` files cannot make `Checker` stat or read outside `basePath` (#61) - 2026-09-21: rewrote `script/test` to the canonical pattern (30s timeout, diff --git a/bin/gitrev.sh b/bin/gitrev.sh index 42a9e60..bc2d943 100644 --- a/bin/gitrev.sh +++ b/bin/gitrev.sh @@ -3,5 +3,5 @@ if [[ ! -z "$GITREV" ]]; then echo $GITREV else - git describe --always --dirty=-dirty + git describe --tags --always --dirty=-dirty fi diff --git a/script/docker b/script/docker index 2884e41..07b626c 100755 --- a/script/docker +++ b/script/docker @@ -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 "$@"