Implemented static code testing

This commit is contained in:
2025-12-17 12:12:22 +01:00
parent 75ee4c737d
commit c94cc2d676
19 changed files with 1039 additions and 172 deletions

226
scripts/build/image.sh Executable file
View File

@@ -0,0 +1,226 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
: "${INFINITO_DISTRO:?Environment variable 'INFINITO_DISTRO' must be set (arch|debian|ubuntu|fedora|centos)}"
NO_CACHE=0
MISSING_ONLY=0
TARGET=""
IMAGE_TAG="" # local image name or base tag (without registry)
PUSH=0 # if 1 -> use buildx and push (requires docker buildx)
PUBLISH=0 # if 1 -> push with semantic tags (latest/version/stable + arch aliases)
REGISTRY="" # e.g. ghcr.io
OWNER="" # e.g. github org/user
REPO_PREFIX="infinito" # image base name (infinito)
VERSION="" # X.Y.Z (required for --publish)
IS_STABLE="false" # "true" -> publish stable tags
DEFAULT_DISTRO="arch"
PKGMGR_IMAGE_OWNER=kevinveenbirkenbach
PKGMGR_IMAGE_TAG=stable
INFINITO_IMAGE_BASE="ghcr.io/${PKGMGR_IMAGE_OWNER}/pkgmgr-${INFINITO_DISTRO}:${PKGMGR_IMAGE_TAG}"
usage() {
local default_tag="infinito-${INFINITO_DISTRO}"
if [[ -n "${TARGET:-}" ]]; then
default_tag="${default_tag}-${TARGET}"
fi
cat <<EOF
Usage: INFINITO_DISTRO=<distro> $0 [options]
Build options:
--missing Build only if the image does not already exist (local build only)
--no-cache Build with --no-cache
--target <name> Build a specific Dockerfile target (e.g. virgin)
--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) + arch aliases
--registry <reg> Registry (e.g. ghcr.io)
--owner <owner> Registry namespace (e.g. \${GITHUB_REPOSITORY_OWNER})
--repo-prefix <name> Image base name (default: infinito)
--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 "infinito-arch" / "infinito-arch-virgin".
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--no-cache) NO_CACHE=1; shift ;;
--missing) MISSING_ONLY=1; shift ;;
--target)
TARGET="${2:-}"
[[ -n "${TARGET}" ]] || { echo "ERROR: --target requires a value (e.g. virgin)"; 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
# Derive default local tag if not provided
if [[ -z "${IMAGE_TAG}" ]]; then
IMAGE_TAG="${REPO_PREFIX}-${INFINITO_DISTRO}"
if [[ -n "${TARGET}" ]]; then
IMAGE_TAG="${IMAGE_TAG}-${TARGET}"
fi
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>/<image>:tag), or use --publish" >&2
exit 2
fi
fi
echo
echo "------------------------------------------------------------"
echo "[build] Building image"
echo "distro = ${INFINITO_DISTRO}"
echo "INFINITO_IMAGE_BASE = ${INFINITO_IMAGE_BASE}"
if [[ -n "${TARGET}" ]]; then echo "target = ${TARGET}"; fi
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
echo "------------------------------------------------------------"
# Common build args
build_args=(--build-arg "INFINITO_IMAGE_BASE=${INFINITO_IMAGE_BASE}")
if [[ "${NO_CACHE}" == "1" ]]; then
build_args+=(--no-cache)
fi
if [[ -n "${TARGET}" ]]; then
build_args+=(--target "${TARGET}")
fi
compute_publish_tags() {
local distro_tag_base="${REGISTRY}/${OWNER}/${REPO_PREFIX}-${INFINITO_DISTRO}"
local alias_tag_base=""
if [[ -n "${TARGET}" ]]; then
distro_tag_base="${distro_tag_base}-${TARGET}"
fi
if [[ "${INFINITO_DISTRO}" == "${DEFAULT_DISTRO}" ]]; then
alias_tag_base="${REGISTRY}/${OWNER}/${REPO_PREFIX}"
if [[ -n "${TARGET}" ]]; then
alias_tag_base="${alias_tag_base}-${TARGET}"
fi
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[@]}"
}
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

