Makefile
Rama main · réplica de lectura del repositorio original.
.PHONY: test-store test-routes test-docs test-doc-links test-doc-versions test-doc-checks test-comparison-sources test-docs-live test-evaluation test-recovery compatibility agent-lifecycle agent-compatibility live-notifications live-oidc live-ct live-kubernetes live-eab live-acme live-winacme all build build-core test test-frontend test-coverage lint routes \
dev dev-certs generate-kek run-core run-gateway-selfsigned run-gateway-acme run-gateway-vault run-frontend \
clean help
# ── Variables ────────────────────────────────────────────
GO := go
BIN := bin
PKI := .certpilot/pki
STATE := .certpilot/state
# Each component is its own Go module, so tooling has to iterate rather than
# rely on a single ./... from the repository root.
MODULES := pkg core
CORE_BIN := $(BIN)/certpilot-core
# The gateways live in their own repositories now and are run from a release
# rather than built from this tree. Pinned, so `make dev` is reproducible and
# does not silently follow whatever is on somebody's main branch.
GW_VERSION := v0.4.2
GW_SELFSIGNED := github.com/certpilot/certpilot-gateway-selfsigned/cmd@$(GW_VERSION)
GW_ACME := github.com/certpilot/certpilot-gateway-acme/cmd@$(GW_VERSION)
GW_VAULT := github.com/certpilot/certpilot-gateway-vault/cmd@$(GW_VERSION)
# ── Build ────────────────────────────────────────────────
all: build
build: build-core
build-core:
$(GO) build -o $(CORE_BIN) ./core/cmd/
# ── Setup ────────────────────────────────────────────────
## Generate a key encryption key. Certificate private keys and CA credentials
## are sealed with it before they reach the database.
generate-kek: build-core
@$(CORE_BIN) --generate-kek
## Generate development mTLS material for the core-to-gateway channel.
## That channel carries CSRs, private keys, and CA credentials, so it is
## mutually authenticated by default; this makes turning it on a single command.
dev-certs: build-core
@$(CORE_BIN) --generate-dev-certs=$(PKI)
## Apply outstanding database migrations. Reads CERTPILOT_DB_URL (or DATABASE_URL);
## override with `make migrate DB=postgres://...`.
##
## Deliberately not run by the server on startup: a schema change should be
## something an operator decides to do, not a side effect of a replica restarting
## mid-deploy while older replicas are still reading the old shape.
migrate: build-core
@$(CORE_BIN) --migrate $(if $(DB),--db=$(DB),)
# ── Run (Development) ───────────────────────────────────
#
# Each of these runs in its own terminal. Run `make dev-certs` first.
## Start PostgreSQL, the local gateway, the API, and the frontend in one
## terminal, migrating the database first. Export CERTPILOT_DB_URL to use a
## database of your own instead of the local certpilot_dev one.
dev:
./scripts/dev.sh
## Fill a running CertPilot with a realistic estate: a CA hierarchy, certificates
## across the whole expiry range, metadata fields of every type, and values on
## every certificate. Idempotent, and deletes nothing.
seed:
./scripts/seed-demo.sh
## Regenerate docs/routes.json from the router and the handlers. The
## documentation site is built from it, so it is checked in CI — a new route
## with a stale inventory fails.
##
## Two passes, because they read different things. extract-routes reads the
## router: what exists, and who may call it. schemagen reads the handlers:
## what to send, what comes back, and which refusals are possible. Neither
## question is answerable from the other file.
##
## One pipeline and one writer, deliberately. These used to be two commands
## that both wrote docs/routes.json, so a schemagen failure left the first
## pass's output on disk: valid JSON, every route listed, and none of the
## request or response shapes the API reference is generated from (#50). Now
## nothing touches the checked-in file until the second pass has succeeded.
##
## bash -o pipefail explicitly, not $(SHELL) and not .SHELLFLAGS. A pipeline
## reports only its last command's status, so extract-routes.py can emit a
## whole document, fail, and have make call the run a success. .SHELLFLAGS is
## the usual way to fix that and it is silently ignored by GNU Make 3.81 —
## which is what macOS ships, so the protection would have existed only on CI.
routes:
bash -o pipefail -c 'python3 scripts/extract-routes.py | $(GO) run scripts/schemagen/main.go -o docs/routes.json'
## Prove a failed `make routes` leaves docs/routes.json byte-identical. Runs
## the real recipe with the generator rigged to fail, five different ways.
test-routes:
./scripts/test-routes-atomicity.sh
## Check every curl example in docs/ against docs/routes.json: that the route
## exists, and that an example carries a credential when its route needs one.
## Written after twenty-eight examples were found to be 401s, left behind when
## anonymous access was removed and only the prose around them was updated.
test-docs:
python3 scripts/check-doc-examples.py
## Check every link in docs/ resolves — including the anchor, which the
## documentation site's build does not. VitePress fails on a dead page and is
## silent on a dead heading, so three links that had already shipped were
## landing readers at the top of the right page with no way to tell.
test-doc-links:
python3 scripts/check-doc-links.py
## Check every version the documentation pins — image tags, module versions,
## the quickstart's two variables — is published, that each page agrees with
## itself, and that a pinned gateway or agent is one compatibility.md measured.
## Asks the registries, so it needs the network; --offline skips that and says so.
test-doc-versions:
python3 scripts/check-doc-versions.py
## Break each documentation check's input on purpose and require it to fail,
## then correct it and require it to pass. A check never seen to fail has not
## been shown to work, and three here once passed while checking less than
## they claimed.
test-doc-checks:
./scripts/test-doc-checks.sh
## Fetch every vendor page docs/comparison.md quotes and check the quoted text
## is still there, and that every claim cites a source the page lists. The
## comparison speaks about other companies' products; a vendor rewriting a page
## is how it would go stale without anybody noticing. Needs the network;
## --offline checks the table and the citations only, and says so.
test-comparison-sources:
python3 scripts/check-comparison-sources.py
## Send every documented GET that is safe to send to a core built from this
## tree, signed in, and require each to succeed. Reading an example against the
## route table proves it could run; this proves it does.
test-docs-live:
./scripts/doc-examples-live.sh
## Run docs/evaluation.md as written, block by block, against the release it
## pins, and check every claim it makes. Needs Docker and ports 3000 and 8080.
## Under colima, pass a directory it shares: make test-evaluation EVAL_DIR=...
test-evaluation:
python3 scripts/run-evaluation-doc.py $(if $(EVAL_DIR),--workdir $(EVAL_DIR),)
## Upgrade a released quickstart to the core built from this tree, then back
## up, restore, start with the wrong key and give a lost one up, checking each
## against the sentence in docs/operations.md that says what should happen.
## Needs Docker and port 8080; RECOVERY_DIR must be somewhere Docker can mount.
test-recovery:
python3 scripts/run-recovery-drill.py $(if $(RECOVERY_DIR),--workdir $(RECOVERY_DIR),)
## Measure which released gateways this core still works with, and write
## docs/compatibility.md from what happened rather than from memory.
compatibility:
./scripts/gateway-compatibility.sh $(GATEWAY)
## Does this core still work with the agent? Enrol, grant, request, install and
## report, against a real core, a real gateway and a real released agent.
##
## Nothing else asks this. The core tests its handlers, the agent tests its
## logic, and the contract between them is verified by neither.
##
## make agent-lifecycle
## make agent-lifecycle AGENT_VERSION=v0.1.0
## make agent-lifecycle AGENT_BIN=/path/to/certpilot-agent
agent-lifecycle:
./scripts/agent-lifecycle.sh
## The same question asked of every released agent, written into the agent
## section of docs/compatibility.md beside the gateway rows.
##
## Run scripts/gateway-compatibility.sh first: it owns the page and this fills
## in one section of it. `make compatibility agent-compatibility` does both in
## the right order, which is what the weekly workflow runs.
##
## A list rather than one version, because the useful question is not "does the
## newest work" but "which of the ones people are running still do".
AGENT_VERSIONS ?= latest
agent-compatibility:
AGENT_VERSIONS="$(AGENT_VERSIONS)" ./scripts/agent-lifecycle.sh
## Do alerts reach a real mail server and a real webhook receiver? Email to
## Mailpit over STARTTLS, implicit TLS and a plain relay; webhooks to a receiver
## that checks the signature with the snippet from the API reference; and a CA
## crossing its expiry threshold, alerting with nobody prompting it. Needs Docker.
live-notifications:
./scripts/live-notifications.sh
## Does single sign-on work against a real identity provider? Keycloak, signed
## into the way the console does it, its tokens used as bearer credentials, the
## refusals that keep both honest, and a rotation of its signing key.
live-oidc:
./scripts/live-oidc.sh
## Does CT monitoring work against the real index? A public site's served
## certificate, imported through discovery, must be found in crt.sh and matched
## to the inventory. Needs the internet, so CI runs it daily, not per pull request.
##
## make live-ct
## make live-ct CT_DOMAIN=example.org
CT_DOMAIN ?= badssl.com
live-ct:
CT_DOMAIN="$(CT_DOMAIN)" ./scripts/live-ct.sh
## Does the Kubernetes inventory work against a real cluster? A kind cluster
## with cert-manager, a token scoped as documented, an Ingress, and more TLS
## secrets than one page holds. Needs Docker; fetches kind if it is missing.
live-kubernetes:
./scripts/live-kubernetes.sh
## Does External Account Binding work against an ACME server that requires it?
## The released ACME gateway against Pebble with a binding required: a correct
## one issues and renews; none, a wrong HMAC key, and an unknown key id are
## refused. Needs Docker.
live-eab:
./scripts/live-eab.sh
## live-acme runs certbot, lego and acme.sh against the core's ACME server.
live-acme:
./scripts/live-acme.sh
## live-winacme runs win-acme against the core's ACME server. Windows only.
live-winacme:
pwsh ./scripts/live-winacme.ps1
run-core:
$(GO) run ./core/cmd/ --config=config.dev.yaml
## The gateways are fetched from their own repositories at $(GW_VERSION).
## Nothing here builds them, and nothing here can: the contract between this
## core and any gateway is provider.v1 over the network, which is what makes a
## gateway somebody else wrote as usable as these three.
run-gateway-selfsigned:
$(GO) run $(GW_SELFSIGNED) \
--port=9091 \
--tls-cert=$(PKI)/gateway.pem \
--tls-key=$(PKI)/gateway-key.pem \
--tls-ca=$(PKI)/ca.pem
run-gateway-acme:
$(GO) run $(GW_ACME) \
--port=9092 \
--directory=letsencrypt-staging \
--state-dir=$(STATE)/acme \
--tls-cert=$(PKI)/gateway.pem \
--tls-key=$(PKI)/gateway-key.pem \
--tls-ca=$(PKI)/ca.pem
## The Vault gateway takes no credential of its own. Each CA account carries
## the AppRole or Kubernetes identity it issues under, so nothing here is
## authorised to sign anything.
run-gateway-vault:
$(GO) run $(GW_VAULT) \
--port=9093 \
--address=$(VAULT_ADDR) \
--tls-cert=$(PKI)/gateway.pem \
--tls-key=$(PKI)/gateway-key.pem \
--tls-ca=$(PKI)/ca.pem
run-frontend:
cd frontend && npm run dev
# ── Test ─────────────────────────────────────────────────
test:
@for m in $(MODULES); do \
echo "==> $$m"; \
(cd $$m && $(GO) test ./... -cover) || exit 1; \
done
## test-store runs the store conformance suite against a real PostgreSQL.
##
## The same assertions run against the in-memory store on every `make test`.
## This adds the implementation that has produced every defect the unit tests
## could not express: constraints, column lists, NULL, and type inference.
##
## Either of these gives it a server:
##
## docker compose -f deploy/plain-postgres/docker-compose.yml up -d
## make test-store
##
## brew services start postgresql@17
## make test-store DB="postgres://$$(whoami)@127.0.0.1:5432/postgres?sslmode=disable"
##
## It creates and drops a database per test, so point it at something
## throwaway. The harness refuses the obvious production hostnames, which is a
## guard rather than a control.
TEST_DB ?= postgres://postgres:conformance@127.0.0.1:55432/postgres?sslmode=disable
test-store:
@CERTPILOT_TEST_DB_URL="$(if $(DB),$(DB),$(TEST_DB))" \
$(GO) test ./core/store/ -count=1 -v -run 'Conformance|TestEveryValue|TestAnUnset|TestAnUpdateKeeps|TestABindingKeeps|TestAnEmptyResult|TestTheDeploymentQueue|TestAnAgentTarget'
test-race:
@for m in $(MODULES); do \
echo "==> $$m"; \
(cd $$m && $(GO) test ./... -race) || exit 1; \
done
## Typecheck the frontend and run its checks.
## Needs Node 22.18+ — the checks import TypeScript directly, using Node's own
## type stripping rather than adding a test runner to the dependency tree.
test-frontend:
cd frontend && npx vue-tsc --noEmit \
&& node scripts/check-sse.mjs \
&& node scripts/check-chain.mjs \
&& node scripts/check-display-token.mjs \
&& node scripts/check-signin.mjs \
&& node scripts/check-renewal.mjs
test-coverage:
@for m in $(MODULES); do \
(cd $$m && $(GO) test ./... -coverprofile=coverage.out && $(GO) tool cover -func=coverage.out | tail -1); \
done
lint:
@for m in $(MODULES); do \
echo "==> $$m"; \
(cd $$m && $(GO) vet ./...) || exit 1; \
done
@## The build tools are outside every module, so the loop above never sees
@## them. schemagen decides what the published API reference says about
@## request bodies; it rotting silently is the same failure as the docs
@## rotting silently, which is what it exists to prevent.
@echo "==> scripts"
@$(GO) vet scripts/schemagen/main.go
@gofmt -l $(MODULES) scripts | grep . && echo "gofmt needed on the files above" && exit 1 || true
@## staticcheck when it is installed, because it catches a class go vet does
@## not: dead assignments, impossible conditions, and code nothing reaches.
@## Not a hard requirement, so a clone can be linted without installing it.
@## go install honnef.co/go/tools/cmd/staticcheck@latest
@if command -v staticcheck >/dev/null 2>&1; then \
for m in $(MODULES); do \
echo "==> staticcheck $$m"; \
(cd $$m && staticcheck ./...) || exit 1; \
done; \
else \
echo "staticcheck not installed; skipping (go install honnef.co/go/tools/cmd/staticcheck@latest)"; \
fi
tidy:
@for m in $(MODULES); do (cd $$m && $(GO) mod tidy); done
# ── Clean ────────────────────────────────────────────────
clean:
rm -rf $(BIN)/
rm -f coverage.out coverage.html
@for m in $(MODULES); do rm -f $$m/coverage.out; done
## Also removes development keys and ACME account state.
clean-all: clean
rm -rf .certpilot/
# ── Help ─────────────────────────────────────────────────
help:
@echo "CertPilot"
@echo ""
@echo "Setup"
@echo " make dev-certs Generate development mTLS material"
@echo " make generate-kek Print a new CERTPILOT_KEK"
@echo ""
@echo "Build"
@echo " make build Build the core"
@echo ""
@echo "Run (one per terminal)"
@echo " make dev Start the complete local development stack"
@echo " make run-core CertPilot Core :8080"
@echo " make run-gateway-selfsigned Self-signed gateway :9091 (fetched, $(GW_VERSION))"
@echo " make run-gateway-acme ACME gateway :9092 (fetched, $(GW_VERSION))"
@echo " make run-gateway-vault Vault PKI gateway :9093 (fetched, $(GW_VERSION))"
@echo " make run-frontend Vue frontend :3000"
@echo ""
@echo "Docs"
@echo " make routes Regenerate docs/routes.json from the router"
@echo " make test-routes Prove a failed regeneration damages nothing"
@echo " make test-docs Check every documented curl still runs"
@echo " make agent-lifecycle Enrol, request, install and report, end to end"
@echo " make agent-compatibility The same, for every released agent, into docs/"
@echo " make live-notifications Alerts to a real SMTP server and webhook receiver"
@echo " make live-oidc Sign-in and bearer tokens against Keycloak"
@echo " make live-ct CT monitoring against crt.sh (needs the internet)"
@echo " make live-kubernetes Kubernetes inventory against a kind cluster"
@echo " make live-eab ACME External Account Binding against Pebble"
@echo ""
@echo "Check"
@echo " make test Run all tests"
@echo " make test-race Run all tests under the race detector"
@echo " make test-frontend Typecheck the UI and run its checks"
@echo " make lint go vet and gofmt"
@echo ""
@echo "Clean"
@echo " make clean Remove build artifacts"
@echo " make clean-all Also remove development keys and ACME state"