Add Debian packaging, Makefile, manpages, tests, and design doc

Introduces a static-binary build and Debian package (amd64/arm64) with
version/commit/date stamped via -ldflags. Ships section-1 manpages for
ctool, ctfetch, and ctail. Adds a `version` subcommand reachable as
`ctool version`, `ctool -version`, `ctool --version`, `ctool fetch
version`, `ctool tail version`, and via the ctfetch/ctail symlinks. Adds
tests covering the dispatcher, fetch/tail argument parsing, and the
formatter/helper functions. Adds a retrofit design document modelled on
the vpp-maglev one, with FRs and NFRs for each tool and the dispatcher.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-21 22:21:32 +02:00
parent bbd566d10e
commit e18a89dcf0
15 changed files with 1632 additions and 8 deletions

160
Makefile Normal file
View File

@@ -0,0 +1,160 @@
BINARIES := ctool
MODULE := git.ipng.ch/certificate-transparency/ctfetch
NATIVE_ARCH := $(shell go env GOARCH)
VERSION := 0.1.0
COMMIT_HASH := $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
DATE := $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
LDFLAGS := -X 'main.version=$(VERSION)' \
-X 'main.commit=$(COMMIT_HASH)' \
-X 'main.date=$(DATE)'
# CGO_ENABLED=0 produces fully static binaries: no libc dependency, so the
# debian package runs on any Linux-amd64/arm64 host regardless of glibc
# version (or musl). The only behavioural change is that Go's net package
# uses the pure-Go resolver instead of libc's getaddrinfo, which skips
# /etc/nsswitch.conf and NSS modules — fine for DNS-only lookups, which
# is all ctool does when resolving log hostnames.
export CGO_ENABLED := 0
# GO_VERSION is what install-deps-go downloads from go.dev when the
# system Go is missing or older than this. Matches the go.mod floor.
# Override on the command line to pull a specific patch release:
# make install-deps GO_VERSION=1.25.0
GO_VERSION ?= 1.24.6
# GOLANGCI_LINT_VERSION is the minimum golangci-lint version that
# install-deps-go-tools accepts. install-deps-go-tools always `go
# install`s @latest, then asserts the resulting binary reports a
# version >= this floor as a sanity check.
GOLANGCI_LINT_VERSION ?= 1.64.0
.PHONY: all build build-amd64 build-arm64 test lint fixstyle pkg-deb clean install-deps install-deps-apt install-deps-go install-deps-go-tools
all: build
build:
mkdir -p build/$(NATIVE_ARCH)
go build -ldflags "$(LDFLAGS)" -o build/$(NATIVE_ARCH)/ctool ./cmd/ctool/
build-amd64:
mkdir -p build/amd64
GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o build/amd64/ctool ./cmd/ctool/
build-arm64:
mkdir -p build/arm64
GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o build/arm64/ctool ./cmd/ctool/
# pkg-deb produces one .deb per architecture. Each package contains a
# single statically-linked ctool binary plus ctfetch and ctail symlinks,
# and the three section-1 manpages.
pkg-deb: build-amd64 build-arm64
debian/build-deb.sh ctool amd64 $(VERSION)
debian/build-deb.sh ctool arm64 $(VERSION)
test:
go test ./...
lint:
golangci-lint run ./...
fixstyle:
gofmt -w .
# install-deps is an opt-in "set up a fresh developer box" target. Tested
# on Debian Trixie; the apt half should also work on Bookworm and recent
# Ubuntu LTS. Splits into three sub-targets so they can be run individually:
#
# install-deps-apt — Debian-packaged build-time deps (git, make,
# dpkg-dev, curl, tar).
# install-deps-go — ensure a Go toolchain >= $(GO_VERSION) is on
# the system. Downloads the upstream tarball
# into /usr/local/go when the system Go is
# missing or older than the go.mod floor.
# install-deps-go-tools — `go install` the helpers this repo needs
# (golangci-lint) and assert it is new enough
# to understand current Go syntax.
#
# Each sub-target is idempotent and safe to re-run.
install-deps: install-deps-apt install-deps-go install-deps-go-tools
@echo ""
@echo "==> All build dependencies installed."
@echo " Make sure these are on PATH:"
@echo " /usr/local/go/bin (Go toolchain)"
@echo " \$$(go env GOPATH)/bin (golangci-lint, ...)"
install-deps-apt:
@set -eu; \
if [ "$$(id -u)" = 0 ]; then SUDO=""; else SUDO="sudo"; fi; \
echo "==> Installing apt packages (git, make, dpkg-dev, curl, tar)"; \
$$SUDO apt-get update; \
$$SUDO apt-get install -y --no-install-recommends \
git make dpkg-dev ca-certificates curl tar
# install-deps-go short-circuits when go env GOVERSION already reports a
# version >= GO_VERSION. Otherwise it downloads the official upstream
# tarball (https://go.dev/dl/) and extracts it to /usr/local/go, matching
# the layout that go.dev recommends and that most Debian setups use for
# "Go newer than apt provides".
install-deps-go:
@set -eu; \
if [ "$$(id -u)" = 0 ]; then SUDO=""; else SUDO="sudo"; fi; \
echo "==> Checking Go toolchain (required: $(GO_VERSION)+)"; \
if command -v go >/dev/null 2>&1; then \
CURRENT=$$(go env GOVERSION 2>/dev/null | sed 's/^go//'); \
OLDEST=$$(printf '%s\n%s\n' "$(GO_VERSION)" "$$CURRENT" | sort -V | head -n1); \
if [ "$$OLDEST" = "$(GO_VERSION)" ] && [ -n "$$CURRENT" ]; then \
echo " go$$CURRENT already installed (>= $(GO_VERSION)), skipping."; \
exit 0; \
fi; \
echo " go$$CURRENT is older than $(GO_VERSION), upgrading."; \
else \
echo " no Go toolchain on PATH, installing."; \
fi; \
DEB_ARCH=$$(dpkg --print-architecture); \
case "$$DEB_ARCH" in \
amd64) GOARCH=amd64 ;; \
arm64) GOARCH=arm64 ;; \
armhf) GOARCH=armv6l ;; \
*) echo " unsupported architecture: $$DEB_ARCH" >&2; exit 1 ;; \
esac; \
TARBALL="go$(GO_VERSION).linux-$$GOARCH.tar.gz"; \
URL="https://go.dev/dl/$$TARBALL"; \
echo " downloading $$URL"; \
curl -fsSL -o "/tmp/$$TARBALL" "$$URL"; \
echo " installing to /usr/local/go"; \
$$SUDO rm -rf /usr/local/go; \
$$SUDO tar -C /usr/local -xzf "/tmp/$$TARBALL"; \
rm -f "/tmp/$$TARBALL"; \
echo " installed $$(/usr/local/go/bin/go version)"
install-deps-go-tools:
@set -eu; \
if ! command -v go >/dev/null 2>&1; then \
export PATH="/usr/local/go/bin:$$PATH"; \
fi; \
echo "==> Installing Go tools via 'go install'"; \
echo " github.com/golangci/golangci-lint/v2/cmd/golangci-lint"; \
go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest; \
GOBIN="$$(go env GOBIN)"; \
if [ -z "$$GOBIN" ]; then GOBIN="$$(go env GOPATH)/bin"; fi; \
echo "==> Asserting golangci-lint version >= $(GOLANGCI_LINT_VERSION)"; \
if ! "$$GOBIN/golangci-lint" version >/dev/null 2>&1; then \
echo " ERROR: $$GOBIN/golangci-lint is not executable" >&2; \
exit 1; \
fi; \
INSTALLED=$$("$$GOBIN/golangci-lint" version 2>&1 | sed -En 's/.*has version v?([0-9][0-9.]*).*/\1/p' | head -n1); \
if [ -z "$$INSTALLED" ]; then \
echo " ERROR: could not parse golangci-lint version output" >&2; \
"$$GOBIN/golangci-lint" version >&2; \
exit 1; \
fi; \
OLDEST=$$(printf '%s\n%s\n' "$(GOLANGCI_LINT_VERSION)" "$$INSTALLED" | sort -V | head -n1); \
if [ "$$OLDEST" != "$(GOLANGCI_LINT_VERSION)" ]; then \
echo " ERROR: golangci-lint $$INSTALLED is older than the required $(GOLANGCI_LINT_VERSION)" >&2; \
exit 1; \
fi; \
echo " golangci-lint $$INSTALLED (>= $(GOLANGCI_LINT_VERSION)) OK"
clean:
rm -rf build/