Files
computer-playbook/scripts/image/build.sh
T
kevinveenbirkenbachandClaude Opus 4.7 9a578952c3 feat(env): SPOT env-pipeline + INFINITO_* namespace
Replace 8 bash env modules with a single Python generator
(cli.meta.env) that materialises env/static.env + runtime context
into a gitignored .env. Each variable lives in its own handler
under utils/env/handlers/; defaults carry inline one-line `# ...`
comments.

Rename to the INFINITO_* schema:
- workflow inputs: APPS, SERVICES_DISABLED, CMD, DEBUG, WHITELIST,
  BUNDLES, FULL_CYCLE, VARIANT, RUN_FLAGS, LIMIT_HOST, PARALLEL
- inventory: INVENTORY_DIR / INVENTORY_FILE / HOST_VARS_FILE,
  PW_FILE -> INVENTORY_PASSWORD_FILE
- venv: VENV_BASE / VENV_FALLBACK / VENV_NAME
- new SPOT defaults: COMPILE_SILENCE, CONTAINER_NO_CA,
  WAIT_HEALTH_TIMEOUT_S, PACKAGE_CACHE_FRONTEND_INIT_IMAGE,
  promoted IMAGE_TAG from GHA-only to general passthrough

compose.yml hardening:
- inline \${VAR:-default} removed; strict \${VAR:?...} everywhere
- new integration lint blocks regressions

Tooling:
- scripts/install/all.sh owns the stamp-file install logic
  (Makefile target delegates)
- scripts/meta/env/load.sh is the single shell entry point;
  8 bash modules deleted
- new shell lint: no INFINITO_* defaults outside env/static.env

Tests:
- 93 new unit tests for utils/env/{builder,parser,runtime,writer}
- restructure tests/lint into repository/ (file-content rules) vs
  filesystem/{shell,python,env}/ (filesystem-shape rules)
- drift + smoke integration tests against the generator

Docs:
- variables.md is now a pointer; per-variable docs live next to
  the key in env/static.env / env/ci.env
- compose.yml.md drops the Default column

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 20:40:34 +02:00

