Ctrl K
ComunidadGuías…

Makefile

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Homelab Horizon Build Makefile

BINARY_NAME=homelab-horizon
CMD_PATH=./cmd/homelab-horizon
VERSION?=$(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
BUILD_TIME=$(shell date -u '+%Y-%m-%d_%H:%M:%S')
LDFLAGS=-ldflags "-s -w -X main.Version=$(VERSION) -X main.BuildTime=$(BUILD_TIME)"

# Server builds embed the per-arch hz operator-CLI binaries (served at
# /admin/hz/*) behind the 'hzembed' tag; the hz-embed target cross-compiles them
# into this dir first. A plain `go build`/CI omits the tag and needs no binaries.
SERVER_TAGS=hzembed,uiembed
HZ_EMBED_DIR=internal/server/hzbin/bin
# The admin UI is compiled in for release builds, so a release is one file that
# renders its own login page. ui-embed stages ui/dist here first; a plain
# `go build`/CI omits the tag and needs no staged assets.
UI_EMBED_DIR=internal/server/uiembed/dist

# Default target
.PHONY: all
all: build

# Generate TypeScript types from Go structs (requires tygo)
.PHONY: generate
generate:
	go run github.com/iodesystems/tygo@v0.2.21-iode.1 generate

# Build frontend (React SPA)
.PHONY: ui
ui: generate
	cd ui && pnpm install --frozen-lockfile && pnpm build

# Create stub ui/dist for Go-only builds (no node toolchain required)
ui/dist/index.html:
	mkdir -p ui/dist
	echo '<!DOCTYPE html><html><body>Run <code>make ui</code> to build the frontend.</body></html>' > ui/dist/index.html

# Build for current platform (includes frontend)
.PHONY: build
build: ui hz-embed ui-embed
	CGO_ENABLED=0 go build -tags $(SERVER_TAGS) $(LDFLAGS) -o $(BINARY_NAME) $(CMD_PATH)

# Build Go only (uses stub frontend if ui/dist doesn't exist)
.PHONY: build-go
build-go: ui/dist/index.html hz-embed ui-embed
	CGO_ENABLED=0 go build -tags $(SERVER_TAGS) $(LDFLAGS) -o $(BINARY_NAME) $(CMD_PATH)

# Build the hz operator CLI (admin-token client: service CRUD + sync + setup)
.PHONY: build-hz
build-hz:
	CGO_ENABLED=0 go build $(LDFLAGS) -o hz ./cmd/hz

# Build hz-probe, the outside-in vantage agent. It runs on a host outside the
# homelab, so the useful build is a cross-compile you scp to that host.
.PHONY: build-probe
build-probe:
	CGO_ENABLED=0 go build $(LDFLAGS) -o hz-probe ./cmd/hz-probe

.PHONY: build-probe-all
build-probe-all: dist
	CGO_ENABLED=0 GOOS=linux GOARCH=amd64        go build $(LDFLAGS) -o dist/hz-probe-linux-amd64 ./cmd/hz-probe
	CGO_ENABLED=0 GOOS=linux GOARCH=arm64        go build $(LDFLAGS) -o dist/hz-probe-linux-arm64 ./cmd/hz-probe
	CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7  go build $(LDFLAGS) -o dist/hz-probe-linux-armv7 ./cmd/hz-probe

# Cross-compile the client binaries the server embeds: hz (served at
# /admin/hz/bin/) and hz-probe (served at /admin/hz-probe/bin/). Always builds
# all served arches regardless of the server's own arch.
.PHONY: ui-embed
ui-embed: ui/dist/index.html
	@rm -rf $(UI_EMBED_DIR)
	@mkdir -p $(UI_EMBED_DIR)
	@cp -r ui/dist/. $(UI_EMBED_DIR)/
	@du -sh $(UI_EMBED_DIR) | sed 's/^/  ui embedded: /'

.PHONY: hz-embed
hz-embed:
	@rm -rf $(HZ_EMBED_DIR)
	@mkdir -p $(HZ_EMBED_DIR)
	CGO_ENABLED=0 GOOS=linux GOARCH=amd64        go build $(LDFLAGS) -o $(HZ_EMBED_DIR)/hz-linux-amd64 ./cmd/hz
	CGO_ENABLED=0 GOOS=linux GOARCH=arm64        go build $(LDFLAGS) -o $(HZ_EMBED_DIR)/hz-linux-arm64 ./cmd/hz
	CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7  go build $(LDFLAGS) -o $(HZ_EMBED_DIR)/hz-linux-arm ./cmd/hz
	CGO_ENABLED=0 GOOS=linux GOARCH=amd64        go build $(LDFLAGS) -o $(HZ_EMBED_DIR)/hz-probe-linux-amd64 ./cmd/hz-probe
	CGO_ENABLED=0 GOOS=linux GOARCH=arm64        go build $(LDFLAGS) -o $(HZ_EMBED_DIR)/hz-probe-linux-arm64 ./cmd/hz-probe
	CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7  go build $(LDFLAGS) -o $(HZ_EMBED_DIR)/hz-probe-linux-arm ./cmd/hz-probe

# Run backend + frontend dev servers together (Ctrl-C stops both)
.PHONY: run
run: ui/node_modules
	@trap 'kill 0' EXIT; \
	cd ui && pnpm dev & \
	go run $(CMD_PATH) & \
	wait

# Run Go backend only (builds frontend first, serves at /app/)
.PHONY: run-backend
run-backend: ui
	go run $(CMD_PATH)

# Run Vite frontend dev server only (proxies API to :8080)
.PHONY: run-frontend
run-frontend: ui/node_modules
	cd ui && pnpm dev

# Install frontend dependencies if needed
ui/node_modules: ui/package.json
	cd ui && pnpm install
	@touch ui/node_modules

# Build for all platforms
.PHONY: build-all
build-all: ui build-linux-amd64 build-linux-arm64 build-linux-arm

# Linux AMD64 (most servers, x86_64)
.PHONY: build-linux-amd64
build-linux-amd64: ui dist hz-embed ui-embed
	CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -tags $(SERVER_TAGS) $(LDFLAGS) -o dist/$(BINARY_NAME)-linux-amd64 $(CMD_PATH)

# Linux ARM64 (Raspberry Pi 4/5, modern ARM servers)
.PHONY: build-linux-arm64
build-linux-arm64: ui dist hz-embed ui-embed
	CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -tags $(SERVER_TAGS) $(LDFLAGS) -o dist/$(BINARY_NAME)-linux-arm64 $(CMD_PATH)

# Linux ARM (Raspberry Pi 2/3, older 32-bit ARM)
.PHONY: build-linux-arm
build-linux-arm: ui dist hz-embed ui-embed
	CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -tags $(SERVER_TAGS) $(LDFLAGS) -o dist/$(BINARY_NAME)-linux-armv7 $(CMD_PATH)

# Clean build artifacts
.PHONY: clean
clean:
	rm -f $(BINARY_NAME) hz hz-probe
	rm -rf dist/
	rm -rf ui/dist/
	rm -rf $(HZ_EMBED_DIR)

# Run tests
.PHONY: test
test:
	go test -v ./...

# Run unit tests only
.PHONY: test-unit
test-unit:
	go test -v ./internal/...

# Run integration tests only
.PHONY: test-integration
test-integration:
	go test -v ./test/integration/...

# Run tests with coverage
.PHONY: test-coverage
test-coverage:
	go test -v -coverprofile=coverage.out ./...
	go tool cover -html=coverage.out -o coverage.html

# Lint (canon X-LINT-1) — install once: go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.13.1
.PHONY: lint
lint:
	golangci-lint run ./...

# Check/lint — gofmt-clean check (no mutation) + vet + golangci-lint
.PHONY: check
check: check-generated
	@test -z "$$(gofmt -l . )" || { echo "gofmt needed:"; gofmt -l .; exit 1; }
	go vet ./...
	golangci-lint run ./...

# The generated TypeScript must be BOTH current and valid, and nothing checked either.
#
# tygo emitted `export const X = any /* configmgr.Y */` for eleven cross-package constant
# aliases — `any` is a type, not a value — and the committed .ts simply went stale instead,
# so the breakage sat waiting for whoever next ran `make generate`. Regenerating and
# diffing catches staleness; tsc catches output that parses as Go but not as TypeScript.
.PHONY: check-generated
check-generated:
	@set -e; \
	 tmp=$$(mktemp); \
	 trap 'rm -f "$$tmp"' EXIT; \
	 cp ui/src/api/generated-types.ts "$$tmp"; \
	 go run github.com/iodesystems/tygo@v0.2.21-iode.1 generate >/dev/null; \
	 if ! diff -q "$$tmp" ui/src/api/generated-types.ts >/dev/null; then \
	   cp "$$tmp" ui/src/api/generated-types.ts; \
	   echo "generated-types.ts is STALE — run 'make generate' and commit the result"; exit 1; \
	 fi; \
	 cd ui && npx --no-install tsc --noEmit --skipLibCheck --ignoreConfig src/api/generated-types.ts \
	   || { echo "generated-types.ts is not valid TypeScript — see the errors above"; exit 1; }; \
	 echo "generated types: current and valid"

# Run all checks
.PHONY: test-all
test-all: test-unit test-integration check

# Create dist directory
dist:
	mkdir -p dist

# DEPLOY-9: assemble a deployable payload — the binary plus the frontend it now
# serves from disk (WEB-5). One tree to copy, so the two cannot arrive
# separately: a binary without its UI answers the API and shows an explanatory
# page where the login form should be.
#
# PAYLOAD_ARCH selects the binary; defaults to this machine's build.
PAYLOAD_ARCH ?=
.PHONY: payload
payload: ui
	@rm -rf dist/payload
	@mkdir -p dist/payload/public
	@if [ -n "$(PAYLOAD_ARCH)" ]; then \
		$(MAKE) build-$(PAYLOAD_ARCH) >/dev/null; \
		cp dist/$(BINARY_NAME)-$(PAYLOAD_ARCH) dist/payload/$(BINARY_NAME); \
	else \
		$(MAKE) build >/dev/null; \
		cp $(BINARY_NAME) dist/payload/$(BINARY_NAME); \
	fi
	cp -r ui/dist/. dist/payload/public/
	@echo "payload: dist/payload (binary + public/)"
	@du -sh dist/payload/public | sed 's/^/  ui: /'


# Build release archives
.PHONY: release
# One file per archive. The UI is compiled into the binary (SERVER_TAGS
# includes uiembed), so there is no second artifact to install correctly and no
# way to land a gateway whose admin page cannot render.
release: clean dist ui build-all
	@echo "Creating release archives..."
	@for arch in linux-amd64 linux-arm64 linux-armv7; do \
		tar -czf dist/$(BINARY_NAME)-$$arch.tar.gz -C dist $(BINARY_NAME)-$$arch; \
	done
	@echo "Release archives created in dist/"
	@ls -la dist/*.tar.gz

# Install locally (requires sudo)
.PHONY: install
install: build
	sudo cp $(BINARY_NAME) /usr/local/bin/
	@echo "Installed to /usr/local/bin/$(BINARY_NAME)"

# Build Docker demo image (vanilla Ubuntu + binary + demo config)
.PHONY: docker
docker: build-linux-amd64
	docker build -t homelab-horizon:demo .

# Run Docker demo container
.PHONY: docker-run
docker-run: docker
	docker run --rm -p 8080:8080 --name hz-demo homelab-horizon:demo

# Regenerate README screenshots in a hermetic Docker container (no real network)
.PHONY: screenshots
screenshots:
	./bin/screenshots

# End-to-end VPN MFA jail test in a throwaway multipass VM (real kernel,
# real WireGuard/iptables/HAProxy). Needs multipass; takes a few minutes.
.PHONY: e2e
e2e:
	./bin/e2e

# Admin account auth: bootstrap, TOTP against oathtool, the second-factor gate,
# the lockout guards, and a passkey ceremony against the admin relying party.
.PHONY: e2e-auth
e2e-auth:
	ACCOUNT=1 ACCOUNT_PASSKEY=1 OIDC=1 ./bin/e2e

.PHONY: help
help:
	@echo "Homelab Horizon Build Targets:"
	@echo ""
	@echo "  make              - Build for current platform (includes frontend)"
	@echo "  make ui           - Build frontend only (React SPA)"
	@echo "  make build-go     - Build Go only (stub frontend)"
	@echo "  make build-hz     - Build the hz operator CLI (admin-token client)"
	@echo "  make build-probe  - Build hz-probe (outside-in vantage agent)"
	@echo "  make build-probe-all - Cross-compile hz-probe for amd64/arm64/armv7"
	@echo "  make run          - Run backend + frontend dev servers together"
	@echo "  make run-backend  - Run Go backend only (:8080)"
	@echo "  make run-frontend - Run Vite frontend dev server only (:5173)"
	@echo "  make build-all    - Build for all platforms"
	@echo "  make release      - Build all platforms and create .tar.gz archives"
	@echo ""
	@echo "  make build-linux-amd64  - Build for Linux x86_64"
	@echo "  make build-linux-arm64  - Build for Linux ARM64 (Raspberry Pi 4/5)"
	@echo "  make build-linux-arm    - Build for Linux ARMv7 (Raspberry Pi 2/3)"
	@echo ""
	@echo "  make install      - Install to /usr/local/bin (requires sudo)"
	@echo "  make clean        - Remove build artifacts"
	@echo "  make test         - Run tests"
	@echo "  make check        - Run go vet and fmt"
	@echo ""
	@echo "  make docker       - Build Docker demo image"
	@echo "  make docker-run   - Build and run Docker demo on :8080"
	@echo "  make screenshots  - Regenerate README screenshots (hermetic Docker)"
	@echo "  make payload      - Assemble dist/payload (binary + public/) for deploy"
	@echo "  make e2e          - End-to-end MFA jail test (multipass VM)"
	@echo "  make e2e-auth     - End-to-end admin account auth test: accounts, passkeys, SSO"