.PHONY: build dev dev-conductor dev-cashier dev-dashboard dev-docs dev-logs dev-stop cockpit cockpit-dev generate-graphql generate-cli-docs lint minikube dns infra infra-down infra-forward infra-forward-stop db-forward deploy-prod deploy-prod-infra # Build all Go services build: go build ./services/conductor/cmd/conductor/... go build ./services/builder/cmd/builder/... go build ./services/cashier/cmd/cashier/... go build ./services/exporter/cmd/exporter/... go build ./services/stagehand/cmd/stagehand/... # Generate GraphQL resolvers (requires gqlgen) generate-graphql: cd services/conductor && go generate ./internal/api/graphql/resolver.go # Regenerate the CLI reference in the docs from the CLI's help texts generate-cli-docs: cd cli && go run ./cmd/docgen ./cmd/lucity ../docs/content/17.cli.md # Start all services with hot reload (air + vite) dev: @bash scripts/dev.sh # View all service logs dev-logs: @tail -f tmp/logs/*.log # View specific service logs (e.g., make dev-logs-conductor) dev-logs-%: @tail -f tmp/logs/$*.log # Stop all dev services dev-stop: @bash scripts/dev-stop.sh # Run individual services (without air) dev-conductor: cd services/conductor && set -a && . .env 2>/dev/null && set +a && go run ./cmd/conductor/... dev-cashier: cd services/cashier && set -a && . .env 2>/dev/null && set +a && go run ./cmd/cashier/... dev-dashboard: cd services/dashboard && npm run dev dev-docs: cd docs && npm run dev # Ops cockpit desktop window, opens on the environment used last (or ENV=dev|prod) and switches in the window cockpit: cd services/cockpit/web && npm run build cd services/cockpit && go run ./cmd/cockpit $(if $(ENV),-env $(ENV)) -deployments $(CURDIR)/deployments # Same window with the UI served by Vite: web/ edits hot reload, Go edits need a restart cockpit-dev: cd services/cockpit/web && (test -d dist || npm run build) (cd services/cockpit/web && exec node_modules/.bin/vite) & pid=$$!; trap "kill $$pid 2>/dev/null" EXIT; \ cd services/cockpit && FRONTEND_DEVSERVER_URL=http://127.0.0.1:5175 go run ./cmd/cockpit $(if $(ENV),-env $(ENV)) -deployments $(CURDIR)/deployments # Lint lint: cd services/dashboard && npm run lint # Create minikube cluster for local development # --insecure-registry covers the entire service CIDR so Docker trusts Zot over HTTP. # See: https://minikube.sigs.k8s.io/docs/handbook/registry/#enabling-insecure-registries minikube: minikube start --insecure-registry="10.96.0.0/12" # Set up local DNS so *.lucity.local resolves to 127.0.0.1 (requires Homebrew) # Run once — survives reboots. Uses dnsmasq on port 5380 (unprivileged) with # macOS /etc/resolver pointing to it. No root needed for dnsmasq itself. dns: @if ! command -v dnsmasq >/dev/null 2>&1; then \ echo "Installing dnsmasq..."; \ brew install dnsmasq; \ fi @if ! grep -q 'port=5380' /opt/homebrew/etc/dnsmasq.conf 2>/dev/null; then \ echo "port=5380" >> /opt/homebrew/etc/dnsmasq.conf; \ echo "Set dnsmasq to listen on port 5380."; \ fi @if ! grep -q 'address=/lucity.local/' /opt/homebrew/etc/dnsmasq.conf 2>/dev/null; then \ echo "address=/lucity.local/127.0.0.1" >> /opt/homebrew/etc/dnsmasq.conf; \ echo "Added *.lucity.local → 127.0.0.1 to dnsmasq config."; \ else \ echo "dnsmasq already configured for *.lucity.local."; \ fi @sudo mkdir -p /etc/resolver @printf "nameserver 127.0.0.1\nport 5380\n" | sudo tee /etc/resolver/lucity.local > /dev/null @brew services restart dnsmasq @echo "Done. All *.lucity.local domains now resolve to 127.0.0.1." # Deploy infrastructure (Zot + Envoy Gateway + CNPG) to a cluster # Envoy Gateway is installed separately — it needs its own namespace for cert management. # Usage: make infra CLUSTER=flxp CLUSTER ?= minikube infra: kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.1/standard-install.yaml helm upgrade --install envoy-gateway oci://docker.io/envoyproxy/gateway-helm \ --version v1.2.6 -n envoy-gateway-system --create-namespace --skip-crds @kubectl apply -f - <<< '{"apiVersion":"gateway.networking.k8s.io/v1","kind":"GatewayClass","metadata":{"name":"eg"},"spec":{"controllerName":"gateway.envoyproxy.io/gatewayclass-controller"}}' helm dependency update charts/lucity-infra helm upgrade --install lucity-infra charts/lucity-infra \ -n lucity-system --create-namespace \ -f deployments/$(CLUSTER)/values.yaml infra-down: helm uninstall lucity-infra -n lucity-system # Port-forward infrastructure services for local development infra-forward: infra-forward-stop @echo "Port-forwarding Zot (5000), Gateway (8880), VictoriaMetrics (8428), VictoriaLogs (9428), Logto Admin (3002), Grafana (3000)..." @kubectl port-forward svc/lucity-infra-zot 5000:5000 -n lucity-system & @(GATEWAY_SVC=$$(kubectl get svc -n envoy-gateway-system -l gateway.envoyproxy.io/owning-gateway-name=lucity-gateway -o name 2>/dev/null) && \ [ -n "$$GATEWAY_SVC" ] && kubectl port-forward $$GATEWAY_SVC 8880:80 -n envoy-gateway-system &) 2>/dev/null || true @kubectl port-forward svc/lucity-infra-logto 3002:3002 -n lucity-system & @kubectl port-forward svc/lucity-infra-victoria-metrics-single-server 8428:8428 -n lucity-system & @kubectl port-forward svc/lucity-infra-victoria-logs-single-server 9428:9428 -n lucity-system & @kubectl port-forward svc/lucity-infra-grafana 3000:80 -n lucity-system & @echo "Ready. Use 'make infra-forward-stop' to stop." infra-forward-stop: @for port in 5000 8880 3002 8428 9428 3000; do \ lsof -ti :$$port | xargs kill 2>/dev/null || true; \ done # Port-forward a project's database for local development (interactive picker) db-forward: @bash scripts/db-forward.sh # Production deployment from OCI registry charts # Usage: make deploy-prod VERSION=0.0.0-85-gabcdef0 HELM_ARGS="..." # Secrets are split into infra-secrets.yaml and secrets.yaml (both gitignored). # First deploy: copy from *.example files and fill in values. PROD_CONTEXT ?= lucity-prod VERSION ?= deploy-prod-infra: @test -f deployments/lucity-prod/infra-secrets.yaml || { echo "Error: deployments/lucity-prod/infra-secrets.yaml not found. Copy deployments/infra-secrets.yaml.example and fill in values."; exit 1; } helm upgrade --install lucity-infra \ oci://ghcr.io/zeitlos/lucity/charts/lucity-infra \ $(if $(VERSION),--version $(VERSION)) \ --kube-context $(PROD_CONTEXT) \ -n lucity-system --create-namespace \ -f deployments/lucity-prod/infra-values.yaml \ -f deployments/lucity-prod/infra-secrets.yaml \ $(HELM_ARGS) deploy-prod: @test -f deployments/lucity-prod/secrets.yaml || { echo "Error: deployments/lucity-prod/secrets.yaml not found. Copy deployments/secrets.yaml.example and fill in values."; exit 1; } helm upgrade --install lucity \ oci://ghcr.io/zeitlos/lucity/charts/lucity \ $(if $(VERSION),--version $(VERSION)) \ --kube-context $(PROD_CONTEXT) \ -n lucity-system --create-namespace \ -f deployments/lucity-prod/values.yaml \ -f deployments/lucity-prod/secrets.yaml \ $(HELM_ARGS) # Development cluster deployment from OCI registry charts # Usage: make deploy-dev VERSION=26.9.1 # Always pass VERSION= explicitly: "latest" resolves to the highest stable tag, # which ranks below every prerelease. DEV_CONTEXT ?= lucity-dev deploy-dev-infra: @test -f deployments/lucity-dev/infra-secrets.yaml || { echo "Error: deployments/lucity-dev/infra-secrets.yaml not found. Copy deployments/infra-secrets.yaml.example and fill in values."; exit 1; } helm upgrade --install lucity-infra \ oci://ghcr.io/zeitlos/lucity/charts/lucity-infra \ $(if $(VERSION),--version $(VERSION)) \ --kube-context $(DEV_CONTEXT) \ -n lucity-system --create-namespace \ -f deployments/lucity-dev/infra-values.yaml \ -f deployments/lucity-dev/infra-secrets.yaml \ $(HELM_ARGS) deploy-dev: @test -f deployments/lucity-dev/secrets.yaml || { echo "Error: deployments/lucity-dev/secrets.yaml not found. Copy deployments/secrets.yaml.example and fill in values."; exit 1; } helm upgrade --install lucity \ oci://ghcr.io/zeitlos/lucity/charts/lucity \ $(if $(VERSION),--version $(VERSION)) \ --kube-context $(DEV_CONTEXT) \ -n lucity-system --create-namespace \ -f deployments/lucity-dev/values.yaml \ -f deployments/lucity-dev/secrets.yaml \ $(HELM_ARGS) # Sync workspace sync: go work sync