build.sh writes KANIDM_UNIXD_TOKEN to /etc/kanidm/unixd_token (0600) when the secret is set, via a generated add-files include. Without the secret the image is built unchanged and the token must be provisioned on the host.
246 lines
7.7 KiB
Bash
Executable File
246 lines
7.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Build a bootc OCI image from an image definition under images/<image>.
|
|
#
|
|
# Usage: build.sh <image> <distro> [arch]
|
|
#
|
|
# The per-image build.conf selects the build mode:
|
|
#
|
|
# upstream Clone the upstream Fedora manifest repo at a ref mapped from
|
|
# <distro>, overlay this image's manifests and support files, then
|
|
# compose the OCI image with `rpm-ostree compose image`.
|
|
#
|
|
# standalone Compose from this image's own manifest.yaml, wrapping it with the
|
|
# releasever/ref from build.conf.
|
|
set -euo pipefail
|
|
|
|
if [[ $# -lt 2 ]]; then
|
|
echo "Usage: $0 <image> <distro> [arch]" >&2
|
|
exit 1
|
|
fi
|
|
|
|
IMAGE="$1"
|
|
DISTRO="$2"
|
|
ARCH="${3:-$(uname -m)}"
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
IMAGE_DIR="${REPO_ROOT}/images/${IMAGE}"
|
|
CONF="${IMAGE_DIR}/build.conf"
|
|
|
|
[[ -d "${IMAGE_DIR}" ]] || { echo "Unknown image: ${IMAGE}" >&2; exit 1; }
|
|
[[ -f "${CONF}" ]] || { echo "Missing build config: ${CONF}" >&2; exit 1; }
|
|
|
|
# Defaults, overridden by build.conf.
|
|
BUILD_MODE=""
|
|
UPSTREAM_REPO=""
|
|
TOKEN2=0
|
|
RUN_TEST_SH=0
|
|
RELEASEVER=""
|
|
REF=""
|
|
declare -A UPSTREAM_REFS=()
|
|
|
|
# shellcheck disable=SC1090
|
|
source "${CONF}"
|
|
|
|
BUILD_DIR="${REPO_ROOT}/build/${IMAGE}-${DISTRO}-${ARCH}"
|
|
OCI_ARCHIVE="${BUILD_DIR}/${IMAGE}-${DISTRO}-${ARCH}.ociarchive"
|
|
CACHE_DIR="${REPO_ROOT}/cache"
|
|
|
|
[[ -n "${BUILD_MODE}" ]] || { echo "BUILD_MODE not set in ${CONF}" >&2; exit 1; }
|
|
|
|
# Build IDs must be unique per build (release tags are derived from them), so
|
|
# include the CI run number when available, otherwise a timestamp.
|
|
if [[ -n "${GITHUB_RUN_NUMBER:-}" ]]; then
|
|
buildid="$(date '+%Y%m%d').${GITHUB_RUN_NUMBER}"
|
|
else
|
|
buildid="$(date '+%Y%m%d.%H%M%S')"
|
|
fi
|
|
echo "${buildid}" > "${REPO_ROOT}/.buildid"
|
|
|
|
rm -rf "${BUILD_DIR}"
|
|
mkdir -p "${BUILD_DIR}" "${CACHE_DIR}"
|
|
|
|
if [[ "${RUN_TEST_SH}" == "1" && -x "${IMAGE_DIR}/test.sh" ]]; then
|
|
echo "Running ${IMAGE_DIR}/test.sh"
|
|
( cd "${IMAGE_DIR}" && ./test.sh )
|
|
fi
|
|
|
|
# Download the latest token2-fido-bridge RPM from GitHub and turn it into a
|
|
# local yum repo so the manifest can install it.
|
|
setup_token2_repo() {
|
|
local dest="$1"
|
|
local api url
|
|
api="https://api.github.com/repos/token2/token2-fido-bridge/releases/latest"
|
|
url="$(curl -fsSL "${api}" \
|
|
| jq -r '.assets[] | select(.name | endswith(".rpm")) | .browser_download_url' \
|
|
| head -n1)"
|
|
if [[ -z "${url}" || "${url}" == "null" ]]; then
|
|
echo "ERROR: no token2-fido-bridge .rpm found in the latest GitHub release" >&2
|
|
exit 1
|
|
fi
|
|
mkdir -p "${dest}/token2-repo"
|
|
curl -fsSL "${url}" -o "${dest}/token2-repo/$(basename "${url}")"
|
|
createrepo_c "${dest}/token2-repo"
|
|
cat > "${dest}/token2-fido-bridge.repo" <<EOF
|
|
[token2-fido-bridge]
|
|
name=token2-fido-bridge
|
|
baseurl=file://${dest}/token2-repo
|
|
enabled=1
|
|
gpgcheck=0
|
|
EOF
|
|
}
|
|
|
|
# Drop packages listed in a blocklist from the cloned upstream package
|
|
# manifests. rpm-ostree refuses (fatally) to exclude a package that an included
|
|
# manifest declares, so remove those lines at the source instead.
|
|
trim_upstream_packages() {
|
|
local root="$1"
|
|
local blocklist="$2"
|
|
local f
|
|
for f in "${root}"/packages/*.yaml; do
|
|
[[ -f "${f}" ]] || continue
|
|
awk '
|
|
NR == FNR {
|
|
if ($0 ~ /^[[:space:]]*#/ || $0 ~ /^[[:space:]]*$/) next
|
|
skip[$0] = 1
|
|
next
|
|
}
|
|
{
|
|
line = $0
|
|
sub(/^[[:space:]]*-[[:space:]]*/, "", line)
|
|
if (line in skip) next
|
|
print
|
|
}
|
|
' "${blocklist}" "${f}" > "${f}.tmp"
|
|
mv "${f}.tmp" "${f}"
|
|
done
|
|
}
|
|
|
|
# Generate an exclude-packages snippet from the same blocklist. This blocks the
|
|
# removed packages from being pulled back in as recommends (e.g. linux-firmware
|
|
# recommends the split firmware packages).
|
|
generate_exclude_yaml() {
|
|
local blocklist="$1"
|
|
local out="$2"
|
|
{
|
|
echo "# Generated by build.sh from $(basename "${blocklist}") - do not edit."
|
|
echo "exclude-packages:"
|
|
grep -vE '^[[:space:]]*(#|$)' "${blocklist}" | sed 's/^/ - /'
|
|
} > "${out}"
|
|
}
|
|
|
|
# Seed the Kanidm unixd service account token from the KANIDM_UNIXD_TOKEN
|
|
# environment variable (a CI secret). The token is added to the image, so treat
|
|
# the image as sensitive: anyone who can pull it can read the token.
|
|
seed_kanidm_token() {
|
|
local dir="$1"
|
|
local out="${dir}/kanidm-token.yaml"
|
|
if [[ -n "${KANIDM_UNIXD_TOKEN:-}" ]]; then
|
|
printf '%s\n' "${KANIDM_UNIXD_TOKEN}" > "${dir}/kanidm-unixd-token"
|
|
chmod 0600 "${dir}/kanidm-unixd-token"
|
|
cat > "${out}" <<'EOF'
|
|
# Generated by build.sh from the KANIDM_UNIXD_TOKEN secret.
|
|
add-files:
|
|
- ["kanidm-unixd-token", "/etc/kanidm/unixd_token"]
|
|
EOF
|
|
else
|
|
cat > "${out}" <<'EOF'
|
|
# Generated by build.sh: no Kanidm token seeded (KANIDM_UNIXD_TOKEN unset).
|
|
add-files: []
|
|
EOF
|
|
fi
|
|
}
|
|
|
|
case "${BUILD_MODE}" in
|
|
upstream)
|
|
[[ -n "${UPSTREAM_REPO}" ]] || { echo "UPSTREAM_REPO not set in ${CONF}" >&2; exit 1; }
|
|
upstream_ref="${UPSTREAM_REFS[${DISTRO}]:-}"
|
|
[[ -n "${upstream_ref}" ]] || { echo "No upstream ref configured for distro '${DISTRO}'" >&2; exit 1; }
|
|
|
|
git clone --depth 1 --branch "${upstream_ref}" "${UPSTREAM_REPO}" "${BUILD_DIR}/upstream"
|
|
|
|
# Overlay our manifests/support files into the upstream tree so that
|
|
# relative includes and *.repo discovery keep working.
|
|
cp "${IMAGE_DIR}/manifest.yaml" "${BUILD_DIR}/upstream/"
|
|
cp "${IMAGE_DIR}/custom.yaml" "${BUILD_DIR}/upstream/"
|
|
if compgen -G "${IMAGE_DIR}/repos/*.repo" >/dev/null; then
|
|
cp "${IMAGE_DIR}"/repos/*.repo "${BUILD_DIR}/upstream/"
|
|
fi
|
|
if compgen -G "${IMAGE_DIR}/repos/${DISTRO}/*.repo" >/dev/null; then
|
|
cp "${IMAGE_DIR}"/repos/"${DISTRO}"/*.repo "${BUILD_DIR}/upstream/"
|
|
fi
|
|
if [[ -d "${IMAGE_DIR}/files" ]]; then
|
|
cp -a "${IMAGE_DIR}/files/." "${BUILD_DIR}/upstream/"
|
|
fi
|
|
|
|
if [[ "${TOKEN2}" == "1" ]]; then
|
|
setup_token2_repo "${BUILD_DIR}/upstream"
|
|
fi
|
|
|
|
if [[ -f "${IMAGE_DIR}/upstream-exclude.txt" ]]; then
|
|
trim_upstream_packages "${BUILD_DIR}/upstream" "${IMAGE_DIR}/upstream-exclude.txt"
|
|
generate_exclude_yaml "${IMAGE_DIR}/upstream-exclude.txt" \
|
|
"${BUILD_DIR}/upstream/hardware-exclude.yaml"
|
|
fi
|
|
|
|
seed_kanidm_token "${BUILD_DIR}/upstream"
|
|
|
|
MANIFEST="${BUILD_DIR}/upstream/manifest.yaml"
|
|
;;
|
|
standalone)
|
|
[[ -n "${RELEASEVER}" ]] || { echo "RELEASEVER not set in ${CONF}" >&2; exit 1; }
|
|
[[ -n "${REF}" ]] || { echo "REF not set in ${CONF}" >&2; exit 1; }
|
|
|
|
# Build from a copy so we never mutate the checked-in image definition.
|
|
mkdir -p "${BUILD_DIR}/images"
|
|
cp -a "${IMAGE_DIR}" "${BUILD_DIR}/images/${IMAGE}"
|
|
|
|
local_dir="${BUILD_DIR}/images/${IMAGE}"
|
|
if compgen -G "${local_dir}/repos/*.repo" >/dev/null; then
|
|
cp "${local_dir}"/repos/*.repo "${local_dir}/"
|
|
fi
|
|
if compgen -G "${local_dir}/repos/${DISTRO}/*.repo" >/dev/null; then
|
|
cp "${local_dir}"/repos/"${DISTRO}"/*.repo "${local_dir}/"
|
|
fi
|
|
|
|
if [[ "${TOKEN2}" == "1" ]]; then
|
|
setup_token2_repo "${local_dir}"
|
|
fi
|
|
|
|
cat > "${local_dir}/.build-manifest.yaml" <<EOF
|
|
releasever: ${RELEASEVER}
|
|
ref: ${REF}
|
|
include:
|
|
- manifest.yaml
|
|
EOF
|
|
seed_kanidm_token "${local_dir}"
|
|
MANIFEST="${local_dir}/.build-manifest.yaml"
|
|
;;
|
|
*)
|
|
echo "Unknown BUILD_MODE: ${BUILD_MODE}" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
echo "Composing ${IMAGE} ${DISTRO} (${ARCH}) build ${buildid} ..."
|
|
|
|
RPM_OSTREE=(rpm-ostree)
|
|
if [[ "${EUID}" -ne 0 ]]; then
|
|
RPM_OSTREE=(sudo -E rpm-ostree)
|
|
fi
|
|
|
|
"${RPM_OSTREE[@]}" compose image \
|
|
--cachedir="${CACHE_DIR}" \
|
|
--initialize \
|
|
--max-layers=256 \
|
|
"${MANIFEST}" \
|
|
"${OCI_ARCHIVE}"
|
|
|
|
# The finalize.d hook records the installed package list next to the treefile.
|
|
manifest_dir="$(dirname "${MANIFEST}")"
|
|
if [[ -f "${manifest_dir}/packages-current.txt" ]]; then
|
|
cp "${manifest_dir}/packages-current.txt" "${BUILD_DIR}/packages-current.txt"
|
|
echo "Package list: ${BUILD_DIR}/packages-current.txt"
|
|
fi
|
|
|
|
echo "Built: ${OCI_ARCHIVE}"
|