With dev and prod open side by side in the same browser, the tabs were indistinguishable. ENV is written into the title of every page unless it says prod, so "dev · Foodster" picks itself out. The value is used verbatim, so ENV=staging labels itself too, and prod and production both count as unmarked so a stray capital cannot tag the real instance. Environment variables lose their prefix: PASSWORD, DB, ENV, ADDR, HOST, REPO, TAG. The container namespaces them already, and this matches how the other services here are configured. PUID/PGID are the exception rather than UID/GID. UID is read-only in bash, so a value set in .env would be silently replaced by the invoking shell's own and compose's user: would ignore what was asked for. Breaking for a running instance: the deployed .env has to be rewritten in the same deploy, or the app will refuse to start on an unset PASSWORD.
151 lines
5.7 KiB
Makefile
151 lines
5.7 KiB
Makefile
# 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)
|
|
PASSWORD=$${PASSWORD:-dev} ENV=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 "$(REPO)" || { echo "set 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 "$(REPO):$$tag" -t "$(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 "$(REPO)" || { echo "set REPO in .env"; exit 1; }
|
|
@test -f $(TAGFILE) || { echo "nothing built - run make image"; exit 1; }; \
|
|
tag=$$(cat $(TAGFILE)); \
|
|
built=$$(podman image inspect "$(REPO):$$tag" --format '{{.Id}}' 2>/dev/null) || \
|
|
{ echo "no local image tagged $$tag - run make image"; exit 1; }; \
|
|
podman push "$(REPO):$$tag"; \
|
|
podman push "$(REPO):latest"; \
|
|
echo "==> verifying $$tag"; \
|
|
for ref in "$$tag" latest; do \
|
|
podman pull -q "$(REPO):$$ref" >/dev/null 2>&1 || \
|
|
{ echo " FAIL $$ref is not in the registry"; exit 1; }; \
|
|
served=$$(podman image inspect "$(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
|