Move the supported systemd path to two services: an owner-user bangerd for orchestration and a narrow root helper for bridge/tap, NAT/resolver, dm/loop, and Firecracker ownership. This removes repeated sudo from daily vm and image flows without leaving the general daemon running as root. Add install metadata, system install/status/restart/uninstall commands, and a system-owned runtime layout. Keep user SSH/config material in the owner home, lock file_sync to the owner home, and move daemon known_hosts handling out of the old root-owned control path. Route privileged lifecycle steps through typed privilegedOps calls, harden the two systemd units, and rewrite smoke plus docs around the supported service model. Verified with make build, make test, make lint, and make smoke on the supported systemd host path.
225 lines
10 KiB
Makefile
225 lines
10 KiB
Makefile
SHELL := /usr/bin/env bash
|
|
|
|
GO ?= go
|
|
GOFMT ?= gofmt
|
|
INSTALL ?= install
|
|
PREFIX ?= $(HOME)/.local
|
|
BINDIR ?= $(PREFIX)/bin
|
|
LIBDIR ?= $(PREFIX)/lib
|
|
DESTDIR ?=
|
|
BUILD_DIR ?= build
|
|
BUILD_BIN_DIR ?= $(BUILD_DIR)/bin
|
|
BUILD_MANUAL_DIR ?= $(BUILD_DIR)/manual
|
|
BANGER_BIN ?= $(BUILD_BIN_DIR)/banger
|
|
BANGERD_BIN ?= $(BUILD_BIN_DIR)/bangerd
|
|
VSOCK_AGENT_BIN ?= $(BUILD_BIN_DIR)/banger-vsock-agent
|
|
BINARIES := $(BANGER_BIN) $(BANGERD_BIN) $(VSOCK_AGENT_BIN)
|
|
GO_SOURCES := $(shell find cmd internal -type f -name '*.go' | sort)
|
|
# BUILD_INPUTS is everything that can change a binary's bytes: Go sources
|
|
# plus embedded assets (catalog.json, future static files). Listing
|
|
# everything is cheaper than missing a rebuild — go's own cache absorbs
|
|
# any redundant invocations.
|
|
BUILD_INPUTS := $(shell find cmd internal -type f | sort)
|
|
SHELL_SOURCES := $(shell find scripts -type f -name '*.sh' | sort)
|
|
SMOKE_DIR := $(BUILD_DIR)/smoke
|
|
SMOKE_BIN_DIR := $(SMOKE_DIR)/bin
|
|
SMOKE_COVER_DIR := $(SMOKE_DIR)/covdata
|
|
SMOKE_XDG_DIR := $(SMOKE_DIR)/xdg
|
|
SMOKE_SCRIPT := scripts/smoke.sh
|
|
VERSION ?= $(shell git describe --tags --exact-match 2>/dev/null || echo dev)
|
|
COMMIT ?= $(shell git rev-parse --verify HEAD 2>/dev/null || echo unknown)
|
|
BUILT_AT ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
GO_LDFLAGS := -X banger/internal/buildinfo.Version=$(VERSION) -X banger/internal/buildinfo.Commit=$(COMMIT) -X banger/internal/buildinfo.BuiltAt=$(BUILT_AT)
|
|
|
|
.DEFAULT_GOAL := help
|
|
|
|
.PHONY: help build banger bangerd test fmt tidy clean install uninstall lint lint-go lint-shell coverage coverage-html coverage-total coverage-combined coverage-combined-html smoke smoke-build smoke-coverage-html smoke-clean smoke-fresh
|
|
|
|
help:
|
|
@printf '%s\n' \
|
|
'Targets:' \
|
|
' make build Build ./build/bin/banger, ./build/bin/bangerd, and ./build/bin/banger-vsock-agent' \
|
|
' make install Build and install banger, bangerd, and the companion vsock helper' \
|
|
' make uninstall Stop the daemon and remove installed binaries (leaves user state by default)' \
|
|
' make test Run go test ./...' \
|
|
' make coverage Run tests with coverage; print per-package + total' \
|
|
' make coverage-html Open a browsable per-line HTML report (writes coverage.html)' \
|
|
' make coverage-total Print just the total statement coverage (for scripts/CI)' \
|
|
' make coverage-combined Merge unit-test + smoke covdata; print per-package + total' \
|
|
' make coverage-combined-html HTML report of the merged unit+smoke coverage' \
|
|
' make lint Run gofmt + go vet + shellcheck (errors)' \
|
|
' make fmt Format Go sources under cmd/ and internal/' \
|
|
' make tidy Run go mod tidy' \
|
|
' make clean Remove built Go binaries and coverage artefacts' \
|
|
' make smoke Build instrumented binaries, run the supported systemd smoke suite, report coverage (needs KVM + sudo)' \
|
|
' make smoke-fresh smoke-clean + smoke — purges stale smoke-owned installs before a clean supported-path run' \
|
|
' make smoke-coverage-html HTML coverage report from the last smoke run' \
|
|
' make smoke-clean Remove the smoke build tree and purge any stale smoke-owned system install'
|
|
|
|
build: $(BINARIES)
|
|
|
|
$(BANGER_BIN): $(BUILD_INPUTS) go.mod go.sum
|
|
mkdir -p "$(BUILD_BIN_DIR)"
|
|
$(GO) build -ldflags '$(GO_LDFLAGS)' -o "$(BANGER_BIN)" ./cmd/banger
|
|
|
|
$(BANGERD_BIN): $(BUILD_INPUTS) go.mod go.sum
|
|
mkdir -p "$(BUILD_BIN_DIR)"
|
|
$(GO) build -ldflags '$(GO_LDFLAGS)' -o "$(BANGERD_BIN)" ./cmd/bangerd
|
|
|
|
$(VSOCK_AGENT_BIN): $(BUILD_INPUTS) go.mod go.sum
|
|
mkdir -p "$(BUILD_BIN_DIR)"
|
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build -ldflags '$(GO_LDFLAGS)' -o "$(VSOCK_AGENT_BIN)" ./cmd/banger-vsock-agent
|
|
|
|
test:
|
|
$(GO) test ./...
|
|
|
|
# Coverage targets use -coverpkg=./... so packages without their own
|
|
# tests still get counted when another package exercises them (common
|
|
# for daemon/* subpackages). coverage.out is gitignored.
|
|
coverage:
|
|
$(GO) test -coverpkg=./... -coverprofile=coverage.out ./...
|
|
@echo ''
|
|
@echo 'Per-package:'
|
|
@$(GO) tool cover -func=coverage.out | awk -F'\t+' '/^total:/ {total=$$NF; next} {pkg=$$1; sub("banger/", "", pkg); sub("/[^/]+:[0-9]+:$$", "", pkg); pkgs[pkg]+=1; covered[pkg]+=$$NF+0} END {for (p in pkgs) printf " %-40s %.1f%% (avg of %d funcs)\n", p, covered[p]/pkgs[p], pkgs[p] | "sort"; print ""; print "Total statement coverage:", total}'
|
|
|
|
coverage-html: coverage
|
|
$(GO) tool cover -html=coverage.out -o coverage.html
|
|
@echo 'wrote coverage.html'
|
|
|
|
coverage-total:
|
|
@$(GO) test -coverpkg=./... -coverprofile=coverage.out ./... >/dev/null 2>&1 && $(GO) tool cover -func=coverage.out | awk '/^total:/ {print $$NF}'
|
|
|
|
# coverage-combined unions unit-test coverage and smoke coverage into
|
|
# one report. Unit tests cover pure-Go logic (error branches, parsing,
|
|
# handler wiring); smoke covers the real sudo / firecracker / dm-snap
|
|
# paths that unit tests physically can't reach. Separately each tells
|
|
# half the story. Merged, this is the single "what's not being
|
|
# exercised at all" view.
|
|
#
|
|
# Requires an up-to-date smoke run (the target depends on smoke-build
|
|
# to rebuild instrumented binaries; re-run `make smoke` yourself if
|
|
# scenarios changed). Modes must match; smoke uses the default 'set',
|
|
# so the unit run below drops the default 'atomic' for alignment.
|
|
COMBINED_COVER_DIR := $(BUILD_DIR)/combined
|
|
UNIT_COVER_DIR := $(BUILD_DIR)/unit/covdata
|
|
coverage-combined:
|
|
@test -d "$(SMOKE_COVER_DIR)" && test "$$(ls -A $(SMOKE_COVER_DIR) 2>/dev/null)" || { \
|
|
echo 'no smoke covdata at $(SMOKE_COVER_DIR); run `make smoke` first' >&2; exit 1; \
|
|
}
|
|
rm -rf "$(UNIT_COVER_DIR)" "$(COMBINED_COVER_DIR)"
|
|
mkdir -p "$(UNIT_COVER_DIR)" "$(COMBINED_COVER_DIR)"
|
|
$(GO) test -cover -coverpkg=./... ./... -args -test.gocoverdir="$(abspath $(UNIT_COVER_DIR))" >/dev/null
|
|
$(GO) tool covdata merge -i="$(UNIT_COVER_DIR),$(SMOKE_COVER_DIR)" -o="$(COMBINED_COVER_DIR)"
|
|
$(GO) tool covdata textfmt -i="$(COMBINED_COVER_DIR)" -o="$(BUILD_DIR)/combined.cover.out"
|
|
@echo ''
|
|
@echo 'Per-package (merged unit + smoke):'
|
|
@$(GO) tool cover -func="$(BUILD_DIR)/combined.cover.out" | awk -F'\t+' '/^total:/ {total=$$NF; next} {pkg=$$1; sub("banger/", "", pkg); sub("/[^/]+:[0-9]+:$$", "", pkg); pkgs[pkg]+=1; covered[pkg]+=$$NF+0} END {for (p in pkgs) printf " %-40s %.1f%% (avg of %d funcs)\n", p, covered[p]/pkgs[p], pkgs[p] | "sort"; print ""; print "Total statement coverage:", total}'
|
|
|
|
coverage-combined-html: coverage-combined
|
|
$(GO) tool cover -html="$(BUILD_DIR)/combined.cover.out" -o "$(BUILD_DIR)/combined.cover.html"
|
|
@echo 'wrote $(BUILD_DIR)/combined.cover.html'
|
|
|
|
lint: lint-go lint-shell
|
|
|
|
lint-go:
|
|
@unformatted="$$($(GOFMT) -l $(GO_SOURCES))"; \
|
|
if [ -n "$$unformatted" ]; then \
|
|
printf 'gofmt: the following files are not formatted:\n%s\n' "$$unformatted" >&2; \
|
|
exit 1; \
|
|
fi
|
|
$(GO) vet ./...
|
|
|
|
lint-shell:
|
|
@command -v shellcheck >/dev/null 2>&1 || { echo 'shellcheck is required for make lint-shell' >&2; exit 1; }
|
|
shellcheck --severity=error $(SHELL_SOURCES)
|
|
|
|
fmt:
|
|
$(GOFMT) -w $(GO_SOURCES)
|
|
|
|
tidy:
|
|
$(GO) mod tidy
|
|
|
|
clean:
|
|
rm -rf "$(BUILD_BIN_DIR)" coverage.out coverage.html
|
|
|
|
# Smoke test suite. Builds the three banger binaries with -cover
|
|
# instrumentation under $(SMOKE_BIN_DIR), installs them as temporary
|
|
# bangerd.service + bangerd-root.service, runs scripts/smoke.sh, copies
|
|
# service covdata out of /var/lib/banger, then purges the smoke-owned
|
|
# install on exit.
|
|
#
|
|
# Unlike the old per-user daemon path, this touches global systemd
|
|
# state. The smoke script refuses to overwrite a pre-existing non-smoke
|
|
# install and uses a marker file so `make smoke-clean` can recover a
|
|
# stale smoke-owned install after an interrupted run.
|
|
#
|
|
# Requires a KVM-capable Linux host with sudo. This is a pre-release
|
|
# gate, not CI — the Go test suite is what runs everywhere.
|
|
smoke-build: $(SMOKE_BIN_DIR)/.built
|
|
|
|
$(SMOKE_BIN_DIR)/.built: $(BUILD_INPUTS) go.mod go.sum
|
|
mkdir -p "$(SMOKE_BIN_DIR)"
|
|
$(GO) build -cover -ldflags '$(GO_LDFLAGS)' -o "$(SMOKE_BIN_DIR)/banger" ./cmd/banger
|
|
$(GO) build -cover -ldflags '$(GO_LDFLAGS)' -o "$(SMOKE_BIN_DIR)/bangerd" ./cmd/bangerd
|
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build -ldflags '$(GO_LDFLAGS)' -o "$(SMOKE_BIN_DIR)/banger-vsock-agent" ./cmd/banger-vsock-agent
|
|
touch "$@"
|
|
|
|
smoke: smoke-build
|
|
rm -rf "$(SMOKE_COVER_DIR)"
|
|
mkdir -p "$(SMOKE_COVER_DIR)" "$(SMOKE_XDG_DIR)"
|
|
BANGER_SMOKE_BIN_DIR="$(abspath $(SMOKE_BIN_DIR))" \
|
|
BANGER_SMOKE_COVER_DIR="$(abspath $(SMOKE_COVER_DIR))" \
|
|
BANGER_SMOKE_XDG_DIR="$(abspath $(SMOKE_XDG_DIR))" \
|
|
bash "$(SMOKE_SCRIPT)"
|
|
@echo ''
|
|
@echo 'Smoke coverage:'
|
|
@$(GO) tool covdata percent -i="$(SMOKE_COVER_DIR)"
|
|
|
|
smoke-coverage-html: smoke
|
|
$(GO) tool covdata textfmt -i="$(SMOKE_COVER_DIR)" -o="$(SMOKE_DIR)/cover.out"
|
|
$(GO) tool cover -html="$(SMOKE_DIR)/cover.out" -o "$(SMOKE_DIR)/cover.html"
|
|
@echo 'wrote $(SMOKE_DIR)/cover.html'
|
|
|
|
smoke-clean:
|
|
@if sudo test -f /etc/banger/.smoke-owned; then \
|
|
bin=''; \
|
|
if [ -x "$(SMOKE_BIN_DIR)/banger" ]; then \
|
|
bin="$(abspath $(SMOKE_BIN_DIR))/banger"; \
|
|
elif [ -x "$(BANGER_BIN)" ]; then \
|
|
bin="$(abspath $(BANGER_BIN))"; \
|
|
elif [ -x /usr/local/bin/banger ]; then \
|
|
bin=/usr/local/bin/banger; \
|
|
fi; \
|
|
if [ -n "$$bin" ]; then \
|
|
sudo "$$bin" system uninstall --purge >/dev/null 2>&1 || true; \
|
|
fi; \
|
|
fi
|
|
rm -rf "$(SMOKE_DIR)"
|
|
|
|
# smoke-fresh wipes the instrumented build tree, purges any stale
|
|
# smoke-owned install, and then runs the supported-path smoke suite
|
|
# from scratch.
|
|
smoke-fresh: smoke-clean smoke
|
|
|
|
install: build
|
|
mkdir -p "$(DESTDIR)$(BINDIR)"
|
|
mkdir -p "$(DESTDIR)$(LIBDIR)/banger"
|
|
$(INSTALL) -m 0755 "$(BANGER_BIN)" "$(DESTDIR)$(BINDIR)/banger"
|
|
$(INSTALL) -m 0755 "$(BANGERD_BIN)" "$(DESTDIR)$(BINDIR)/bangerd"
|
|
$(INSTALL) -m 0755 "$(VSOCK_AGENT_BIN)" "$(DESTDIR)$(LIBDIR)/banger/banger-vsock-agent"
|
|
|
|
# uninstall stops a running daemon (if any) and removes the installed
|
|
# binaries. It does NOT touch user data (config, SSH keys, VM state,
|
|
# image/kernel caches) — rm -rf those paths manually if wanted; they
|
|
# are printed for convenience.
|
|
uninstall:
|
|
@if [ -x "$(DESTDIR)$(BINDIR)/banger" ]; then \
|
|
"$(DESTDIR)$(BINDIR)/banger" daemon stop >/dev/null 2>&1 || true; \
|
|
fi
|
|
rm -f "$(DESTDIR)$(BINDIR)/banger" "$(DESTDIR)$(BINDIR)/bangerd"
|
|
rm -rf "$(DESTDIR)$(LIBDIR)/banger"
|
|
@printf '\nRemoved binaries. User data is preserved at:\n'
|
|
@printf ' ~/.config/banger/ (config, ssh keys)\n'
|
|
@printf ' ~/.local/state/banger/ (VMs, images, kernels, db, logs)\n'
|
|
@printf ' ~/.cache/banger/ (OCI layer cache)\n'
|
|
@printf '\nDelete those paths manually if you want a full purge.\n'
|