# restic-manager — common dev targets SHELL := /bin/bash BIN_DIR := bin SERVER_BIN := $(BIN_DIR)/restic-manager-server AGENT_BIN := $(BIN_DIR)/restic-manager-agent VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev) COMMIT ?= $(shell git rev-parse HEAD 2>/dev/null || echo none) DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ) VERSION_PKG := gitea.dcglab.co.uk/steve/restic-manager/internal/version LDFLAGS := -s -w -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.date=$(DATE) \ -X $(VERSION_PKG).Version=$(VERSION) -X $(VERSION_PKG).Commit=$(COMMIT) GOFLAGS := -trimpath DOCKER_IMAGE ?= gitea.dcglab.co.uk/steve/restic-manager DOCKER_TAG ?= dev # Tailwind standalone CLI — single binary, no Node toolchain. # See spec.md §4.1 / tasks.md P1-28 for why. TAILWIND_VERSION ?= v3.4.17 TAILWIND_OS := $(shell uname -s | tr A-Z a-z) TAILWIND_ARCH := $(shell uname -m | sed 's/x86_64/x64/;s/aarch64/arm64/') TAILWIND_BIN := $(BIN_DIR)/tailwindcss TAILWIND_URL := https://github.com/tailwindlabs/tailwindcss/releases/download/$(TAILWIND_VERSION)/tailwindcss-$(TAILWIND_OS)-$(TAILWIND_ARCH) TAILWIND_INPUT := web/styles/input.css TAILWIND_OUTPUT := web/static/css/styles.css # mdBook for the docs site (P5-01). Single static binary, no # Rust toolchain — same pattern as Tailwind. MDBOOK_VERSION ?= v0.4.51 MDBOOK_OS := $(shell uname -s | tr A-Z a-z) MDBOOK_TRIPLE := $(shell uname -m)-unknown-$(if $(filter darwin,$(MDBOOK_OS)),apple-darwin,linux-gnu) MDBOOK_BIN := $(BIN_DIR)/mdbook MDBOOK_TARBALL := mdbook-$(MDBOOK_VERSION)-$(MDBOOK_TRIPLE).tar.gz MDBOOK_URL := https://github.com/rust-lang/mdBook/releases/download/$(MDBOOK_VERSION)/$(MDBOOK_TARBALL) DOCS_BOOK_DIR := docs/book DOCS_BOOK_OUT := $(DOCS_BOOK_DIR)/book .PHONY: help build server agent test test-race lint fmt tidy clean run-server run-agent docker release tailwind tailwind-watch docs docs-watch setup hooks smoke-restart smoke-stop smoke-status smoke-logs smoke-deploy # ---- smoke-env tooling ------------------------------------------------- # The smoke server runs as a transient user-systemd unit so it survives # bash-tool boundaries and reboots-of-the-shell. Use `make smoke-restart` # any time you've rebuilt the server. `make smoke-deploy` is the full # rebuild + restage + restart workflow described in CLAUDE.md. SMOKE_UNIT := restic-manager-smoke SMOKE_DATA_DIR := $(HOME)/smoke/data SMOKE_LOG_FILE := $(HOME)/smoke/server.log SMOKE_BASE_URL := http://127.0.0.1:8080 SMOKE_LISTEN := :8080 help: @grep -E '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) | awk 'BEGIN{FS=":.*?## "};{printf " \033[36m%-14s\033[0m %s\n",$$1,$$2}' build: tailwind server agent ## Build server + agent into ./bin (incl. Tailwind CSS) server: ## Build the server binary @mkdir -p $(BIN_DIR) CGO_ENABLED=0 go build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(SERVER_BIN) ./cmd/server $(TAILWIND_BIN): @mkdir -p $(BIN_DIR) @echo "==> downloading tailwindcss $(TAILWIND_VERSION) ($(TAILWIND_OS)/$(TAILWIND_ARCH))" curl -fsSL -o $@ "$(TAILWIND_URL)" chmod +x $@ tailwind: $(TAILWIND_BIN) ## Build the CSS bundle from web/styles/input.css @mkdir -p $$(dirname $(TAILWIND_OUTPUT)) $(TAILWIND_BIN) -c tailwind.config.js -i $(TAILWIND_INPUT) -o $(TAILWIND_OUTPUT) --minify tailwind-watch: $(TAILWIND_BIN) ## Watch and rebuild on every save @mkdir -p $$(dirname $(TAILWIND_OUTPUT)) $(TAILWIND_BIN) -c tailwind.config.js -i $(TAILWIND_INPUT) -o $(TAILWIND_OUTPUT) --watch $(MDBOOK_BIN): @mkdir -p $(BIN_DIR) @echo "==> downloading mdbook $(MDBOOK_VERSION) ($(MDBOOK_TRIPLE))" curl -fsSL "$(MDBOOK_URL)" | tar -xz -C $(BIN_DIR) mdbook @chmod +x $@ docs: $(MDBOOK_BIN) ## Build the docs/book/ mdBook site into docs/book/book/ $(MDBOOK_BIN) build $(DOCS_BOOK_DIR) docs-watch: $(MDBOOK_BIN) ## Serve the docs site at http://127.0.0.1:3000 with live reload $(MDBOOK_BIN) serve $(DOCS_BOOK_DIR) -n 127.0.0.1 -p 3000 agent: ## Build the agent binary @mkdir -p $(BIN_DIR) CGO_ENABLED=0 go build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(AGENT_BIN) ./cmd/agent test: ## Run tests go test ./... test-race: ## Run tests with the race detector go test -race -coverprofile=coverage.out ./... lint: ## Run golangci-lint golangci-lint run ./... setup: hooks ## One-time per-clone setup (Go tools + git hooks) @command -v gofumpt >/dev/null 2>&1 || go install mvdan.cc/gofumpt@latest @command -v golangci-lint >/dev/null 2>&1 || go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.5.0 @echo "==> setup complete: gofumpt, golangci-lint, pre-commit hooks installed" hooks: ## Install the pre-commit hooks defined in .pre-commit-config.yaml @command -v pre-commit >/dev/null 2>&1 || { echo "pre-commit not found — install with 'pip install pre-commit' or 'brew install pre-commit'" >&2; exit 1; } pre-commit install fmt: ## Format with gofumpt + goimports gofumpt -w . goimports -local gitea.dcglab.co.uk/steve/restic-manager -w . tidy: ## go mod tidy go mod tidy clean: ## Remove build artifacts rm -rf $(BIN_DIR) coverage.out coverage.html $(TAILWIND_OUTPUT) $(DOCS_BOOK_OUT) run-server: server ## Build and run the server $(SERVER_BIN) run-agent: agent ## Build and run the agent $(AGENT_BIN) docker: ## Build the server Docker image docker build -f deploy/Dockerfile.server \ --build-arg VERSION=$(VERSION) \ --build-arg COMMIT=$(COMMIT) \ --build-arg DATE=$(DATE) \ -t $(DOCKER_IMAGE):$(DOCKER_TAG) . smoke-restart: server ## (Re)start the smoke server as a transient user-systemd unit @systemctl --user reset-failed $(SMOKE_UNIT) >/dev/null 2>&1 || true @systemctl --user stop $(SMOKE_UNIT) >/dev/null 2>&1 || true @echo "==> launching $(SMOKE_UNIT)" systemd-run --user --unit=$(SMOKE_UNIT) \ --setenv=RM_LISTEN=$(SMOKE_LISTEN) \ --setenv=RM_DATA_DIR=$(SMOKE_DATA_DIR) \ --setenv=RM_BASE_URL=$(SMOKE_BASE_URL) \ --setenv=RM_SECRET_KEY_FILE=$(SMOKE_DATA_DIR)/secret.key \ --setenv=RM_COOKIE_SECURE=false \ --property=StandardOutput=append:$(SMOKE_LOG_FILE) \ --property=StandardError=append:$(SMOKE_LOG_FILE) \ --property=Restart=on-failure \ $(PWD)/$(SERVER_BIN) @for i in 1 2 3 4 5; do \ curl -fsS -o /dev/null $(SMOKE_BASE_URL)/api/version 2>/dev/null && \ { echo "==> smoke server up: $$(curl -s $(SMOKE_BASE_URL)/api/version)"; exit 0; }; \ sleep 1; \ done; \ echo "!! smoke server did not respond on $(SMOKE_BASE_URL) — check $(SMOKE_LOG_FILE)" >&2; \ systemctl --user status --no-pager $(SMOKE_UNIT) || true; \ exit 1 smoke-stop: ## Stop the smoke server systemctl --user stop $(SMOKE_UNIT) || true @systemctl --user reset-failed $(SMOKE_UNIT) >/dev/null 2>&1 || true smoke-status: ## Show status of the smoke server @systemctl --user status --no-pager $(SMOKE_UNIT) 2>&1 | head -20 || true smoke-logs: ## Tail the smoke server log tail -50 $(SMOKE_LOG_FILE) smoke-deploy: build smoke-restart ## Rebuild + restage agent into smoke + restart server (full per-CLAUDE.md cycle) @echo "==> restaging agent + install assets into $(SMOKE_DATA_DIR)" cp $(AGENT_BIN) $(SMOKE_DATA_DIR)/agent-binaries/restic-manager-agent-linux-amd64 cp deploy/install/install.sh $(SMOKE_DATA_DIR)/install/install.sh cp deploy/install/install.ps1 $(SMOKE_DATA_DIR)/install/install.ps1 cp deploy/install/restic-manager-agent.service $(SMOKE_DATA_DIR)/install/restic-manager-agent.service @echo "==> NOTE: this dev box's installed agent at /usr/local/bin/restic-manager-agent is NOT updated by this target." @echo " Run the agent restage block in CLAUDE.md if your change touches agent code or the unit file." release: ## Cross-compile for all supported platforms @mkdir -p $(BIN_DIR) @for target in linux/amd64 linux/arm64 windows/amd64; do \ goos=$${target%/*}; goarch=$${target#*/}; \ ext=""; if [ "$$goos" = "windows" ]; then ext=".exe"; fi; \ echo "==> $$goos/$$goarch"; \ GOOS=$$goos GOARCH=$$goarch CGO_ENABLED=0 \ go build $(GOFLAGS) -ldflags "$(LDFLAGS)" \ -o $(BIN_DIR)/restic-manager-server-$$goos-$$goarch$$ext ./cmd/server; \ GOOS=$$goos GOARCH=$$goarch CGO_ENABLED=0 \ go build $(GOFLAGS) -ldflags "$(LDFLAGS)" \ -o $(BIN_DIR)/restic-manager-agent-$$goos-$$goarch$$ext ./cmd/agent; \ done