From 6788f04d06fd19edfaeb3fd3f178bf8b7869dba1 Mon Sep 17 00:00:00 2001
From: subramanianaug@gmail.com
Date: Fri, 26 Jun 2026 14:26:35 -0700
Subject: [PATCH 1/1] builder: add Ubuntu 24.04 cloud-image template builder

Ubuntu 24.04 (noble) no longer publishes the legacy debian-installer
netboot tree, so make-template.ml cannot build noble templates via
preseed + virt-install.

Add make-ubuntu-cloud-template.sh to build a virt-builder template from
the official noble cloud image, detect-cloudimg-expand.sh for expand=
partition detection, tests, and README documentation.  Direct
make-template ubuntu 24.04 to the cloud script with a clear error.

Signed-off-by: subramanianaug@gmail.com

diff --git a/builder/templates/Makefile.am b/builder/templates/Makefile.am
index b4d06357..27ef20ec 100644
--- a/builder/templates/Makefile.am
+++ b/builder/templates/Makefile.am
@@ -24,7 +24,13 @@ EXTRA_DIST = \
 	*.ks \
 	*.virt-install-cmd \
 	debian.preseed \
+	detect-cloudimg-expand.sh \
 	make-template.ml \
+	make-ubuntu-cloud-template.sh \
+	README-ubuntu-cloud.md \
+	test-make-template-noble.sh \
+	test-make-ubuntu-cloud-template-integration.sh \
+	test-make-ubuntu-cloud-template.sh \
 	ubuntu.preseed \
 	validate.sh
 
@@ -42,7 +48,15 @@ index: $(index_fragments)
 TESTS_ENVIRONMENT = \
 	top_srcdir="$(top_srcdir)" \
 	$(top_builddir)/run --test
-TESTS = validate.sh
+TESTS = \
+	validate.sh \
+	test-make-ubuntu-cloud-template.sh \
+	test-make-template-noble.sh
+
+SLOW_TESTS = test-make-ubuntu-cloud-template-integration.sh
+
+check-slow:
+	$(MAKE) check TESTS="$(SLOW_TESTS)" SLOW=1
 
 check-valgrind:
 	$(MAKE) VG="@VG@" check
