From ac1a5d85f6fb88223c95b52ecc00bb202980a395 Mon Sep 17 00:00:00 2001 From: Ben Vincent Date: Thu, 30 Jul 2026 21:04:06 +1000 Subject: [PATCH] Seed bootapi-images: AlmaLinux node rootfs build Builds the AlmaLinux 9 node rootfs tarball bootapi's image-based provisioning (liveimg) unpacks, and on a v* tag publishes almalinux9-node-.tar.zst to the artifactapi rootfs-images repo. Moved out of bootapi-templates (which stays templates-only) per review on bootapi-templates#3. - packages.txt (bake list) + scripts/build-rootfs.sh (dnf --installroot -> tar.zst). - .woodpecker: pre-commit + shellcheck (PR); build+upload on v* tag. - Makefile (build/lint/patch|minor|major). Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv --- .gitignore | 2 ++ .pre-commit-config.yaml | 20 +++++++++++++ .woodpecker/lint.yaml | 18 ++++++++++++ .woodpecker/pre-commit.yaml | 18 ++++++++++++ .woodpecker/release.yaml | 57 +++++++++++++++++++++++++++++++++++++ .yamllint.yaml | 8 ++++++ Makefile | 32 +++++++++++++++++++++ README.md | 40 +++++++++++++++++++++++++- packages.txt | 40 ++++++++++++++++++++++++++ scripts/build-rootfs.sh | 43 ++++++++++++++++++++++++++++ 10 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 .pre-commit-config.yaml create mode 100644 .woodpecker/lint.yaml create mode 100644 .woodpecker/pre-commit.yaml create mode 100644 .woodpecker/release.yaml create mode 100644 .yamllint.yaml create mode 100644 Makefile create mode 100644 packages.txt create mode 100755 scripts/build-rootfs.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eeee0bf --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/rootfs/ +/dist/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..740efd6 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,20 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - id: check-merge-conflict + + - repo: https://github.com/adrienverge/yamllint + rev: v1.35.1 + hooks: + - id: yamllint + args: [-c, .yamllint.yaml] + + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: v0.10.0.1 + hooks: + - id: shellcheck diff --git a/.woodpecker/lint.yaml b/.woodpecker/lint.yaml new file mode 100644 index 0000000..deb8fcc --- /dev/null +++ b/.woodpecker/lint.yaml @@ -0,0 +1,18 @@ +when: + - event: [pull_request, push] + +steps: + - name: lint + image: koalaman/shellcheck-alpine:stable + commands: + - shellcheck scripts/*.sh + backend_options: + kubernetes: + serviceAccountName: default + resources: + requests: + memory: 128Mi + cpu: 100m + limits: + memory: 512Mi + cpu: 500m diff --git a/.woodpecker/pre-commit.yaml b/.woodpecker/pre-commit.yaml new file mode 100644 index 0000000..98ffe88 --- /dev/null +++ b/.woodpecker/pre-commit.yaml @@ -0,0 +1,18 @@ +when: + - event: [pull_request, push] + +steps: + - name: pre-commit + image: git.unkin.net/unkin/almalinux9-gobuilder:20260606 + commands: + - uvx pre-commit run --all-files + backend_options: + kubernetes: + serviceAccountName: default + resources: + requests: + memory: 512Mi + cpu: 1 + limits: + memory: 2Gi + cpu: 2 diff --git a/.woodpecker/release.yaml b/.woodpecker/release.yaml new file mode 100644 index 0000000..33460fa --- /dev/null +++ b/.woodpecker/release.yaml @@ -0,0 +1,57 @@ +when: + - event: tag + ref: refs/tags/v* + +# On a v* tag: build the AlmaLinux 9 node rootfs and publish +# almalinux9-node-.tar.zst to the artifactapi rootfs-images local +# repo, where the bootapi-templates almalinux9-image catalog entry's liveimg +# points. Bump the image with `make patch|minor|major`. +steps: + - name: build-rootfs + image: git.unkin.net/unkin/almalinux9-base:20260606 + commands: + - dnf -y install tar zstd + - ./scripts/build-rootfs.sh ${CI_COMMIT_TAG} + backend_options: + kubernetes: + serviceAccountName: default + resources: + requests: + memory: 2Gi + cpu: 2 + ephemeral-storage: 8Gi + limits: + memory: 4Gi + cpu: 4 + ephemeral-storage: 16Gi + + - name: upload + image: git.unkin.net/unkin/almalinux9-base:20260606 + commands: + - | + VER="${CI_COMMIT_TAG#v}" + HOST="https://artifactapi.k8s.syd1.au.unkin.net" + REPO="rootfs-images" + FILE="almalinux9-node-$${VER}.tar.zst" + # Immutable, versioned artifact; skip if this version already exists. + code=$$(curl -s -o /dev/null -w '%{http_code}' "$$HOST/api/v2/remotes/$$REPO/files/$$FILE" || true) + if [ "$$code" = "200" ]; then + echo "$$FILE already exists (HTTP $$code); skipping upload" + exit 0 + fi + curl -f -X PUT "$$HOST/api/v2/remotes/$$REPO/files/$$FILE" \ + -H "Content-Type: application/zstd" \ + --data-binary @"dist/$$FILE" + depends_on: [build-rootfs] + backend_options: + kubernetes: + serviceAccountName: default + resources: + requests: + memory: 512Mi + cpu: 500m + ephemeral-storage: 8Gi + limits: + memory: 1Gi + cpu: 1 + ephemeral-storage: 16Gi diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 0000000..5173038 --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,8 @@ +--- +extends: relaxed + +rules: + line-length: disable + document-start: disable + comments: + min-spaces-from-content: 1 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cec0bbd --- /dev/null +++ b/Makefile @@ -0,0 +1,32 @@ +.PHONY: build lint clean patch minor major + +VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo "0.0.0-dev") + +# Build the rootfs tarball locally (needs dnf + tar + zstd; run as root). +build: + ./scripts/build-rootfs.sh $(VERSION) + +lint: + shellcheck scripts/*.sh + +clean: + rm -rf rootfs/ dist/ + +# --- version bump: tag + push triggers the release pipeline (build + upload) --- +_LATEST := $(shell git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$$' | head -1) +_BASE := $(if $(_LATEST),$(_LATEST),v0.0.0) +_MAJ := $(shell echo $(_BASE) | sed 's/^v//' | cut -d. -f1) +_MIN := $(shell echo $(_BASE) | sed 's/^v//' | cut -d. -f2) +_PAT := $(shell echo $(_BASE) | sed 's/^v//' | cut -d. -f3) + +patch: + @NEW=v$(_MAJ).$(_MIN).$(shell expr $(_PAT) + 1); \ + git tag $$NEW && echo "Tagged $$NEW" && git push origin $$NEW + +minor: + @NEW=v$(_MAJ).$(shell expr $(_MIN) + 1).0; \ + git tag $$NEW && echo "Tagged $$NEW" && git push origin $$NEW + +major: + @NEW=v$(shell expr $(_MAJ) + 1).0.0; \ + git tag $$NEW && echo "Tagged $$NEW" && git push origin $$NEW diff --git a/README.md b/README.md index cd1f447..f90d032 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,41 @@ # bootapi-images -Builds AlmaLinux node rootfs tarballs for bootapi image-based provisioning; on v* tag publishes almalinux9-node-.tar.zst to the artifactapi rootfs-images repo. \ No newline at end of file +Builds the AlmaLinux 9 **node rootfs** tarball that +[bootapi](https://git.unkin.net/unkin/bootapi)'s image-based provisioning unpacks +with Anaconda `liveimg`. On a `v*` tag it publishes +`almalinux9-node-.tar.zst` to the artifactapi `rootfs-images` local repo, +where the [bootapi-templates](https://git.unkin.net/unkin/bootapi-templates) +`almalinux9-image` catalog entry's `rootfs_tarball` points. + +This is the build half; the kickstart/catalog that *consumes* the tarball live in +bootapi-templates. Kept separate so that repo stays templates-only. + +## What's baked in + +`packages.txt` is the package set installed into the rootfs — everything the +`image.ks.tmpl` `%post` assumes is already present (kernel, grub2 BIOS+UEFI, +dracut, NetworkManager, openssh, chrony, kexec-tools, curl, …). `puppet-agent` is +added from the puppet platform repo in `scripts/build-rootfs.sh`. Per-host config +(networking, hostname, puppet server, callback) stays in the kickstart `%post`. + +## Layout + +``` +packages.txt # bake list (one package per line) +scripts/build-rootfs.sh # dnf --installroot -> tar.zst +.woodpecker/ # pre-commit + lint (PR); release build+upload (v* tag) +Makefile # build / lint / patch|minor|major +``` + +## Releasing an image + +`make patch|minor|major` tags `v` and pushes; CI builds the rootfs and +uploads `almalinux9-node-.tar.zst`. Then bump `rootfs_tarball` in the +bootapi-templates `almalinux9-image` catalog entry to the new version (immutable; +overwrites are 409-rejected). + +Build locally (needs `dnf`, `tar`, `zstd`, root): + +```bash +make build VERSION=v0.0.0-dev +``` diff --git a/packages.txt b/packages.txt new file mode 100644 index 0000000..b543256 --- /dev/null +++ b/packages.txt @@ -0,0 +1,40 @@ +# Package set baked into the AlmaLinux 9 node rootfs image. +# Everything image.ks.tmpl's %post assumes is already installed (puppet-agent is +# added separately in build-rootfs.sh from the puppet platform repo). One package +# per line; blank lines and # comments ignored. + +# base system + kernel +@core +kernel + +# boot chain: BIOS + UEFI (fleet firmware is not standardized yet) +grub2-pc +grub2-efi-x64 +shim-x64 +grub2-tools +grub2-tools-efi +efibootmgr +dracut +dracut-config-generic + +# storage +lvm2 +xfsprogs +e2fsprogs +dosfstools + +# networking + selinux +NetworkManager +selinux-policy-targeted +policycoreutils + +# node tooling (matches the classic package install) +openssh-server +chrony +kexec-tools +bind-utils +vim-minimal +tmux +git +curl +glibc-langpack-en diff --git a/scripts/build-rootfs.sh b/scripts/build-rootfs.sh new file mode 100755 index 0000000..4084846 --- /dev/null +++ b/scripts/build-rootfs.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# +# Build the AlmaLinux 9 node rootfs and tar it as almalinux9-node-.tar.zst +# for bootapi image-based provisioning (liveimg). Usage: +# scripts/build-rootfs.sh [version] (version defaults to ${CI_COMMIT_TAG#v}) +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "${ROOT_DIR}" + +VER="${1:-${CI_COMMIT_TAG:-0.0.0-dev}}" +VER="${VER#v}" +RELEASEVER="${RELEASEVER:-9}" +ROOTFS="${ROOTFS:-${ROOT_DIR}/rootfs}" +DIST="${DIST:-${ROOT_DIR}/dist}" +OUT="${DIST}/almalinux9-node-${VER}.tar.zst" + +# Read the bake list (skip comments / blank lines). +mapfile -t PKGS < <(grep -vE '^[[:space:]]*(#|$)' packages.txt) +if [ "${#PKGS[@]}" -eq 0 ]; then + echo "ERROR: packages.txt is empty" >&2 + exit 1 +fi + +rm -rf "${ROOTFS}" +mkdir -p "${ROOTFS}" "${DIST}" + +dnf -y --installroot="${ROOTFS}" --releasever="${RELEASEVER}" \ + --setopt=install_weak_deps=False install "${PKGS[@]}" + +# Puppet agent from the puppet platform repo (baked; image.ks.tmpl %post only +# configures it). +dnf -y --installroot="${ROOTFS}" install https://yum.puppet.com/puppet8-release-el-9.noarch.rpm +dnf -y --installroot="${ROOTFS}" install puppet-agent + +dnf -y --installroot="${ROOTFS}" clean all +rm -rf "${ROOTFS}"/var/cache/dnf/* "${ROOTFS}"/var/log/dnf* "${ROOTFS}"/etc/machine-id + +# Reproducible, ownership/xattr/SELinux-preserving tarball. +tar --numeric-owner --acls --xattrs --selinux -C "${ROOTFS}" -caf "${OUT}" . + +echo "Built: ${OUT}" +ls -lh "${OUT}"