55
scripts/build/publish.sh Executable file
View File

@@ -0,0 +1,55 @@
#!/usr/bin/env bash
set -euo pipefail
# Publish all distro images (full + virgin) to a registry via image.sh --publish
#
# Required env:
# OWNER (e.g. GITHUB_REPOSITORY_OWNER)
# VERSION (e.g. 1.2.3)
#
# Optional env:
# REGISTRY (default: ghcr.io)
# IS_STABLE (default: false)
# DISTROS (default: "arch debian ubuntu fedora centos")
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REGISTRY="${REGISTRY:-ghcr.io}"
IS_STABLE="${IS_STABLE:-false}"
DISTROS="${DISTROS:-arch debian ubuntu fedora centos}"
: "${OWNER:?Environment variable OWNER must be set (e.g. github.repository_owner)}"
: "${VERSION:?Environment variable VERSION must be set (e.g. 1.2.3)}"
echo "[publish] REGISTRY=${REGISTRY}"
echo "[publish] OWNER=${OWNER}"
echo "[publish] VERSION=${VERSION}"
echo "[publish] IS_STABLE=${IS_STABLE}"
echo "[publish] DISTROS=${DISTROS}"
for d in ${DISTROS}; do
echo
echo "============================================================"
echo "[publish] INFINITO_DISTRO=${d}"
echo "============================================================"
# virgin
INFINITO_DISTRO="${d}" bash "${SCRIPT_DIR}/image.sh" \
--publish \
--registry "${REGISTRY}" \
--owner "${OWNER}" \
--version "${VERSION}" \
--stable "${IS_STABLE}" \
--target virgin
# full (default target)
INFINITO_DISTRO="${d}" bash "${SCRIPT_DIR}/image.sh" \
--publish \
--registry "${REGISTRY}" \
--owner "${OWNER}" \
--version "${VERSION}" \
--stable "${IS_STABLE}"
done
echo
echo "[publish] Done."

35
scripts/docker/entry.sh Executable file
View File

