From 10812606bf98f9047bee135d853727dfae4c6d03 Mon Sep 17 00:00:00 2001 From: Niklas Ye Date: Tue, 1 Sep 2026 21:47:59 +0200 Subject: [PATCH] =?UTF-8?q?L=C3=A4gg=20terdut-server=20under=20den=20gemen?= =?UTF-8?q?samma=20sl=C3=A4ppprocessen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Släppprocessen (~/.claude/skills/release) körde hittills bara riksdata och rd-web, och vägrade den här katalogen med "not one of the release-managed repos". Den kräver två saker: en .release.conf och ett release-vars-mål som skriver ut IMAGE, HELM_CHART och HELM_REPO. Poängen med att fråga make i stället för att upprepa värdena i processen är att de bara kan ha en definition, så det som taggas, det som pushas och det som wrappern pinnar inte kan glida isär. Makefilen är avsiktligt inte en kopia av riksdatas. Två skillnader: fmt, lint och test speglar .gitea/workflows/ci.yaml steg för steg, så ett grönt "make fmt lint test" här betyder samma sak som en grön CI. gofmt-målet är kopierat ordagrant och inte förenklat, eftersom gofmts två felsätt inte är lika: en felformaterad fil listas på stdout med exit 0, medan en fil som inte går att parsa ger tom stdout och exit 2 — och den naiva varianten läser det andra som framgång (9046f6e). Undantaget är -race, som CI inte kör: sveparen, notifieraren och deadman-svepet delar en enda databasanslutning, och en kapplöpning där dyker upp som en flaxig incident i produktion i stället för som ett rött bygge. Det finns medvetet inga build-, push- eller helm-push-mål, till skillnad från riksdata och rd-web. Här äger .gitea/workflows/release.yaml publiceringen, och den gör två saker en lokal make inte gör: bygger linux/amd64 och linux/arm64 genom buildx, och stämplar chartets version och appVersion från taggen. Ett vanligt "docker build && docker push" skulle lägga en enarkitektursbild över den multiarkitekturella taggen — lätt att göra av misstag och osynligt efteråt, eftersom taggen fortfarande svarar, bara inte på arm64. Publicering sker genom att pusha en tagg, inget annat. Claude-Session: https://claude.ai/code/session_01S7R4gWTz5wh5xCY4nCSJjN --- .release.conf | 17 +++++++++++ Makefile | 84 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 .release.conf create mode 100644 Makefile 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.