diff --git a/builder/templates/README-ubuntu-cloud.md b/builder/templates/README-ubuntu-cloud.md
new file mode 100644
index 00000000..15ec186f
--- /dev/null
+++ b/builder/templates/README-ubuntu-cloud.md
@@ -0,0 +1,52 @@
+# Ubuntu cloud-image virt-builder templates
+
+Ubuntu **24.04 (noble)** and later no longer publish the legacy
+debian-installer netboot tree:
+
+```text
+http://archive.ubuntu.com/ubuntu/dists/noble/main/installer-amd64/  → 404
+```
+
+`make-template.ml` depends on that netboot path (preseed + `virt-install
+--location=.../installer-amd64`), so it cannot build noble templates.
+
+## Building ubuntu-24.04
+
+From this directory, on a Linux host with libguestfs-tools installed:
+
+```bash
+./make-ubuntu-cloud-template.sh 24.04 noble
+```
+
+This downloads the official noble server cloud image, customizes it to
+match existing virt-builder Ubuntu templates (root password `root`,
+cloud-init disabled, `builder` user removed), converts to raw, compresses
+with xz, and writes `ubuntu-24.04.index-fragment`.
+
+Maintainers then concatenate the fragment into `index`, re-sign
+`index.asc`, and upload the `.xz` next to the index on libguestfs.org.
+
+## Validation notes
+
+On noble cloud images the root filesystem is usually `/dev/sda1` with
+label `cloudimg-rootfs`.  `detect-cloudimg-expand.sh` implements the
+expand-partition detection used by the builder script.
+
+Run the fast tests from a build tree:
+
+```bash
+make -C builder/templates check
+```
+
+The integration test downloads the full cloud image (~600M) and is run
+only as a slow test:
+
+```bash
+make -C builder/templates check-slow
+```
+
+## ubuntu-22.04
+
+Jammy still has installer-amd64; use `make-template.ml` if preseed install
+works on your host. The cloud script also accepts `22.04 jammy` as a
+fallback.
diff --git a/builder/templates/detect-cloudimg-expand.sh b/builder/templates/detect-cloudimg-expand.sh
new file mode 100644
index 00000000..ce7544b4
--- /dev/null
+++ b/builder/templates/detect-cloudimg-expand.sh
@@ -0,0 +1,67 @@
+#!/bin/bash -
+# libguestfs virt-builder cloud-image helpers
+# Copyright (C) 2026 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# detect_cloudimg_expand IMAGE
+#
+# Print the partition virt-builder should use for expand= on a raw
+# Ubuntu cloud image.  Returns 1 if no suitable partition is found.
+detect_cloudimg_expand ()
+{
+  local image="$1"
+  local expand
+
+  expand="$(
+    virt-filesystems --long -a "$image" --parts --blkdevs --filesystems 2>/dev/null |
+      awk '$2 == "filesystem" && $4 == "cloudimg-rootfs" { print $1; exit }'
+  )"
+  if [ -n "$expand" ]; then
+    echo "$expand"
+    return 0
+  fi
+
+  expand="$(
+    virt-filesystems --long -a "$image" --parts --blkdevs --filesystems 2>/dev/null |
+      awk '$2 == "filesystem" && $3 == "ext4" && $1 ~ /^\/dev\/sd[a-z][0-9]+$/ { print $1; exit }'
+  )"
+  if [ -n "$expand" ]; then
+    echo "$expand"
+    return 0
+  fi
+
+  expand="$(
+    virt-filesystems --long -a "$image" --parts --blkdevs 2>/dev/null |
+      awk '$2 == "device" && $1 ~ /^\/dev\/sda[0-9]+$/ { print $1 }' |
+      tail -1
+  )"
+  if [ -n "$expand" ]; then
+    echo "$expand"
+    return 0
+  fi
+
+  expand="$(
+    virt-filesystems --long -a "$image" --parts --blkdevs 2>/dev/null |
+      awk '$2 == "device" && $1 ~ /^\/dev\/vda[0-9]+$/ { print $1 }' |
+      tail -1
+  )"
+  if [ -n "$expand" ]; then
+    echo "$expand"
+    return 0
+  fi
+
+  return 1
+}
diff --git a/builder/templates/make-template.ml b/builder/templates/make-template.ml
index c3df7ff2..9551d5f9 100755
--- a/builder/templates/make-template.ml
+++ b/builder/templates/make-template.ml
@@ -370,6 +370,11 @@ and os_of_string os ver =
   | "ubuntu", "18.04" -> Ubuntu (ver, "bionic")
   | "ubuntu", "20.04" -> Ubuntu (ver, "focal")
   | "ubuntu", "22.04" -> Ubuntu (ver, "jammy")
+  | "ubuntu", "24.04" ->
+     eprintf "%s: Ubuntu 24.04 (noble) has no debian-installer netboot.\n"
+             prog;
+     eprintf "Use builder/templates/make-ubuntu-cloud-template.sh instead.\n";
+     exit 1
   | "fedora", ver -> Fedora (int_of_string ver)
   | "freebsd", ver -> let maj, min = parse_major_minor ver in FreeBSD (maj, min)
   | "windows", ver -> parse_windows_version ver
