diff options
| -rw-r--r-- | .github/workflows/build-installer-iso.yml | 2 | ||||
| -rw-r--r-- | .github/workflows/validate-installer-iso.yml | 11 | ||||
| -rw-r--r-- | Justfile | 5 | ||||
| -rw-r--r-- | README.md | 7 | ||||
| -rw-r--r-- | installer/Containerfile | 6 | ||||
| -rwxr-xr-x | installer/build.sh | 16 | ||||
| -rwxr-xr-x | installer/patch-anaconda-webui.sh | 47 | ||||
| -rw-r--r-- | installer/system_files/overrides/etc/anaconda/conf.d/99-aerocore-password.conf | 5 | ||||
| -rwxr-xr-x | just_scripts/build-installer-iso.sh | 54 |
9 files changed, 129 insertions, 24 deletions
diff --git a/.github/workflows/build-installer-iso.yml b/.github/workflows/build-installer-iso.yml index a6348aa..143eafc 100644 --- a/.github/workflows/build-installer-iso.yml +++ b/.github/workflows/build-installer-iso.yml @@ -91,6 +91,8 @@ jobs: --build-arg "BASE_IMAGE=${LIVE_BASE_IMAGE}" \ --build-arg "INSTALL_IMAGE_PAYLOAD=${install_image_ref}:${IMAGE_TAG}" \ --build-arg "INSTALL_IMAGE_PAYLOAD_ARCHIVE=/install-payload.oci" \ + --build-arg "INSTALL_IMAGE_TAG=${IMAGE_TAG}" \ + --build-arg "OSTREE_IMAGE_REF=${ostree_image_ref}" \ --tag localhost/aerocore-os-installer:latest \ --file installer/Containerfile \ . diff --git a/.github/workflows/validate-installer-iso.yml b/.github/workflows/validate-installer-iso.yml index 391afd8..a93e9cd 100644 --- a/.github/workflows/validate-installer-iso.yml +++ b/.github/workflows/validate-installer-iso.yml @@ -39,6 +39,17 @@ jobs: done bash -n system_files/usr/libexec/aerocore-instruments-setup + - name: Validate Anaconda WebUI patch + run: | + fixture="$(mktemp)" + trap 'rm -f "${fixture}"' EXIT + printf '%s' 'n===0?r(new Error(X2("Password is too weak"))):o(' | + gzip -n > "${fixture}" + installer/patch-anaconda-webui.sh "${fixture}" + gzip -dc "${fixture}" | + grep -Fq 'n===0&&!e?r(new Error(X2("Password is too weak"))):o(' + installer/patch-anaconda-webui.sh "${fixture}" + - name: Validate installer polkit rules run: | for rule in installer/system_files/overrides/etc/polkit-1/rules.d/*.rules; do @@ -14,7 +14,8 @@ export VERSION_PRETTY := env_var_or_default("VERSION_PRETTY", VERSION_TAG) export BASE_IMAGE := env_var_or_default("BASE_IMAGE", "ghcr.io/ublue-os/bazzite-deck:stable") export OSTREE_IMAGE_REF := env_var_or_default("OSTREE_IMAGE_REF", "ostree-image-signed:docker://ghcr.io/" + repo_organization + "/" + image_name) export LIVE_BASE_IMAGE := env_var_or_default("LIVE_BASE_IMAGE", "quay.io/fedora/fedora-kinoite:43") -export INSTALLER_BUILDER_IMAGE := env_var_or_default("INSTALLER_BUILDER_IMAGE", "ghcr.io/jasonn3/build-container-installer:v1.5.0") +export TITANOBOA_REPOSITORY := env_var_or_default("TITANOBOA_REPOSITORY", "https://github.com/Zeglius/titanoboa.git") +export TITANOBOA_REVISION := env_var_or_default("TITANOBOA_REVISION", "7737f4748458252ac827dca14b3d6dd09298472a") export FLATPAK_REMOTE_URL := env_var_or_default("FLATPAK_REMOTE_URL", "") export HOMEBREW_BOTTLE_DOMAIN := env_var_or_default("HOMEBREW_BOTTLE_DOMAIN", "") export HOMEBREW_API_DOMAIN := env_var_or_default("HOMEBREW_API_DOMAIN", "") @@ -38,7 +39,7 @@ alias run-live-installer := run-installer-iso default: @just --list -# Build a Bazzite-style installer ISO +# Build the same Titanoboa-based installer ISO as CI build-installer-iso $target_image=("localhost/" + image_name) $tag=default_tag: @IMAGE_NAME="{{ target_image }}" IMAGE_TAG="{{ tag }}" ./just_scripts/build-installer-iso.sh @@ -64,6 +64,7 @@ just build-installer-iso just run-installer-iso ``` -This requires privileged Podman, a rootful Podman API socket, and roughly -70 GiB of free space. The installer ISO workflow consumes the published -`latest` AeroCore image and uploads the ISO as a GitHub Actions artifact. +This requires privileged Podman, a rootful Podman API socket, `git`, and +roughly 70 GiB of free space. Local ISO generation uses the same pinned +Titanoboa builder as the installer workflow, and writes the ISO plus its +SHA-256 checksum under `output/`. diff --git a/installer/Containerfile b/installer/Containerfile index cf20e4f..32b327e 100644 --- a/installer/Containerfile +++ b/installer/Containerfile @@ -6,6 +6,8 @@ ARG IMAGE_VENDOR=AeroCore-IO ARG IMAGE_NAME=aerocore-os ARG INSTALL_IMAGE_PAYLOAD=ghcr.io/${IMAGE_VENDOR}/${IMAGE_NAME}:stable ARG INSTALL_IMAGE_PAYLOAD_ARCHIVE=/install-payload.oci +ARG INSTALL_IMAGE_TAG=latest +ARG OSTREE_IMAGE_REF=ostree-image-signed:docker://ghcr.io/${IMAGE_VENDOR}/${IMAGE_NAME} FROM ${BASE_IMAGE} @@ -14,10 +16,14 @@ ARG IMAGE_VENDOR ARG IMAGE_NAME ARG INSTALL_IMAGE_PAYLOAD ARG INSTALL_IMAGE_PAYLOAD_ARCHIVE +ARG INSTALL_IMAGE_TAG +ARG OSTREE_IMAGE_REF RUN --mount=type=bind,source=.,target=/src \ --mount=type=bind,source=output/install-payload.oci,target=/install-payload.oci,ro \ env BASE_IMAGE="${BASE_IMAGE}" \ INSTALL_IMAGE_PAYLOAD="${INSTALL_IMAGE_PAYLOAD}" \ INSTALL_IMAGE_PAYLOAD_ARCHIVE="${INSTALL_IMAGE_PAYLOAD_ARCHIVE}" \ + INSTALL_IMAGE_TAG="${INSTALL_IMAGE_TAG}" \ + OSTREE_IMAGE_REF="${OSTREE_IMAGE_REF}" \ /src/installer/build.sh diff --git a/installer/build.sh b/installer/build.sh index 0cc1b8a..4535113 100755 --- a/installer/build.sh +++ b/installer/build.sh @@ -6,6 +6,9 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BASE_IMAGE=${BASE_IMAGE:?} INSTALL_IMAGE_PAYLOAD=${INSTALL_IMAGE_PAYLOAD:?} INSTALL_IMAGE_PAYLOAD_ARCHIVE=${INSTALL_IMAGE_PAYLOAD_ARCHIVE:-} +INSTALL_IMAGE_TAG=${INSTALL_IMAGE_TAG:?} +OSTREE_IMAGE_REF=${OSTREE_IMAGE_REF:?} +ostree_origin_ref="${OSTREE_IMAGE_REF}:${INSTALL_IMAGE_TAG}" # The installer is a live KDE environment. Keep the installed system payload # separate: it is the AeroCore image, while BASE_IMAGE provides the live UI. @@ -28,6 +31,11 @@ dnf --setopt=excludepkgs= reinstall -y \ grub2-efi-x64 \ grub2-efi-x64-cdboot +# Fedora 43's anaconda-webui bundle embeds a Cockpit password helper from +# before cockpit-project/cockpit@900c13f. Restore that upstream fix so a weak +# pwscore result is returned as score 0 instead of rejecting the WebUI promise. +"${SCRIPT_DIR}/patch-anaconda-webui.sh" + # Apply the same AeroCore overlay to the live session where possible. if [[ -d /src/system_files ]]; then cp -a /src/system_files/. / @@ -124,6 +132,14 @@ df -h /var/tmp \${target_tmp} ostreecontainer --url=${INSTALL_IMAGE_PAYLOAD} --transport=containers-storage --no-signature-verification +# The local containers-storage deployment is intentionally unverified because +# the payload is loaded from an OCI archive. Point future bootc updates at the +# signed registry reference configured for the image instead. +%post --erroronfail --log=/tmp/aerocore-origin.log +set -eux +sed -i 's|container-image-reference=.*|container-image-reference=${ostree_origin_ref}|' /ostree/deploy/default/deploy/*.origin +%end + %post --nochroot --log=/tmp/aerocore-hostname.log set -eux target_root= diff --git a/installer/patch-anaconda-webui.sh b/installer/patch-anaconda-webui.sh new file mode 100755 index 0000000..2f61516 --- /dev/null +++ b/installer/patch-anaconda-webui.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash + +set -euo pipefail + +bundle="${1:-/usr/share/cockpit/anaconda-webui/index.js.gz}" +broken='n===0?r(new Error(X2("Password is too weak"))):o(' +fixed='n===0&&!e?r(new Error(X2("Password is too weak"))):o(' +fixed_sed='n===0\&\&!e?r(new Error(X2("Password is too weak"))):o(' + +if [[ ! -f "${bundle}" ]]; then + echo "Anaconda WebUI bundle not found: ${bundle}" >&2 + exit 1 +fi + +webui_js="$(mktemp)" +patched_js="$(mktemp)" +patched_bundle="$(mktemp)" +trap 'rm -f "${webui_js}" "${patched_js}" "${patched_bundle}"' EXIT + +gzip -dc "${bundle}" > "${webui_js}" + +fixed_count="$( (grep -oF "${fixed}" "${webui_js}" || true) | wc -l | tr -d ' ')" +if [[ "${fixed_count}" == "1" ]]; then + echo "Anaconda WebUI weak-password handling is already fixed" + exit 0 +fi + +broken_count="$( (grep -oF "${broken}" "${webui_js}" || true) | wc -l | tr -d ' ')" +if [[ "${broken_count}" != "1" ]]; then + echo "Unexpected Anaconda WebUI password helper; refusing an unsafe patch" >&2 + echo "Expected one weak-password rejection, found ${broken_count}" >&2 + exit 1 +fi + +sed "s|${broken}|${fixed_sed}|" "${webui_js}" > "${patched_js}" + +fixed_count="$( (grep -oF "${fixed}" "${patched_js}" || true) | wc -l | tr -d ' ')" +if [[ "${fixed_count}" != "1" ]] || grep -Fq "${broken}" "${patched_js}"; then + echo "Failed to patch Anaconda WebUI weak-password handling" >&2 + exit 1 +fi + +gzip -n -9 -c "${patched_js}" > "${patched_bundle}" +chmod 0644 "${patched_bundle}" +mv -f "${patched_bundle}" "${bundle}" + +echo "Patched Anaconda WebUI weak-password handling" diff --git a/installer/system_files/overrides/etc/anaconda/conf.d/99-aerocore-password.conf b/installer/system_files/overrides/etc/anaconda/conf.d/99-aerocore-password.conf new file mode 100644 index 0000000..3105934 --- /dev/null +++ b/installer/system_files/overrides/etc/anaconda/conf.d/99-aerocore-password.conf @@ -0,0 +1,5 @@ +[User Interface] +password_policies = + root (quality 1, length 6, strict) + user (quality 1, length 6, empty, strict) + luks (quality 1, length 6) diff --git a/just_scripts/build-installer-iso.sh b/just_scripts/build-installer-iso.sh index abd668f..0aacf43 100755 --- a/just_scripts/build-installer-iso.sh +++ b/just_scripts/build-installer-iso.sh @@ -8,7 +8,8 @@ cd "${project_root}" image_name="${IMAGE_NAME:-localhost/$(just image_name)}" image_tag="${IMAGE_TAG:-$(just generate-default-tag)}" live_base_image="${LIVE_BASE_IMAGE:-quay.io/fedora/fedora-kinoite:43}" -installer_builder_image="${INSTALLER_BUILDER_IMAGE:-ghcr.io/jasonn3/build-container-installer:v1.5.0}" +titanoboa_repository="${TITANOBOA_REPOSITORY:-https://github.com/Zeglius/titanoboa.git}" +titanoboa_revision="${TITANOBOA_REVISION:-7737f4748458252ac827dca14b3d6dd09298472a}" source_image="${image_name}:${image_tag}" # Keep the payload in local container storage, but use the public update # reference as its local tag so Anaconda writes that reference as the boot @@ -90,6 +91,8 @@ ensure_rootful_image --build-arg "BASE_IMAGE=${live_base_image}" \ --build-arg "INSTALL_IMAGE_PAYLOAD=${payload}" \ --build-arg "INSTALL_IMAGE_PAYLOAD_ARCHIVE=/install-payload.oci" \ + --build-arg "INSTALL_IMAGE_TAG=${image_tag}" \ + --build-arg "OSTREE_IMAGE_REF=${ostree_image_ref}" \ --tag "${payload}" \ --file installer/Containerfile \ . @@ -100,24 +103,37 @@ else sudo rm -f "${iso}" "${checksum}" fi -"${podman_cmd[@]}" run --rm --privileged \ - --volume "${api_socket}:/var/run/docker.sock" \ - --volume /var/lib/containers/storage:/var/lib/containers/storage \ - --volume "${project_root}/output:/build-container-installer/build" \ - --volume "${project_root}/installer/lorax_templates:/additional_lorax_templates:ro" \ - "${installer_builder_image}" \ - ADDITIONAL_TEMPLATES="/additional_lorax_templates/remove_root_password_prompt.tmpl /additional_lorax_templates/set_default_user.tmpl" \ - ARCH="x86_64" \ - ENABLE_CACHE_DNF="false" \ - ENABLE_CACHE_SKOPEO="false" \ - ENABLE_FLATPAK_DEPENDENCIES="false" \ - IMAGE_NAME="aerocore-os-installer" \ - IMAGE_REPO="localhost" \ - IMAGE_SRC="containers-storage:${payload}" \ - IMAGE_TAG="latest" \ - ISO_NAME="build/aerocore-os-${image_tag}-amd64.iso" \ - VARIANT="Kinoite" \ - VERSION="${FEDORA_VERSION:-44}" +tmp_titanoboa="$(mktemp -d -t aerocore-titanoboa.XXXXXXXXXX)" +trap 'rm -rf "${tmp_titanoboa}"' EXIT + +git clone --quiet --filter=blob:none --no-checkout \ + "${titanoboa_repository}" "${tmp_titanoboa}" +git -C "${tmp_titanoboa}" fetch --quiet origin "${titanoboa_revision}" +git -C "${tmp_titanoboa}" checkout --quiet "${titanoboa_revision}" + +# Keep this invocation aligned with the pinned revision's GitHub Action. +# This revision exposes main.sh rather than a Justfile. +( + cd "${tmp_titanoboa}" + env \ + CI=1 \ + TITANOBOA_CTR_IMAGE="${payload}" \ + TITANOBOA_OUTPUT_DIR="${tmp_titanoboa}/output" \ + "${tmp_titanoboa}/main.sh" +) + +titanoboa_iso="$(find "${tmp_titanoboa}/output" -maxdepth 1 -type f -name '*.iso' -print -quit)" +if [[ ! -f "${titanoboa_iso}" ]]; then + echo "Titanoboa did not produce an ISO in ${tmp_titanoboa}/output" >&2 + exit 1 +fi +if [[ "${EUID}" -eq 0 ]]; then + rm -f "${iso}" "${checksum}" + mv "${titanoboa_iso}" "${iso}" +else + sudo rm -f "${iso}" "${checksum}" + sudo mv "${titanoboa_iso}" "${iso}" +fi if [[ -f "${iso}" ]]; then if [[ "${EUID}" -ne 0 ]]; then |
