appliance/Makefile
Rama main · réplica de lectura del repositorio original.
# Windows appliance image build. All tooling runs in one builder container.
SHELL := /bin/bash
.DEFAULT_GOAL := help
REPO := $(abspath $(CURDIR)/..)
OUT := $(CURDIR)/out
COMMIT := $(shell git rev-parse HEAD)
BUILDER := achilles-appliance-builder:local
BASE_URL := https://cloud-images.ubuntu.com/releases/noble/release
BASE_IMG := ubuntu-24.04-server-cloudimg-amd64.img
HOST_UID := $(shell id -u)
HOST_GID := $(shell id -g)
# The appliance version is the platform release tag it was built from (spec
# 9): describe only v<x>.<y>.<z> tags, never agent-v* ones, which share
# commits with platform releases. Computed once (:=).
DESCRIBED := $(shell git describe --tags --match 'v[0-9]*.[0-9]*.[0-9]*' --always --dirty)
ifndef VER
VER := $(DESCRIBED)
endif
# Expanded by `image`: a VER from the environment or the command line must
# equal $(DESCRIBED), or the image would be named, versioned and stamped
# (-X main.version, ACHILLES_VERSION) with it. ALLOW_VER_OVERRIDE=1 allows it.
VER_CHECK = $(if $(filter 1,$(ALLOW_VER_OVERRIDE)),,$(if $(filter $(DESCRIBED),$(VER)),,$(error \
VER=$(VER) is not this commit's version $(DESCRIBED) (git describe). Unset VER, \
or set ALLOW_VER_OVERRIDE=1 to build an image named and stamped $(VER))))
IMAGE ?= out/achilles-appliance-$(VER).qcow2
# Run a command in the builder. Same absolute path inside as on the host.
DOCKER_RUN = docker run --rm --user root --device /dev/kvm \
-v $(REPO):$(REPO) -w $(REPO)/appliance \
-e VER="$(VER)" -e COMMIT="$(COMMIT)" $(BUILDER)
# Same, for targets that write to out/: always chown out/ back to the invoking
# user afterwards (even on failure) and keep the command's exit code, so
# `make clean` works without sudo. Usage: $(DOCKER_RUN_OUT) <cmd...>
DOCKER_RUN_OUT = $(DOCKER_RUN) bash -c \
'"$$@"; rc=$$?; chown -R $(HOST_UID):$(HOST_GID) out; exit $$rc' _
.PHONY: help builder builder-smoke check-space base-image installer-bin \
installer-testbin out-ownership-check image repo-bundle check-sealed boot-test vhdx clean
help:
@echo "targets: builder builder-smoke check-space base-image installer-bin"
@echo " installer-testbin out-ownership-check image repo-bundle check-sealed"
@echo " boot-test vhdx clean"
$(OUT):
mkdir -p $(OUT)
builder:
docker build -t $(BUILDER) -f build/Dockerfile.builder build
builder-smoke:
$(DOCKER_RUN) build/builder-smoke.sh
check-space: | $(OUT)
@free_kb=$$(df -Pk $(OUT) | awk 'NR==2 {print $$4}'); \
if [ "$$free_kb" -lt 41943040 ]; then \
echo "need at least 40 GB free in appliance/out" >&2; exit 1; \
fi
base-image: | $(OUT)
build/fetch-base-image.sh $(OUT) $(BASE_URL) $(BASE_IMG)
installer-bin: | $(OUT)
$(MAKE) -C ../installer build VERSION=$(VER)
cp ../installer/dist/achilles-installer-linux-amd64 $(OUT)/achilles-installer
installer-testbin: | $(OUT)
cd ../installer && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -trimpath -tags appliancetest \
-ldflags "-X main.version=$(VER)" -o $(OUT)/achilles-installer-test .
# Proves DOCKER_RUN_OUT leaves out/ files owned by the invoking user.
out-ownership-check: | $(OUT)
rm -f $(OUT)/.ownership-check
$(DOCKER_RUN_OUT) touch out/.ownership-check
@owner=$$(stat -c %u $(OUT)/.ownership-check); rm -f $(OUT)/.ownership-check; \
if [ "$$owner" != "$(HOST_UID)" ]; then \
echo "out/ file owned by uid $$owner, expected $(HOST_UID)" >&2; exit 1; \
fi; echo "out-ownership-check: OK"
# Sealed qcow2 from the committed HEAD: refuses a dirty tree (the image holds
# a clean checkout of exactly $(COMMIT)) and a VER that is not its describe
# (VER_CHECK), rebuilds the installer binary for this commit, writes
# repo.bundle, then runs Packer in the builder (build/build-image.sh, which
# also runs check-sealed before naming the image).
image:
$(VER_CHECK)
@case "$(VER)" in *-dirty) \
echo "commit your changes: the image must hold a clean checkout" >&2; exit 1 ;; esac
@git diff --quiet HEAD -- || { \
echo "commit your changes: the image must hold a clean checkout" >&2; exit 1; }
$(MAKE) builder check-space base-image installer-bin repo-bundle VER=$(VER)
$(DOCKER_RUN_OUT) build/build-image.sh
# out/repo.bundle: the build commit (as HEAD; a bare SHA gives the bundle no
# ref to clone) and the platform release tags merged into it, which
# `upgrade` and describe need. Nothing else of the build host's: no stash,
# no local branches, no agent-v* tags, and no local-only tags (they could
# carry private commits). Asserts the bundle holds exactly those refs.
BUNDLE_TAGS = git tag --merged HEAD -l 'v[0-9]*.[0-9]*.[0-9]*'
repo-bundle: | $(OUT)
rm -f $(OUT)/repo.bundle
git bundle create $(OUT)/repo.bundle HEAD $$($(BUNDLE_TAGS) | sed 's|^|refs/tags/|')
@heads=$$(git bundle list-heads $(OUT)/repo.bundle); \
echo "$$heads" | grep -qx '$(COMMIT) HEAD' || { echo "repo.bundle: HEAD is not $(COMMIT)" >&2; exit 1; }; \
got=$$(echo "$$heads" | awk '{print $$2}' | sort); \
want=$$( { echo HEAD; $(BUNDLE_TAGS) | sed 's|^|refs/tags/|'; } | sort); \
[ "$$got" = "$$want" ] || { echo "repo.bundle: refs are not HEAD + the v* tags merged into it:" >&2; \
diff <(echo "$$want") <(echo "$$got") >&2; exit 1; }; \
echo "repo.bundle: HEAD + $$(echo "$$want" | grep -c '^refs/tags/') tags"
# Offline inspection of IMAGE (path relative to appliance/; defaults to the
# image `make image` builds for this VER). Exit 1 = not sealed.
check-sealed:
@test -n "$(IMAGE)" || { echo "usage: make check-sealed IMAGE=out/<image>" >&2; exit 2; }
$(DOCKER_RUN) build/check-sealed.sh $(IMAGE)
# Boot-test IMAGE in two throwaway VMs (test/boot-test.sh; spec 10.1).
# MODE=quick: first boot, identity, image reuse labels, reboot. MODE=full also
# installs, backs up and restores (10-40 min). The build commit comes from the
# image name (<ver> = git describe: ...-g<sha>, or a tag); unknown names fall
# back to HEAD. Override with BUILD_COMMIT=<sha>.
MODE ?= quick
boot-test: installer-testbin
@case "$(MODE)" in quick|full) ;; *) \
echo "usage: make boot-test MODE=quick|full [IMAGE=out/<image>.qcow2]" >&2; exit 2 ;; esac
@test -f "$(IMAGE)" || { echo "boot-test: no image $(IMAGE)" >&2; exit 2; }
@bc='$(BUILD_COMMIT)'; \
if [ -z "$$bc" ]; then \
v=$$(basename "$(IMAGE)" .qcow2); v=$${v#achilles-appliance-}; ref=$$v; \
if [[ $$v =~ -g([0-9a-f]{7,40})$$ ]]; then ref=$${BASH_REMATCH[1]}; fi; \
bc=$$(git rev-parse --verify -q "$$ref^{commit}") || { \
bc=$(COMMIT); echo "boot-test: no commit in the image name; expecting HEAD"; }; \
fi; \
echo "boot-test: $(IMAGE), MODE=$(MODE), build commit $$bc"; \
$(DOCKER_RUN_OUT) env BUILD_COMMIT="$$bc" test/boot-test.sh "$(IMAGE)" out/achilles-installer-test "$(MODE)"
# Hyper-V package from IMAGE: dynamic VHDX, zipped, with a SHA-256 of the zip.
# Outputs in out/: ProjectAchilles-<ver>.vhdx, ProjectAchilles-<ver>-hyperv.zip,
# appliance-SHA256SUMS (zip only, relative name, so `sha256sum -c` works in out/).
vhdx:
@test -f "$(IMAGE)" || { echo "vhdx: no image $(IMAGE)" >&2; exit 2; }
$(DOCKER_RUN_OUT) bash -euo pipefail -c ' \
rm -f out/ProjectAchilles-$(VER).vhdx out/ProjectAchilles-$(VER)-hyperv.zip out/appliance-SHA256SUMS; \
qemu-img convert -O vhdx -o subformat=dynamic "$(IMAGE)" out/ProjectAchilles-$(VER).vhdx; \
qemu-img check out/ProjectAchilles-$(VER).vhdx; \
zip -j out/ProjectAchilles-$(VER)-hyperv.zip out/ProjectAchilles-$(VER).vhdx; \
cd out && sha256sum ProjectAchilles-$(VER)-hyperv.zip > appliance-SHA256SUMS'
clean:
rm -rf $(OUT)