diff --git a/builder/templates/make-ubuntu-cloud-template.sh b/builder/templates/make-ubuntu-cloud-template.sh
new file mode 100755
index 00000000..52d8c288
--- /dev/null
+++ b/builder/templates/make-ubuntu-cloud-template.sh
@@ -0,0 +1,165 @@
+#!/bin/bash -
+# Build a virt-builder template from an official Ubuntu cloud image.
+#
+# Ubuntu 24.04 (noble) and later dropped the legacy debian-installer
+# netboot under dists/<codename>/main/installer-amd64, so make-template.ml
+# cannot install them via preseed + virt-install.  This script is the
+# supported replacement for those releases.
+#
+# Usage:
+#   ./make-ubuntu-cloud-template.sh [version] [codename]
+# Example:
+#   ./make-ubuntu-cloud-template.sh 24.04 noble
+#
+# Outputs (in the current directory):
+#   ubuntu-<ver>.img       raw disk image
+#   ubuntu-<ver>.xz        compressed template
+#   ubuntu-<ver>.index-fragment
+#
+# Copyright (C) 2026 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+export LANG=C
+set -euo pipefail
+
+VER="${1:-24.04}"
+CODENAME="${2:-noble}"
+ARCH="${ARCH:-x86_64}"
+DISK_GB="${DISK_GB:-6}"
+WORKDIR="${WORKDIR:-.}"
+
+if [ "$ARCH" != x86_64 ]; then
+  echo "$0: only x86_64 is supported for now" >&2
+  exit 1
+fi
+
+case "$VER:$CODENAME" in
+  22.04:jammy|24.04:noble) ;;
+  *)
+    echo "$0: unsupported version/codename pair ($VER, $CODENAME)" >&2
+    exit 1
+    ;;
+esac
+
+TEMPLATE="ubuntu-${VER}"
+CLOUD_URL="https://cloud-images.ubuntu.com/${CODENAME}/current/${CODENAME}-server-cloudimg-amd64.img"
+QCOW="${WORKDIR}/${TEMPLATE}.qcow2"
+RAW="${WORKDIR}/${TEMPLATE}.img"
+XZ="${WORKDIR}/${TEMPLATE}.xz"
+FRAG="${WORKDIR}/${TEMPLATE}.index-fragment"
+
+cd "$WORKDIR"
+
+need() {
+  command -v "$1" >/dev/null 2>&1 || {
+    echo "$0: required command not found: $1" >&2
+    exit 1
+  }
+}
+
+for cmd in wget qemu-img virt-customize virt-sysprep virt-filesystems xz sha512sum; do
+  need "$cmd"
+done
+
+echo "Downloading ${CLOUD_URL} ..."
+wget -q --show-progress -O "$QCOW" "$CLOUD_URL"
+
+echo "Resizing cloud image to ${DISK_GB}G ..."
+qemu-img resize "$QCOW" "${DISK_GB}G"
+
+echo "Customizing cloud image ..."
+virt-customize -a "$QCOW" \
+  --root-password password:root \
+  --run-command "userdel -f builder 2>/dev/null || true" \
+  --run-command "groupdel builder 2>/dev/null || true" \
+  --run-command "rm -f /etc/ssh/ssh_host_*" \
+  --run-command "ln -sf /dev/null /etc/systemd/system/cloud-init.service" \
+  --run-command "ln -sf /dev/null /etc/systemd/system/cloud-init-local.service" \
+  --run-command "ln -sf /dev/null /etc/systemd/system/cloud-config.service" \
+  --run-command "ln -sf /dev/null /etc/systemd/system/cloud-final.service" \
+  --run-command "rm -f /etc/apt/apt.conf; touch /etc/apt/apt.conf"
+
+echo "Sysprepping template ..."
+virt-sysprep -a "$QCOW" \
+  --operations defaults,-ssh-hostkeys,-ssh-userdir,-udev-persistent-net
+
+echo "Converting to raw ..."
+qemu-img convert -p -f qcow2 -O raw "$QCOW" "$RAW"
+
+echo "Detecting root partition for virt-builder expand= ..."
+. "$(dirname "$0")/detect-cloudimg-expand.sh"
+EXPAND="$(detect_cloudimg_expand "$RAW")" || {
+  echo "$0: could not detect expand partition; inspect with virt-filesystems" >&2
+  exit 1
+}
+echo "expand=${EXPAND}"
+
+echo "Compressing template ..."
+xz -k -f "$RAW"
+mv -f "${RAW}.xz" "$XZ"
+
+COMPRESSED_SIZE=$(stat -c%s "$XZ" 2>/dev/null || stat -f%z "$XZ")
+VIRTUAL_SIZE=$(qemu-img info "$RAW" | awk -F': ' '/^disk size/ {print $2}')
+# Normalize to bytes for index (qemu-img may print KiB/MiB/GiB).
+case "$VIRTUAL_SIZE" in
+  *GiB) SIZE_BYTES=$(echo "$VIRTUAL_SIZE" | awk '{printf "%.0f", $1*1024*1024*1024}') ;;
+  *MiB) SIZE_BYTES=$(echo "$VIRTUAL_SIZE" | awk '{printf "%.0f", $1*1024*1024}') ;;
+  *) SIZE_BYTES=$(qemu-img info "$RAW" --output=json | python3 -c 'import json,sys; print(json.load(sys.stdin)["virtual-size"])') ;;
+esac
+
+CHECKSUM=$(sha512sum "$XZ" | awk '{print $1}')
+
+cat > "$FRAG" <<EOF
+[${TEMPLATE}]
+name=Ubuntu ${VER} (${CODENAME})
+osinfo=ubuntu${VER}
+arch=x86_64
+file=${TEMPLATE}.xz
+checksum[sha512]=${CHECKSUM}
+format=raw
+size=${SIZE_BYTES}
+compressed_size=${COMPRESSED_SIZE}
+expand=${EXPAND}
+notes=Ubuntu ${VER} (${CODENAME})
+ 
+ This is a minimal Ubuntu install prepared from the official cloud image.
+ ${CLOUD_URL}
+
+ Ubuntu ${VER} no longer publishes the legacy debian-installer netboot used
+ by builder/templates/make-template.ml, so this template is built with
+ builder/templates/make-ubuntu-cloud-template.sh instead.
+
+ This image does not contain SSH host keys.  To regenerate them use:
+
+     --firstboot-command "dpkg-reconfigure openssh-server"
+
+ This template was contributed for virt-builder / guestfs-tools.
+
+EOF
+
+if command -v virt-index-validate >/dev/null 2>&1; then
+  virt-index-validate "$FRAG"
+fi
+
+echo "Created:"
+echo "  $RAW"
+echo "  $XZ"
+echo "  $FRAG"
+echo
+echo "Next steps for maintainers:"
+echo "  1. Validate: virt-builder --source file://$WORKDIR --list"
+echo "  2. Concatenate ${FRAG} into the builder index"
+echo "  3. Upload ${XZ} next to index.asc on libguestfs.org"
diff --git a/builder/templates/test-make-template-noble.sh b/builder/templates/test-make-template-noble.sh
new file mode 100644
index 00000000..8b8474bc
--- /dev/null
+++ b/builder/templates/test-make-template-noble.sh
@@ -0,0 +1,39 @@
+#!/bin/bash -
+# libguestfs virt-builder make-template noble redirect test
+# Copyright (C) 2026 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+export LANG=C
+set -e
+
+if [ ! -x "$top_builddir/run" ]; then
+  echo "$0: test skipped because $top_builddir/run is not built yet"
+  exit 77
+fi
+
+set +e
+out="$("$top_builddir/run" ./make-template.ml ubuntu 24.04 2>&1)"
+rc=$?
+set -e
+
+if [ "$rc" -ne 1 ]; then
+  echo "$0: expected make-template.ml ubuntu 24.04 to exit 1, got $rc" >&2
+  echo "$out" >&2
+  exit 1
+fi
+
+echo "$out" | grep -q 'debian-installer netboot'
+echo "$out" | grep -q 'make-ubuntu-cloud-template.sh'
diff --git a/builder/templates/test-make-ubuntu-cloud-template-integration.sh b/builder/templates/test-make-ubuntu-cloud-template-integration.sh
new file mode 100644
index 00000000..ba2e0afd
--- /dev/null
+++ b/builder/templates/test-make-ubuntu-cloud-template-integration.sh
@@ -0,0 +1,52 @@
+#!/bin/bash -
+# libguestfs virt-builder ubuntu cloud template integration test
+# Copyright (C) 2026 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+export LANG=C
+set -e
+
+if [ -z "${SLOW}" ]; then
+  echo "$0: test skipped because SLOW is not set (downloads ~600M cloud image)"
+  exit 77
+fi
+
+for cmd in wget qemu-img virt-customize virt-sysprep virt-filesystems xz sha512sum virt-index-validate; do
+  if ! command -v "$cmd" >/dev/null 2>&1; then
+    echo "$0: test skipped because $cmd is not available"
+    exit 77
+  fi
+done
+
+work="$PWD/cloud-template-integration-$$"
+mkdir -p "$work"
+trap 'rm -rf "$work"' EXIT
+
+export WORKDIR="$work"
+export DISK_GB=6
+"$srcdir/make-ubuntu-cloud-template.sh" 24.04 noble
+
+test -f "$work/ubuntu-24.04.img"
+test -f "$work/ubuntu-24.04.xz"
+test -f "$work/ubuntu-24.04.index-fragment"
+
+$VG virt-index-validate "$work/ubuntu-24.04.index-fragment"
+
+expand="$(grep '^expand=' "$work/ubuntu-24.04.index-fragment" | cut -d= -f2)"
+test -n "$expand"
+
+$VG virt-ls -a "$work/ubuntu-24.04.img" /
+$VG virt-cat -a "$work/ubuntu-24.04.img" /etc/os-release | grep -q 'VERSION_ID="24.04"'
diff --git a/builder/templates/test-make-ubuntu-cloud-template.sh b/builder/templates/test-make-ubuntu-cloud-template.sh
new file mode 100644
index 00000000..31a0ed98
--- /dev/null
+++ b/builder/templates/test-make-ubuntu-cloud-template.sh
@@ -0,0 +1,77 @@
+#!/bin/bash -
+# libguestfs virt-builder ubuntu cloud template tests
+# Copyright (C) 2026 Red Hat Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+export LANG=C
+set -e
+
+script="$srcdir/make-ubuntu-cloud-template.sh"
+helper="$srcdir/detect-cloudimg-expand.sh"
+
+bash -n "$script"
+bash -n "$helper"
+
+# Unsupported version/codename pairs must fail before any download.
+if "$script" 26.04 noble >/dev/null 2>&1; then
+  echo "$0: expected unsupported version to fail" >&2
+  exit 1
+fi
+
+if "$script" 24.04 jammy >/dev/null 2>&1; then
+  echo "$0: expected mismatched codename to fail" >&2
+  exit 1
+fi
+
+if ARCH=aarch64 "$script" 24.04 noble >/dev/null 2>&1; then
+  echo "$0: expected unsupported architecture to fail" >&2
+  exit 1
+fi
+
+# Expand detection helper: mock virt-filesystems output for cloudimg-rootfs.
+mock_bin="$PWD/mock-bin-$$"
+mkdir -p "$mock_bin"
+cat > "$mock_bin/virt-filesystems" <<'EOF'
+#!/bin/bash -
+case "$*" in
+  *cloudimg-rootfs*)
+    echo "/dev/sda1 filesystem ext4 cloudimg-rootfs"
+    exit 0
+    ;;
+esac
+echo "$0: unexpected arguments: $*" >&2
+exit 1
+EOF
+chmod +x "$mock_bin/virt-filesystems"
+trap 'rm -rf "$mock_bin"' EXIT
+
+PATH="$mock_bin:$PATH"
+. "$helper"
+expand="$(detect_cloudimg_expand /dev/null)"
+test "$expand" = /dev/sda1
+
+# Cloud image URL used by the builder must exist (HEAD only, no download).
+url="https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img"
+if ! wget -S --spider "$url" 2>&1 | grep -q '200 OK'; then
+  echo "$0: cloud image URL is not reachable: $url" >&2
+  exit 1
+fi
+
+# Legacy debian-installer netboot used by make-template.ml is gone on noble.
+if wget -S --spider "http://archive.ubuntu.com/ubuntu/dists/noble/main/installer-amd64/" 2>&1 | grep -q '200 OK'; then
+  echo "$0: expected noble installer-amd64 to be unavailable" >&2
+  exit 1
+fi
