diff --git a/.release.conf b/.release.conf new file mode 100644 index 0000000..6401c12 --- /dev/null +++ b/.release.conf @@ -0,0 +1,17 @@ +# Read by the `release` skill (~/.claude/skills/release). +# +# Only what the Makefile cannot already say. IMAGE, HELM_CHART and HELM_REPO come from +# `make release-vars`, so they have one definition and cannot drift from what is built. +# +# Defaults, set here only where this repo differs: +# CHARTS_REPO=$HOME/git/charts CHARTS_DIR= +# GITEA_LOGIN=Ryuvia APPVERSION_PREFIX= + +# Same as the image basename, so this is only stated to be read rather than derived. +CHARTS_DIR=terdut-server + +# riksdata writes appVersion: "v0.3.1", rd-web writes a bare 0.5.0; this repo writes the +# v, like riksdata. Nothing reads the field -- .gitea/workflows/release.yaml stamps both +# version and appVersion from the tag when it publishes -- but people read it, and until +# 2026-09-01 it said "latest" while the tree headed for a numbered release. +APPVERSION_PREFIX=v diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..95a7328 --- /dev/null +++ b/Makefile @@ -0,0 +1,84 @@ +REGISTRY := git.ryuvia.com +# The personal namespace, not ryuvia — deliberately, and for one reason: Gitea +# scopes package visibility to the owner with no per-package override, so +# ryuvia/* is private because the org is. Publishing here keeps the image and +# chart anonymously pullable, so no pull secret is needed in the cluster and +# Flux needs no registry credentials. Same choice riksdata and rd-web made. +OWNER := niklas + +IMAGE := $(REGISTRY)/$(OWNER)/terdut-server +HELM_CHART := charts/terdut-server +HELM_REPO := oci://$(REGISTRY)/$(OWNER) + +# go.mod pins an exact patch release so nobody builds the shipped binary with a +# toolchain carrying known stdlib CVEs. Fedora's Go package overrides the +# upstream GOTOOLCHAIN default to `local`, which turns that pin into a hard +# failure on a dev box one patch behind, so restore the upstream default here. +export GOTOOLCHAIN ?= auto + +.PHONY: help +help: ## Show this help + @grep -hE '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) | \ + awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-22s\033[0m %s\n", $$1, $$2}' + +## --- checks --- +# +# These three mirror .gitea/workflows/ci.yaml step for step, so a green `make fmt +# lint test` here means the same thing CI means. The one deliberate difference is +# -race below. + +.PHONY: test +test: ## Run the test suite + go test -race ./... + +# CI runs a bare `go test ./...`. This is stricter on purpose: the sweeper, the +# notifier goroutine and the deadman sweep all touch the same single-connection +# database, and a race there would surface as a flaky production incident rather +# than a failed build. It passes today; if it ever costs more than it catches, +# the honest fix is to teach CI -race too, not to quietly drop it here. +.PHONY: lint +lint: ## go vet + go vet ./... + +# Copied from ci.yaml rather than simplified, because both of gofmt's failure +# modes need handling and they are not alike. A file that is merely misformatted +# is listed on stdout with exit 0 — so the failure has to be raised by hand. A +# file that does not parse is the opposite: nothing on stdout and exit 2, which a +# naive `[ -n "$$out" ]` reads as success. See 9046f6e. +.PHONY: fmt +fmt: ## Report unformatted files + @if ! unformatted=$$(gofmt -l .); then \ + echo "gofmt could not parse the tree:"; gofmt -l .; exit 1; \ + fi; \ + if [ -n "$$unformatted" ]; then \ + echo "gofmt needed:"; echo "$$unformatted"; gofmt -d .; exit 1; \ + fi + +.PHONY: helm-lint +helm-lint: ## Lint and render the chart + helm lint $(HELM_CHART) --set image.tag=v0.0.0 + helm template terdut-server $(HELM_CHART) --namespace terdut-server \ + --set image.tag=v0.0.0 >/dev/null + @# networking.listener defaults to "", which attaches the route to every + @# matching listener including plaintext HTTP. Production sets it, so the + @# default render proves nothing about the path that actually ships. + helm template terdut-server $(HELM_CHART) --namespace terdut-server \ + --set image.tag=v0.0.0 --set networking.listener=https-terdut >/dev/null + +## --- release --- + +# The release process (~/.claude/skills/release) reads these rather than restating them. +# One definition, so the version that gets tagged, the image that gets pushed and the chart +# the wrapper pins cannot drift apart in a second copy. +.PHONY: release-vars +release-vars: ## Print the variables the release process reads + @printf 'IMAGE=%s\nHELM_CHART=%s\nHELM_REPO=%s\n' '$(IMAGE)' '$(HELM_CHART)' '$(HELM_REPO)' + +# There is deliberately no build/push/helm-package/helm-push/release here, unlike +# riksdata and rd-web. .gitea/workflows/release.yaml owns publishing for this repo, +# and it does two things a local make cannot: it builds linux/amd64 and linux/arm64 +# through buildx, and it stamps the chart's version and appVersion from the tag. A +# `docker build && docker push` target would push a single-architecture image over +# the multi-arch tag, which is both easy to do by accident and invisible afterwards +# — the tag would still resolve, just not on arm64. Publishing happens by pushing a +# tag; nothing else.