343 lines
9.2 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
: "${INFINITO_DISTRO:?Environment variable 'INFINITO_DISTRO' must be set (arch|debian|ubuntu|fedora|centos)}"
NO_CACHE=0
MISSING_ONLY=0
# New: build variant
VARIANT="full" # full | slim
# Keep --target for advanced usage, but default is derived from VARIANT.
TARGET=""
IMAGE_TAG="${IMAGE_TAG:-}" # local image name or base tag (without registry); can be pre-set via env
PUSH=0 # if 1 -> use buildx and push (requires docker buildx)
PUBLISH=0 # if 1 -> push with semantic tags (latest/version/stable + aliases)
REGISTRY="" # e.g. ghcr.io
OWNER="" # e.g. github org/user
REPO_PREFIX="" # image repository name
VERSION="" # X.Y.Z (required for --publish)
IS_STABLE="false" # "true" -> publish stable tags
DEFAULT_DISTRO="debian"
# Base pkgmgr image selection
PKGMGR_IMAGE_OWNER="kevinveenbirkenbach"
PKGMGR_IMAGE_TAG="stable" # can be overridden by env or future flag
PKGMGR_IMAGE="" # computed below (single build-arg for Dockerfile)
usage() {
local repo_prefix="${REPO_PREFIX:-${INFINITO_IMAGE_REPOSITORY:-<repo>}}" # nocheck: usage-text-placeholder -- '<repo>' is a literal placeholder rendered into --help, not a runtime default
local default_tag="${repo_prefix}/${INFINITO_DISTRO}"
if [[ "${VARIANT}" == "slim" ]]; then
default_tag="${default_tag}-slim"
fi
if [[ -n "${TARGET:-}" ]]; then
default_tag="${default_tag}-${TARGET}"
fi
cat <<EOF
Usage: INFINITO_DISTRO=<distro> $0 [options]
Build options:
--variant <full|slim> Build full or slim image (default: full)
--missing Build only if the image does not already exist (local build only)
--no-cache Build with --no-cache
--target <name> Override Dockerfile target (advanced). Default derived from --variant.
--tag <image> Override the output image tag (default: ${default_tag})
Publish options:
--push Push the built image (uses docker buildx build --push)
--publish Publish semantic tags (latest, <version>, optional stable) + default-distro aliases
--registry <reg> Registry (e.g. ghcr.io)
--owner <owner> Registry namespace (e.g. \${GITHUB_REPOSITORY_OWNER})
--repo-prefix <name> Image repository name (default: \$INFINITO_IMAGE_REPOSITORY)
--version <X.Y.Z> Version for --publish
--stable <true|false> Whether to publish :stable tags (default: false)
Notes:
- --publish implies --push and requires --registry, --owner, and --version.
- Local build (no --push) uses "docker build" and creates local images like "<repo>/arch" / "<repo>/arch-slim".
- If you set NIX_CONFIG in the environment (e.g. access-tokens), it will be forwarded into the build.
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--variant)
VARIANT="${2:-}"
[[ "${VARIANT}" == "full" || "${VARIANT}" == "slim" ]] || {
echo "ERROR: --variant must be 'full' or 'slim'" >&2
exit 2
}
shift 2
;;
--no-cache)
NO_CACHE=1
shift
;;
--missing)
MISSING_ONLY=1
shift
;;
--target)
TARGET="${2:-}"
[[ -n "${TARGET}" ]] || {
echo "ERROR: --target requires a value"
exit 2
}
shift 2
;;
--tag)
IMAGE_TAG="${2:-}"
[[ -n "${IMAGE_TAG}" ]] || {
echo "ERROR: --tag requires a value"
exit 2
}
shift 2
;;
--push)
PUSH=1
shift
;;
--publish)
PUBLISH=1
PUSH=1
shift
;;
--registry)
REGISTRY="${2:-}"
[[ -n "${REGISTRY}" ]] || {
echo "ERROR: --registry requires a value"
exit 2
}
shift 2
;;
--owner)
OWNER="${2:-}"
[[ -n "${OWNER}" ]] || {
echo "ERROR: --owner requires a value"
exit 2
}
shift 2
;;
--repo-prefix)
REPO_PREFIX="${2:-}"
[[ -n "${REPO_PREFIX}" ]] || {
echo "ERROR: --repo-prefix requires a value"
exit 2
}
shift 2
;;
--version)
VERSION="${2:-}"
[[ -n "${VERSION}" ]] || {
echo "ERROR: --version requires a value"
exit 2
}
shift 2
;;
--stable)
IS_STABLE="${2:-}"
[[ -n "${IS_STABLE}" ]] || {
echo "ERROR: --stable requires a value (true|false)"
exit 2
}
shift 2
;;
-h | --help)
usage
exit 0
;;
*)
echo "ERROR: Unknown argument: $1" >&2
usage
exit 2
;;
esac
done
if [[ -z "${REPO_PREFIX}" ]]; then
REPO_PREFIX="${INFINITO_IMAGE_REPOSITORY:-}"
fi
# REPO_PREFIX is only required when IMAGE_TAG is not already provided externally.
if [[ -z "${REPO_PREFIX}" && -z "${IMAGE_TAG}" ]]; then
echo "ERROR: Missing REPO_PREFIX or INFINITO_IMAGE_REPOSITORY (or set IMAGE_TAG directly)" >&2
exit 1
fi
if [[ -n "${REPO_PREFIX}" ]]; then
REPO_PREFIX="${REPO_PREFIX,,}"
fi
if [[ -n "${OWNER}" || -n "${GITHUB_REPOSITORY_OWNER:-}" || -n "${GITHUB_REPOSITORY:-}" ]]; then
OWNER="$(OWNER="${OWNER}" GITHUB_REPOSITORY_OWNER="${GITHUB_REPOSITORY_OWNER:-}" GITHUB_REPOSITORY="${GITHUB_REPOSITORY:-}" scripts/meta/resolve/repository/owner.sh)"
fi
# Derive default TARGET from VARIANT if not explicitly provided
if [[ -z "${TARGET}" ]]; then
if [[ "${VARIANT}" == "slim" ]]; then
TARGET="slim"
else
TARGET="full"
fi
fi
# Derive default local tag if not provided
if [[ -z "${IMAGE_TAG}" ]]; then
IMAGE_TAG="${REPO_PREFIX}/${INFINITO_DISTRO}"
if [[ "${VARIANT}" == "slim" ]]; then
IMAGE_TAG="${IMAGE_TAG}-slim"
fi
fi
# Compute PKGMGR_IMAGE based on variant (slim uses pkgmgr-*-slim)
if [[ "${VARIANT}" == "slim" ]]; then
PKGMGR_IMAGE="ghcr.io/${PKGMGR_IMAGE_OWNER}/pkgmgr-${INFINITO_DISTRO}-slim:${PKGMGR_IMAGE_TAG}"
else
PKGMGR_IMAGE="ghcr.io/${PKGMGR_IMAGE_OWNER}/pkgmgr-${INFINITO_DISTRO}:${PKGMGR_IMAGE_TAG}"
fi
# Local-only "missing" shortcut
if [[ "${MISSING_ONLY}" == "1" ]]; then
if [[ "${PUSH}" == "1" ]]; then
echo "ERROR: --missing is only supported for local builds (without --push/--publish)" >&2
exit 2
fi
if docker image inspect "${IMAGE_TAG}" >/dev/null 2>&1; then
echo "[build] Image already exists: ${IMAGE_TAG} (skipping due to --missing)"
exit 0
fi
fi
# Validate publish parameters
if [[ "${PUBLISH}" == "1" ]]; then
[[ -n "${REGISTRY}" ]] || {
echo "ERROR: --publish requires --registry"
exit 2
}
[[ -n "${OWNER}" ]] || {
echo "ERROR: --publish requires --owner"
exit 2
}
[[ -n "${VERSION}" ]] || {
echo "ERROR: --publish requires --version"
exit 2
}
fi
# Guard: --push without --publish requires fully-qualified --tag
if [[ "${PUSH}" == "1" && "${PUBLISH}" != "1" ]]; then
if [[ "${IMAGE_TAG}" != */* ]]; then
echo "ERROR: --push requires --tag with a fully-qualified name (e.g. ghcr.io/<owner>/<repo>/<image>:tag), or use --publish" >&2
exit 2
fi
fi
echo
echo "------------------------------------------------------------"
echo "[build] Building image"
echo "distro = ${INFINITO_DISTRO}"
echo "variant = ${VARIANT}"
echo "target = ${TARGET}"
echo "PKGMGR_IMAGE = ${PKGMGR_IMAGE}"
if [[ "${NO_CACHE}" == "1" ]]; then echo "cache = disabled"; fi
if [[ "${PUSH}" == "1" ]]; then echo "push = enabled"; fi
if [[ "${PUBLISH}" == "1" ]]; then
echo "publish = enabled"
echo "registry = ${REGISTRY}"
echo "owner = ${OWNER}"
echo "version = ${VERSION}"
echo "stable = ${IS_STABLE}"
fi
if [[ -n "${NIX_CONFIG:-}" ]]; then
echo "NIX_CONFIG = <set>"
else
echo "NIX_CONFIG = <empty>"
fi
echo "------------------------------------------------------------"
# Common build args
build_args=(
--build-arg "PKGMGR_IMAGE=${PKGMGR_IMAGE}"
--build-arg "NIX_CONFIG=${NIX_CONFIG:-}"
)
if [[ "${NO_CACHE}" == "1" ]]; then
build_args+=(--no-cache)
fi
if [[ -n "${TARGET}" ]]; then
build_args+=(--target "${TARGET}")
fi
compute_publish_tags() {
local suffix=""
local distro_tag_base=""
local alias_tag_base=""
if [[ "${VARIANT}" == "slim" ]]; then
suffix="-slim"
fi
distro_tag_base="${REGISTRY}/${OWNER}/${REPO_PREFIX}/${INFINITO_DISTRO}${suffix}"
if [[ "${INFINITO_DISTRO}" == "${DEFAULT_DISTRO}" ]]; then
alias_tag_base="${REGISTRY}/${OWNER}/${REPO_PREFIX}${suffix}"
fi
local tags=()
tags+=("${distro_tag_base}:latest")
tags+=("${distro_tag_base}:${VERSION}")
if [[ "${IS_STABLE}" == "true" ]]; then
tags+=("${distro_tag_base}:stable")
fi
if [[ -n "${alias_tag_base}" ]]; then
tags+=("${alias_tag_base}:latest")
tags+=("${alias_tag_base}:${VERSION}")
if [[ "${IS_STABLE}" == "true" ]]; then
tags+=("${alias_tag_base}:stable")
fi
fi
printf '%s\n' "${tags[@]}"
}
# WSL2: detect and remove a corrupted docker-buildx plugin (text file instead of ELF binary)
_buildx_bin="${HOME}/.docker/cli-plugins/docker-buildx"
if [[ -f "${_buildx_bin}" ]] && command -v file >/dev/null 2>&1; then
if ! file "${_buildx_bin}" | grep -qE "ELF|executable"; then
echo "[build] WARNING: Removing corrupted docker-buildx plugin (not an ELF binary)"
rm -f "${_buildx_bin}"
fi
fi
if [[ "${PUSH}" == "1" ]]; then
bx_args=(docker buildx build --push)
if [[ "${PUBLISH}" == "1" ]]; then
while IFS= read -r t; do
bx_args+=(-t "$t")
done < <(compute_publish_tags)
else
bx_args+=(-t "${IMAGE_TAG}")
fi
bx_args+=("${build_args[@]}")
bx_args+=(.)
echo "[build] Running: ${bx_args[*]}"
"${bx_args[@]}"
else
local_args=(docker build)
local_args+=("${build_args[@]}")
local_args+=(-t "${IMAGE_TAG}")
local_args+=(.)
echo "[build] Running: ${local_args[*]}"
"${local_args[@]}"
fi