# Foodster. Run `make` for the target list. COMPOSE ?= podman compose BIN := foodster PKG := ./cmd/foodster STATIC := cmd/foodster/static # What `make image` last built. push reads it rather than re-deriving the tag: # sorting tags by date is ambiguous when two point at the same commit, and # re-deriving is what let a parallel make push the wrong one. TAGFILE := .release-tag # Vendored Datastar client. Bump, run `make vendor`, commit the result. DATASTAR_VERSION ?= v1.0.3 SEED ?= seeds/testi.json # Registry coordinates, shared password and TZ live here. Gitignored. ifneq (,$(wildcard .env)) include .env export endif # Generated templ output is excluded — it is not ours to format. GOFILES = $(shell find . -name '*.go' -not -name '*_templ.go' 2>/dev/null) .DEFAULT_GOAL := help .PHONY: help generate build run seed test smoke check lint fix icons vendor image push release up down logs clean help: ## Show this help @grep -hE '^[a-zA-Z_-]+:.*## ' $(MAKEFILE_LIST) \ | awk -F':.*## ' '{printf " \033[1m%-9s\033[0m %s\n", $$1, $$2}' generate: ## Generate Go from .templ files go tool templ generate build: generate ## Build ./foodster CGO_ENABLED=0 go build -trimpath \ -ldflags="-s -w -X main.version=dev" -o $(BIN) $(PKG) run: generate ## Run locally on :8080 (database in ./data) FOODSTER_PASSWORD=$${FOODSTER_PASSWORD:-dev} go run $(PKG) seed: ## Import a dish bundle (SEED=seeds/testi.json) go run $(PKG) -import $(SEED) test: generate ## Run unit tests go test ./... smoke: generate ## End-to-end check: auth, static assets, bundle import ./scripts/smoke.sh # Sub-makes rather than prerequisites: these must run in order even under # `make -j`, and a parallel run vets generated code that is being rewritten. check: ## Everything that must pass before a commit @$(MAKE) --no-print-directory lint @$(MAKE) --no-print-directory test @$(MAKE) --no-print-directory smoke @echo "check: all passed" icons: ## Rasterise home-screen PNGs from assets/icon.svg and optimise them rsvg-convert -w 180 -h 180 assets/icon.svg -o $(STATIC)/apple-touch-icon.png rsvg-convert -w 192 -h 192 assets/icon.svg -o $(STATIC)/icon-192.png rsvg-convert -w 512 -h 512 assets/icon.svg -o $(STATIC)/icon-512.png # oxipng -o max alone loses to optipng on the 512; --zopfli wins at every # size. Slow, but these are three tiny files built by hand. oxipng -o max --zopfli --quiet \ $(STATIC)/apple-touch-icon.png $(STATIC)/icon-192.png $(STATIC)/icon-512.png @ls -l $(STATIC)/*.png vendor: ## Re-download the Datastar client (DATASTAR_VERSION=v1.0.3) curl -sSfL -o $(STATIC)/datastar.js \ "https://cdn.jsdelivr.net/gh/starfederation/datastar@$(DATASTAR_VERSION)/bundles/datastar.js" @head -1 $(STATIC)/datastar.js lint: generate ## go vet, gofmt check, golangci-lint when installed go vet ./... @bad=$$(gofmt -l $(GOFILES) 2>/dev/null); \ if [ -n "$$bad" ]; then echo "gofmt needed:"; echo "$$bad"; exit 1; fi @if command -v golangci-lint >/dev/null 2>&1; then golangci-lint run; \ else echo "golangci-lint not installed - skipped"; fi fix: ## Format Go and templ sources, tidy go.mod @if [ -n "$(GOFILES)" ]; then gofmt -w $(GOFILES); fi go tool templ fmt . go mod tidy image: ## Build and tag an image as vYYYYMMDD-N. Creates a git tag. @test -n "$(FOODSTER_REPO)" || { echo "set FOODSTER_REPO in .env"; exit 1; } @# A release tag must point into main, or the tag records a commit that @# was never released. @branch=$$(git symbolic-ref --short HEAD); \ if [ "$$branch" != "main" ]; then \ echo "releases are cut from main, not $$branch:"; \ echo " git switch main && git merge --ff-only dev"; \ exit 1; \ fi @day=$$(date +%Y%m%d); \ tag="v$$day-$$(( $$(git tag -l "v$$day-*" | wc -l) + 1 ))"; \ echo "==> $$tag"; \ git tag "$$tag"; \ podman build --platform linux/amd64 --build-arg VERSION="$$tag" \ -t "$(FOODSTER_REPO):$$tag" -t "$(FOODSTER_REPO):latest" . ; \ echo "$$tag" > $(TAGFILE) # Pushing reported success while uploading the previous release once, because # nothing compared what was built against what arrived. So afterwards, ask the # registry what it actually serves for each tag and fail if it is not the # image we just built. push: ## Push the newest tag and :latest, then verify the registry @test -n "$(FOODSTER_REPO)" || { echo "set FOODSTER_REPO in .env"; exit 1; } @test -f $(TAGFILE) || { echo "nothing built - run make image"; exit 1; }; \ tag=$$(cat $(TAGFILE)); \ built=$$(podman image inspect "$(FOODSTER_REPO):$$tag" --format '{{.Id}}' 2>/dev/null) || \ { echo "no local image tagged $$tag - run make image"; exit 1; }; \ podman push "$(FOODSTER_REPO):$$tag"; \ podman push "$(FOODSTER_REPO):latest"; \ echo "==> verifying $$tag"; \ for ref in "$$tag" latest; do \ podman pull -q "$(FOODSTER_REPO):$$ref" >/dev/null 2>&1 || \ { echo " FAIL $$ref is not in the registry"; exit 1; }; \ served=$$(podman image inspect "$(FOODSTER_REPO):$$ref" --format '{{.Id}}'); \ if [ "$$served" != "$$built" ]; then \ echo " FAIL $$ref serves $$served"; \ echo " expected $$built"; \ exit 1; \ fi; \ echo " ok $$ref"; \ done # Sub-makes, not prerequisites. Under `make -j` — and -j16 is the default on # at least one machine here — these run concurrently, so push resolves the # newest tag and uploads :latest before image has finished building and # tagging. That silently ships the previous release a second time. release: ## Build, tag and push in one go @$(MAKE) --no-print-directory image @$(MAKE) --no-print-directory push up: ## Start the stack @mkdir -p data # or the engine creates it root-owned and the app cannot write $(COMPOSE) up -d down: ## Stop the stack $(COMPOSE) down logs: ## Follow app logs $(COMPOSE) logs -f app clean: ## Remove the binary and generated templates rm -f $(BIN) find . -name '*_templ.go' -delete