| 1 | # shithub Makefile |
| 2 | # Targets mirror what CI runs. The Makefile is the source of truth. |
| 3 | |
| 4 | .DEFAULT_GOAL := help |
| 5 | .PHONY: help dev build test test-race lint lint-policy lint-markdown lint-org-plan lint-secret-logs lint-spdx lint-unused lint-migrations verify-api-docs fmt tidy clean ci assets install-tools version deploy deploy-check restore-drill bench-staging docs docs-serve docs-verify gen-third-party-notices audit-actions-ga audit-a11y audit-a11y-pa11y audit-a11y-axe load-test |
| 6 | |
| 7 | # Build metadata embedded into the binary via -ldflags. |
| 8 | VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo dev) |
| 9 | COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown) |
| 10 | BUILT := $(shell date -u +%Y-%m-%dT%H:%M:%SZ) |
| 11 | LDFLAGS := -X github.com/tenseleyFlow/shithub/internal/version.Version=$(VERSION) \ |
| 12 | -X github.com/tenseleyFlow/shithub/internal/version.Commit=$(COMMIT) \ |
| 13 | -X github.com/tenseleyFlow/shithub/internal/version.BuiltAt=$(BUILT) |
| 14 | |
| 15 | GOFLAGS := -trimpath |
| 16 | BIN := bin/shithubd |
| 17 | RUNNER_BIN := bin/shithubd-runner |
| 18 | |
| 19 | # Tools installed via 'go install' live in GOBIN (or GOPATH/bin). Reference |
| 20 | # them by absolute path so make recipes don't depend on PATH ordering. |
| 21 | GOBIN := $(shell go env GOBIN) |
| 22 | ifeq ($(GOBIN),) |
| 23 | GOBIN := $(shell go env GOPATH)/bin |
| 24 | endif |
| 25 | GOFUMPT := $(GOBIN)/gofumpt |
| 26 | GOIMPORTS := $(GOBIN)/goimports |
| 27 | AIR := $(GOBIN)/air |
| 28 | |
| 29 | help: ## Show this help. |
| 30 | @awk 'BEGIN {FS = ":.*##"} /^[a-zA-Z_-]+:.*##/ {printf " %-20s %s\n", $$1, $$2}' $(MAKEFILE_LIST) |
| 31 | |
| 32 | dev: ## Run the web server with hot reload via air. Sources .env if present. |
| 33 | @if [ -f .env ]; then set -a; . ./.env; set +a; fi; $(AIR) |
| 34 | |
| 35 | dev-migrate: ## Apply DB migrations against $$SHITHUB_DATABASE_URL (sources .env). |
| 36 | @if [ -f .env ]; then set -a; . ./.env; set +a; fi; \ |
| 37 | go run ./cmd/shithubd migrate up |
| 38 | |
| 39 | dev-run: ## Run the binary directly (no air); sources .env. |
| 40 | @if [ -f .env ]; then set -a; . ./.env; set +a; fi; \ |
| 41 | go run ./cmd/shithubd web |
| 42 | |
| 43 | build: ## Build shithubd and shithubd-runner into bin/. |
| 44 | @mkdir -p $(dir $(BIN)) $(dir $(RUNNER_BIN)) |
| 45 | go build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(BIN) ./cmd/shithubd |
| 46 | go build $(GOFLAGS) -ldflags "$(LDFLAGS)" -o $(RUNNER_BIN) ./cmd/shithubd-runner |
| 47 | |
| 48 | test: ## Run unit tests. |
| 49 | go test $(GOFLAGS) ./... |
| 50 | |
| 51 | test-race: ## Run unit tests with the race detector. |
| 52 | go test $(GOFLAGS) -race ./... |
| 53 | |
| 54 | lint: ## Run golangci-lint. |
| 55 | golangci-lint run |
| 56 | |
| 57 | fmt: ## Format the codebase with gofumpt and goimports. |
| 58 | $(GOFUMPT) -l -w cmd internal pkg |
| 59 | $(GOIMPORTS) -local github.com/tenseleyFlow/shithub -w cmd internal pkg |
| 60 | |
| 61 | tidy: ## Tidy go.mod / go.sum. |
| 62 | go mod tidy |
| 63 | |
| 64 | clean: ## Remove build artifacts. |
| 65 | rm -rf bin tmp coverage.out |
| 66 | |
| 67 | assets: ## Copy Primer CSS into internal/web/static/ for embedding. |
| 68 | @mkdir -p internal/web/static/primer |
| 69 | @if [ -d .refs/primer-css/dist ]; then \ |
| 70 | cp -R .refs/primer-css/dist/* internal/web/static/primer/; \ |
| 71 | else \ |
| 72 | echo "warn: .refs/primer-css/dist not found; run 'git clone https://github.com/primer/css .refs/primer-css' first"; \ |
| 73 | fi |
| 74 | |
| 75 | ci: lint lint-policy lint-markdown lint-org-plan lint-secret-logs lint-spdx lint-unused lint-migrations verify-api-docs test build ## Full CI pipeline (matches .github/workflows/ci.yml). |
| 76 | @echo "ci: ok" |
| 77 | |
| 78 | lint-policy: ## Enforce policy-package boundary (no inline auth checks in handlers/git/cmd). |
| 79 | @scripts/lint-policy-boundary.sh |
| 80 | |
| 81 | lint-markdown: ## Enforce markdown-package boundary (no goldmark/bluemonday outside internal/markdown). |
| 82 | @scripts/lint-markdown-boundary.sh |
| 83 | |
| 84 | lint-org-plan: ## Enforce paid org entitlement boundary (no direct orgs.plan feature gates). |
| 85 | @scripts/lint-org-plan-boundary.sh |
| 86 | |
| 87 | lint-secret-logs: ## Fail when source emits log lines containing token-prefix patterns. |
| 88 | @scripts/lint-secret-logs.sh |
| 89 | |
| 90 | lint-spdx: ## Verify every Go + shell source carries the SPDX license header. |
| 91 | @scripts/verify-spdx-headers.sh |
| 92 | |
| 93 | lint-unused: ## Fail when source carries dead-code 'silence unused import' shims (var _ = symbol). |
| 94 | @scripts/lint-unused.sh |
| 95 | |
| 96 | lint-migrations: ## Fail when goose migration numeric versions collide. |
| 97 | @scripts/lint-migration-versions.sh |
| 98 | |
| 99 | verify-api-docs: ## Fail when an /api/v1 route in code is missing from docs/public/api/. |
| 100 | @scripts/verify-api-docs.sh |
| 101 | |
| 102 | bench-small: ## Run the bench harness against $$BENCH_TARGET (default localhost:8080). |
| 103 | @go run ./bench -target=$${BENCH_TARGET:-http://localhost:8080} -iters=$${BENCH_ITERS:-20} |
| 104 | |
| 105 | bench-full: ## Placeholder — runs nightly off-CI against big fixtures (see bench/fixtures/README.md). |
| 106 | @echo "bench-full: big-fixture generators land in a follow-up — see bench/fixtures/README.md" |
| 107 | @exit 0 |
| 108 | |
| 109 | install-tools: ## Install development tools via 'go install'. |
| 110 | go install mvdan.cc/gofumpt@latest |
| 111 | go install golang.org/x/tools/cmd/goimports@latest |
| 112 | go install github.com/air-verse/air@latest |
| 113 | go install github.com/sqlc-dev/sqlc/cmd/sqlc@latest |
| 114 | |
| 115 | dev-db: ## Bring up Postgres in docker-compose. |
| 116 | docker compose up -d postgres |
| 117 | @echo "Waiting for postgres to become healthy..." |
| 118 | @until docker compose exec -T postgres pg_isready -U shithub -d shithub >/dev/null 2>&1; do sleep 1; done |
| 119 | @echo "Postgres ready at 127.0.0.1:5432" |
| 120 | |
| 121 | dev-db-down: ## Stop the dev Postgres container. |
| 122 | docker compose down |
| 123 | |
| 124 | dev-db-reset: ## Drop the dev Postgres volume and re-create. |
| 125 | docker compose down -v |
| 126 | $(MAKE) dev-db |
| 127 | |
| 128 | dev-storage: ## Bring up MinIO + run minio-init to seed the bucket. |
| 129 | docker compose up -d minio |
| 130 | docker compose run --rm minio-init |
| 131 | @echo "MinIO S3 API: http://127.0.0.1:9000 console: http://127.0.0.1:9001" |
| 132 | @echo "Credentials: shithub-dev / shithub-dev-secret-please-change" |
| 133 | |
| 134 | dev-storage-down: ## Stop the MinIO container (volume persists). |
| 135 | docker compose stop minio |
| 136 | |
| 137 | dev-storage-reset: ## Drop the MinIO volume and re-seed. |
| 138 | docker compose down minio |
| 139 | docker volume rm -f shithub-miniodata |
| 140 | $(MAKE) dev-storage |
| 141 | |
| 142 | storage-check: build ## Run shithubd storage check against the configured backend. |
| 143 | ./bin/shithubd storage check |
| 144 | |
| 145 | dev-email: ## Bring up MailHog for local email capture (S05). |
| 146 | docker compose up -d mailhog |
| 147 | @echo "MailHog SMTP: 127.0.0.1:1025 web UI: http://127.0.0.1:8025" |
| 148 | |
| 149 | dev-email-down: ## Stop MailHog. |
| 150 | docker compose stop mailhog |
| 151 | |
| 152 | migrate-up: ## Apply all pending migrations. |
| 153 | ./bin/shithubd migrate up |
| 154 | |
| 155 | migrate-down: ## Roll back the most recent migration. |
| 156 | ./bin/shithubd migrate down |
| 157 | |
| 158 | migrate-status: ## Show migration status. |
| 159 | ./bin/shithubd migrate status |
| 160 | |
| 161 | sqlc-generate: ## Regenerate sqlc Go code from queries. |
| 162 | $(GOBIN)/sqlc generate |
| 163 | |
| 164 | test-integration: ## Run tests with SHITHUB_TEST_DATABASE_URL set against the dev Postgres. |
| 165 | SHITHUB_TEST_DATABASE_URL=$${SHITHUB_TEST_DATABASE_URL:-postgres://shithub:shithub_dev@127.0.0.1:5432/postgres?sslmode=disable} \ |
| 166 | go test -trimpath ./... |
| 167 | |
| 168 | version: ## Print version info that would be embedded into the binary. |
| 169 | @echo "Version: $(VERSION)" |
| 170 | @echo "Commit: $(COMMIT)" |
| 171 | @echo "Built: $(BUILT)" |
| 172 | |
| 173 | # --- deploy --- |
| 174 | # Inventory selection: ANSIBLE_INVENTORY=production make deploy (default: staging). |
| 175 | ANSIBLE_INVENTORY ?= staging |
| 176 | ANSIBLE_TAGS ?= |
| 177 | ANSIBLE_LIMIT ?= |
| 178 | |
| 179 | deploy-check: ## Dry-run the Ansible playbook (--check) against $$ANSIBLE_INVENTORY. |
| 180 | cd deploy/ansible && ansible-playbook -i inventory/$(ANSIBLE_INVENTORY) site.yml --check --diff \ |
| 181 | $(if $(ANSIBLE_TAGS),--tags $(ANSIBLE_TAGS)) \ |
| 182 | $(if $(ANSIBLE_LIMIT),--limit $(ANSIBLE_LIMIT)) |
| 183 | |
| 184 | deploy: ## Apply the Ansible playbook against $$ANSIBLE_INVENTORY (set to production for prod). |
| 185 | cd deploy/ansible && ansible-playbook -i inventory/$(ANSIBLE_INVENTORY) site.yml \ |
| 186 | $(if $(ANSIBLE_TAGS),--tags $(ANSIBLE_TAGS)) \ |
| 187 | $(if $(ANSIBLE_LIMIT),--limit $(ANSIBLE_LIMIT)) |
| 188 | |
| 189 | restore-drill: ## Run the restore drill on the backup host (must be run via ssh on that host). |
| 190 | deploy/restore-drill/run.sh |
| 191 | |
| 192 | bench-staging: ## Run the bench harness against staging (BENCH_TARGET must be set to the staging URL). |
| 193 | @if [ -z "$$BENCH_TARGET" ]; then echo "set BENCH_TARGET=https://staging.shithub.example"; exit 2; fi |
| 194 | go run ./bench -target=$$BENCH_TARGET -iters=$${BENCH_ITERS:-50} |
| 195 | |
| 196 | # --- docs --- |
| 197 | docs: ## Build the public docs site to build/docs/ via mdBook. |
| 198 | cd docs/public && mdbook build |
| 199 | |
| 200 | docs-serve: ## Serve the public docs site locally on http://127.0.0.1:3000. |
| 201 | cd docs/public && mdbook serve --port 3000 |
| 202 | |
| 203 | docs-verify: verify-api-docs ## Verify docs are in sync (API routes documented + SPDX headers). |
| 204 | @$(MAKE) lint-spdx |
| 205 | @if command -v mdbook >/dev/null 2>&1; then \ |
| 206 | cd docs/public && mdbook build >/dev/null && echo "mdbook build: ok"; \ |
| 207 | else \ |
| 208 | echo "mdbook not installed; skipping site build"; \ |
| 209 | fi |
| 210 | |
| 211 | gen-third-party-notices: ## Regenerate THIRD_PARTY_NOTICES.md from the active go.mod. |
| 212 | @scripts/gen-third-party-notices.sh > THIRD_PARTY_NOTICES.md |
| 213 | @echo "gen-third-party-notices: wrote THIRD_PARTY_NOTICES.md" |
| 214 | |
| 215 | audit-actions-ga: ## Run the read-only S41h Actions pre-GA static audit packet. |
| 216 | @scripts/audit-actions-ga.sh |
| 217 | |
| 218 | # --- S39 hardening --- |
| 219 | audit-a11y-pa11y: ## pa11y-ci scan of anonymous routes (needs running shithub on 127.0.0.1:8080). |
| 220 | @command -v pa11y-ci >/dev/null 2>&1 || { echo "pa11y-ci not installed; npm i -g pa11y-ci"; exit 2; } |
| 221 | pa11y-ci --config tests/a11y/pa11y-config.json |
| 222 | |
| 223 | audit-a11y-axe: ## axe-core scan of authenticated routes (needs SHITHUB_USER + SHITHUB_PASS). |
| 224 | @command -v node >/dev/null 2>&1 || { echo "node not installed"; exit 2; } |
| 225 | node tests/a11y/axe-runner.js |
| 226 | |
| 227 | audit-a11y: audit-a11y-pa11y audit-a11y-axe ## Run both accessibility scans. |
| 228 | |
| 229 | load-test: ## Run a k6 scenario (set K6_SCENARIO=mixed-read|auth-mix|issue-comment-storm|search-load; default mixed-read). |
| 230 | @command -v k6 >/dev/null 2>&1 || { echo "k6 not installed; see https://k6.io/docs/getting-started/installation/"; exit 2; } |
| 231 | @if [ -z "$$BASE" ] && [ -z "$$BENCH_TARGET" ]; then echo "set BASE or BENCH_TARGET (e.g. https://staging.shithub.example)"; exit 2; fi |
| 232 | BASE="$${BASE:-$$BENCH_TARGET}" k6 run tests/load/k6/scenarios/$${K6_SCENARIO:-mixed-read}.js |