@@ -0,0 +1,35 @@
#!/usr/bin/env bash
set -euo pipefail
echo "[docker-infinito] Starting package-manager container"
# ---------------------------------------------------------------------------
# Log distribution info
# ---------------------------------------------------------------------------
if [[ -f /etc/os-release ]]; then
# shellcheck disable=SC1091
. /etc/os-release
echo "[docker-infinito] Detected distro: ${ID:-unknown} (like: ${ID_LIKE:-})"
fi
# ---------------------------------------------------------------------------
# DEV mode: rebuild package-manager from the mounted /opt/src/infinito tree
# ---------------------------------------------------------------------------
if [[ "${REINSTALL_INFINITO:-0}" == "1" ]]; then
echo "[docker-infinito] Using /opt/src/infinito as working directory"
cd /opt/src/infinito
echo "[docker-infinito] DEV mode enabled (REINSTALL_INFINITO=1)"
echo "[docker-infinito] Reinstall via 'make install'..."
make install || exit 1
fi
# ---------------------------------------------------------------------------
# Hand off to infinito or arbitrary command
# ---------------------------------------------------------------------------
if [[ $# -eq 0 ]]; then
echo "[docker-infinito] No arguments provided. Showing infinito help..."
exec infinito --help
else
echo "[docker-infinito] Executing command: $*"
exec "$@"
fi

194
scripts/tests/deploy.sh Normal file
View File

@@ -0,0 +1,194 @@
#!/usr/bin/env bash
set -euo pipefail
# -------------------------------------------------------------------
# Config
# -------------------------------------------------------------------
INFINITO_IMAGE_TEMPLATE_DEFAULT="infinito:{distro}:latest"
INFINITO_IMAGE_TEMPLATE="${INFINITO_IMAGE_TEMPLATE:-$INFINITO_IMAGE_TEMPLATE_DEFAULT}"
MASK_CREDENTIALS_IN_LOGS_DEFAULT="false"
MASK_CREDENTIALS_IN_LOGS="${MASK_CREDENTIALS_IN_LOGS:-$MASK_CREDENTIALS_IN_LOGS_DEFAULT}"
AUTHORIZED_KEYS_DEFAULT="ssh-ed25519 AAAA_TEST_DUMMY_KEY github-ci-dummy@infinito"
AUTHORIZED_KEYS="${AUTHORIZED_KEYS:-$AUTHORIZED_KEYS_DEFAULT}"
DEPLOY_TARGET_DEFAULT="server"
DEPLOY_TARGET="${DEPLOY_TARGET:-$DEPLOY_TARGET_DEFAULT}"
# Optional: allow adding additional excludes globally (comma-separated or newline-separated)
ALWAYS_EXCLUDE="${ALWAYS_EXCLUDE:-}"
# Central excludes (same everywhere) - can be comma-separated or newline-separated.
BASE_EXCLUDE="${BASE_EXCLUDE:-$(
cat <<'EOF'
drv-lid-switch
svc-db-memcached
svc-db-redis
svc-net-wireguard-core
svc-net-wireguard-firewalled
svc-net-wireguard-plain
svc-bkp-loc-2-usb
svc-bkp-rmt-2-loc
svc-opt-keyboard-color
svc-opt-ssd-hdd
web-app-bridgy-fed
web-app-oauth2-proxy
web-app-postmarks
web-app-elk
web-app-syncope
web-app-socialhome
web-svc-xmpp
EOF
)}"
# -------------------------------------------------------------------
# Helpers
# -------------------------------------------------------------------
die() { echo "[ERROR] $*" >&2; exit 1; }
render_image() {
local distro="$1"
echo "${INFINITO_IMAGE_TEMPLATE//\{distro\}/$distro}"
}
join_by_comma() {
local IFS=","
echo "$*"
}
trim_lines() {
sed -e 's/[[:space:]]\+$//' -e '/^$/d'
}
get_invokable() {
if command -v infinito >/dev/null 2>&1; then
infinito meta applications invokable | trim_lines
return 0
fi
# Best-effort fallback: if your repo exposes a python module for this.
if python3 -c "import cli" >/dev/null 2>&1; then
python3 -m cli.meta.applications invokable 2>/dev/null | trim_lines || true
return 0
fi
die "Cannot list invokable applications (neither 'infinito' nor usable python module found)."
}
filter_allowed() {
local type="$1"
case "$type" in
workstation)
# desk-* and util-desk-*
grep -E '^(desk-|util-desk-)' || true
;;
server)
# web-* (includes web-app-*, web-svc-*, etc.)
grep -E '^web-' || true
;;
universal)
cat
;;
*)
die "Unknown deploy type: $type (expected: workstation|server|universal)"
;;
esac
}
compute_exclude_csv() {
local type="$1"
local all allowed computed combined final
all="$(get_invokable)"
allowed="$(printf "%s\n" "$all" | filter_allowed "$type")"
# computed = all - allowed
computed="$(comm -23 <(printf "%s\n" "$all" | sort) <(printf "%s\n" "$allowed" | sort) | trim_lines)"
# combined = computed BASE_EXCLUDE ALWAYS_EXCLUDE
combined="$(
printf "%s\n%s\n%s\n" \
"$computed" \
"$(printf "%s\n" "$BASE_EXCLUDE" | tr ',' '\n')" \
"$(printf "%s\n" "$ALWAYS_EXCLUDE" | tr ',' '\n')" \
| trim_lines | sort -u
)"
# final = combined ∩ all (keep only invokable names)
final="$(comm -12 <(printf "%s\n" "$combined" | sort) <(printf "%s\n" "$all" | sort) | trim_lines)"
# To comma-separated
# shellcheck disable=SC2206
local arr=($final)
if [[ ${#arr[@]} -eq 0 ]]; then
echo ""
else
join_by_comma "${arr[@]}"
fi
}
run_deploy() {
local image="$1"
local exclude_csv="$2"
shift 2
python3 -m cli.deploy.container run --image "$image" "$@" -- \
--exclude "$exclude_csv" \
--vars "{\"MASK_CREDENTIALS_IN_LOGS\": ${MASK_CREDENTIALS_IN_LOGS}}" \
--authorized-keys "$AUTHORIZED_KEYS" \
-- \
-T "$DEPLOY_TARGET"
}
# -------------------------------------------------------------------
# Main
# -------------------------------------------------------------------
DEPLOY_TYPE=""
DISTRO=""
while [[ $# -gt 0 ]]; do
case "$1" in
--type) DEPLOY_TYPE="${2:-}"; shift 2 ;;
--distro) DISTRO="${2:-}"; shift 2 ;;
--target) DEPLOY_TARGET="${2:-}"; shift 2 ;;
--image-template) INFINITO_IMAGE_TEMPLATE="${2:-}"; shift 2 ;;
--) shift; break ;;
*) die "Unknown arg: $1" ;;
esac
done
[[ -n "$DEPLOY_TYPE" ]] || die "Missing --type workstation|server|universal"
[[ -n "$DISTRO" ]] || die "Missing --distro arch|debian|ubuntu|fedora|centos"
case "$DISTRO" in
arch|debian|ubuntu|fedora|centos) ;;
*) die "Unsupported distro: $DISTRO" ;;
esac
IMAGE="$(render_image "$DISTRO")"
EXCLUDE_CSV="$(compute_exclude_csv "$DEPLOY_TYPE")"
echo ">>> Deploy type: $DEPLOY_TYPE"
echo ">>> Distro: $DISTRO"
echo ">>> Target: $DEPLOY_TARGET"
echo ">>> Image: $IMAGE"
echo ">>> Excluded roles: ${EXCLUDE_CSV:-<none>}"
# -------------------------------------------------------------------
# 1) First deploy: normal + debug (with build)
# -------------------------------------------------------------------
echo ">>> [1/3] First deploy (normal + debug, with build)"
run_deploy "$IMAGE" "$EXCLUDE_CSV" --build -- --debug --skip-cleanup --skip-tests
# -------------------------------------------------------------------
# 2) Second deploy: reset + debug (without build)
# -------------------------------------------------------------------
echo ">>> [2/3] Second deploy (--reset --debug, reuse image)"
run_deploy "$IMAGE" "$EXCLUDE_CSV" -- --reset --debug --skip-cleanup --skip-tests
# -------------------------------------------------------------------
# 3) Third deploy: async deploy no debug (reuse image)
# -------------------------------------------------------------------
echo ">>> [3/3] Third deploy (async deploy no debug)"
run_deploy "$IMAGE" "$EXCLUDE_CSV" -- --skip-cleanup --skip-tests

24
scripts/tests/static.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/usr/bin/env bash
set -euo pipefail
echo "============================================================"
echo ">>> Running UNIT tests in ${INFINITO_DISTRO} container"
echo "============================================================"
docker run --rm \
-e REINSTALL_INFINITO=1 \
-e PYTHON="${PYTHON}" \
-e TEST_PATTERN="${TEST_PATTERN}" \
-e TEST_TYPE="${TEST_TYPE}" \
-v "$(pwd):/opt/src/infinito" \
"infinito-${INFINITO_DISTRO}" \
bash -lc '
set -euo pipefail
cd /opt/src/infinito
echo "PWD=$(pwd)"
echo "PYTHON=${PYTHON}"
export PATH="$(dirname "$PYTHON"):$PATH"
# Ensure we really use the exported interpreter (and thus the global venv)
"${PYTHON}" -m unittest discover -s tests/${TEST_TYPE} -t . -p "${TEST_PATTERN}"
'