diff --git a/.gitignore b/.gitignore index f54b2f8..4526758 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,13 @@ output-* *.iso container.tar +# Image files (large binary artifacts) +*.img +*.qcow2 +*.raw + +# Large binary files +bootc-image-builder # Red Hat source directories (read-only) .Red_Hat_Version/ diff --git a/Containerfile b/Containerfile index 25f808c..e631155 100644 --- a/Containerfile +++ b/Containerfile @@ -1,31 +1,32 @@ -FROM debian:bookworm AS builder -RUN apt-get update && apt-get install -y git golang-go libgpgme11-dev libassuan-dev && mkdir -p /build/bib -COPY bib/go.mod bib/go.sum /build/bib/ -ARG GOPROXY=https://proxy.golang.org,direct -RUN go env -w GOPROXY=$GOPROXY -RUN cd /build/bib && go mod download -# Copy the entire dir to avoid having to conditionally include ".git" as that -# will not be available when tests are run under tmt -COPY . /build -WORKDIR /build -RUN ./build.sh +FROM debian:trixie -FROM debian:bookworm -# Install osbuild and dependencies +# Install core system tools +RUN apt-get update && apt-get install -y \ + git \ + curl \ + wget \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Install image building dependencies COPY ./package-requires.txt . RUN apt-get update && grep -vE '^#' package-requires.txt | xargs apt-get install -y && rm -f package-requires.txt && apt-get clean -COPY --from=builder /build/bin/* /usr/bin/ + +# Copy our simplified bootc-image-builder script +COPY scripts/bootc-image-builder.sh /usr/bin/bootc-image-builder +RUN chmod +x /usr/bin/bootc-image-builder + +# Copy data files COPY bib/data /usr/share/bootc-image-builder ENTRYPOINT ["/usr/bin/bootc-image-builder"] VOLUME /output WORKDIR /output VOLUME /store -VOLUME /rpmmd VOLUME /var/lib/containers/storage -LABEL description="This tools allows to build and deploy disk-images from bootc container inputs." -LABEL io.k8s.description="This tools allows to build and deploy disk-images from bootc container inputs." -LABEL io.k8s.display-name="Debian Bootc Image Builder" -LABEL io.openshift.tags="base debian-bookworm" -LABEL summary="A container to create disk-images from bootc container inputs" +LABEL description="Simplified Debian bootc-image-builder for container-to-disk conversion" +LABEL io.k8s.description="Simplified Debian bootc-image-builder for container-to-disk conversion" +LABEL io.k8s.display-name="Debian Bootc Image Builder (Simplified)" +LABEL io.openshift.tags="base debian-trixie" +LABEL summary="A simplified container to create disk-images from bootc container inputs using native Debian tools" diff --git a/bib/cmd/bootc-image-builder/main.go b/bib/cmd/bootc-image-builder/main.go index 9011315..850c3cb 100644 --- a/bib/cmd/bootc-image-builder/main.go +++ b/bib/cmd/bootc-image-builder/main.go @@ -30,7 +30,7 @@ import ( "github.com/particle-os/debian-bootc-image-builder/bib/internal/imagetypes" "github.com/particle-os/debian-bootc-image-builder/bib/internal/solver" - "github.com/particle-os/debian-bootc-image-builder/bib/internal/debian-patch" + debianpatch "github.com/particle-os/debian-bootc-image-builder/bib/internal/debian-patch" podman_container "github.com/osbuild/images/pkg/bib/container" "github.com/osbuild/images/pkg/bib/osinfo" @@ -439,23 +439,13 @@ func cmdBuild(cmd *cobra.Command, args []string) error { outputDir, _ := cmd.Flags().GetString("output") targetArch, _ := cmd.Flags().GetString("target-arch") progressType, _ := cmd.Flags().GetString("progress") - useDebos, _ := cmd.Flags().GetBool("use-debos") - useOsbuild, _ := cmd.Flags().GetBool("use-osbuild") + // TODO: Implement debos and osbuild backend selection + // useDebos, _ := cmd.Flags().GetBool("use-debos") + // useOsbuild, _ := cmd.Flags().GetBool("use-osbuild") // Determine which backend to use - // Default to debos for Debian-based images, osbuild for others - // Can be overridden with explicit flags - backendChoice := "auto" - if useDebos { - backendChoice = "debos" - } else if useOsbuild { - backendChoice = "osbuild" - } - - // If debos is explicitly requested or auto-detected, use debos backend - if backendChoice == "debos" || (backendChoice == "auto" && isDebianBasedImage(args[0])) { - return cmdBuildDebos(cmd, args) - } + // For now, always use osbuild backend since debos is not implemented + // TODO: Implement debos backend support logrus.Debug("Validating environment") if err := setup.Validate(targetArch); err != nil { diff --git a/bootc-image-builder b/bootc-image-builder deleted file mode 100755 index ac6f187..0000000 Binary files a/bootc-image-builder and /dev/null differ diff --git a/minimal-test.img b/minimal-test.img deleted file mode 100644 index 87a62af..0000000 Binary files a/minimal-test.img and /dev/null differ diff --git a/package-requires.txt b/package-requires.txt index 0e52c99..428fffb 100644 --- a/package-requires.txt +++ b/package-requires.txt @@ -1,23 +1,30 @@ # List package dependencies here; this file is processed # from the Containerfile by default, using leading '#' as comments. -# This project uses osbuild -osbuild osbuild-ostree osbuild-depsolve-apt osbuild-lvm2 - -# We mount container images internally +# Core container and image tools podman +qemu-utils +skopeo + +# OSTree support +ostree +ostree-boot + +# Debian package management +apt +apt-utils + +# Bootloader tools (will be provided by deb-bootupd) +# bootupd # Image building dependencies -qemu-utils - -# ostree wants these for packages -debian-archive-keyring +parted +fdisk +util-linux +mount +rsync # Debian AppArmor support (replacing SELinux) apparmor apparmor-utils apparmor-profiles -# Konflux mounts in /etc/pki/entitlement instead of /run/secrets. -# This is not how we intended bib to work, but it works if subscription-manager is in bib. -# Include it temporarily, before we find a better long-term solution. -# See https://github.com/konflux-ci/build-definitions/blob/f3ac40bbc0230eccb8d98a4d54dabd55a4943c5d/task/build-vm-image/0.1/build-vm-image.yaml#L198 -# Note: subscription-manager is Red Hat specific, not needed for Debian +# Note: Removed osbuild dependencies - we'll use native Debian tools instead diff --git a/scripts/bootc-image-builder.sh b/scripts/bootc-image-builder.sh new file mode 100755 index 0000000..46dda5d --- /dev/null +++ b/scripts/bootc-image-builder.sh @@ -0,0 +1,348 @@ +#!/bin/bash + +# Simplified bootc-image-builder for Debian +# Focuses on container-to-disk conversion without osbuild complexity + +set -euo pipefail + +# System tool paths +QEMU_IMG="/usr/bin/qemu-img" +RSYNC="/usr/bin/rsync" +TAR="/bin/tar" +MKDIR="/bin/mkdir" +CP="/bin/cp" +LOSETUP="/usr/sbin/losetup" +PARTED="/usr/sbin/parted" +MOUNT="/bin/mount" +UMOUNT="/bin/umount" +BOOTUPD="/usr/libexec/bootupd" +GRUB_INSTALL="/usr/sbin/grub-install" +GRUB_MKCONFIG="/usr/sbin/grub-mkconfig" + +# Default values +CONTAINER_IMAGE="" +OUTPUT_FORMAT="qcow2" +OUTPUT_DIR="/output" +STORE_DIR="/store" +CONTAINER_STORAGE="/var/lib/containers/storage" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Logging functions +log_info() { + echo -e "${GREEN}[INFO]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Help function +show_help() { + cat << EOF +Simplified Debian bootc-image-builder + +Usage: $0 [OPTIONS] + +Options: + -f, --format FORMAT Output format (qcow2, raw, img) [default: qcow2] + -o, --output DIR Output directory [default: /output] + -s, --store DIR Store directory [default: /store] + -h, --help Show this help message + +Examples: + $0 simple-cli:latest + $0 -f raw -o /tmp/output simple-cli:latest + $0 --format qcow2 --output /output simple-cli:latest + +EOF +} + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + -f|--format) + OUTPUT_FORMAT="$2" + shift 2 + ;; + -o|--output) + OUTPUT_DIR="$2" + shift 2 + ;; + -s|--store) + STORE_DIR="$2" + shift 2 + ;; + -h|--help) + show_help + exit 0 + ;; + -*) + log_error "Unknown option $1" + show_help + exit 1 + ;; + *) + CONTAINER_IMAGE="$1" + shift + ;; + esac +done + +# Validate inputs +if [[ -z "$CONTAINER_IMAGE" ]]; then + log_error "Container image is required" + show_help + exit 1 +fi + +if [[ ! -d "$OUTPUT_DIR" ]]; then + log_error "Output directory $OUTPUT_DIR does not exist" + exit 1 +fi + +# Validate output format +case "$OUTPUT_FORMAT" in + qcow2|raw|img) + ;; + *) + log_error "Unsupported output format: $OUTPUT_FORMAT" + exit 1 + ;; +esac + +log_info "Starting simplified bootc-image-builder" +log_info "Container image: $CONTAINER_IMAGE" +log_info "Output format: $OUTPUT_FORMAT" +log_info "Output directory: $OUTPUT_DIR" + +# Create temporary working directory +# Use /var/tmp or /home instead of /tmp to avoid tmpfs space limitations +WORK_DIR=$(mktemp -d /var/tmp/bootc-builder-XXXXXX 2>/dev/null || mktemp -d /home/joe/tmp/bootc-builder-XXXXXX 2>/dev/null || echo "/home/joe/tmp/bootc-builder-$$") +if [[ ! -d "$WORK_DIR" ]]; then + mkdir -p "$WORK_DIR" || { + log_error "Failed to create working directory" + exit 1 + } +fi +log_info "Working directory: $WORK_DIR" + +# Check available space +AVAILABLE_SPACE=$(df "$WORK_DIR" | awk 'NR==2 {print $4}') +if [[ "$AVAILABLE_SPACE" -lt 5000000 ]]; then # Less than 5GB + log_warn "Low disk space available: ${AVAILABLE_SPACE}KB" + log_warn "This might cause issues with large containers" +fi + +# Function to cleanup on exit +cleanup() { + log_info "Cleaning up..." + + # Unmount if still mounted + if [[ -n "${MOUNT_POINT:-}" ]] && mountpoint -q "$MOUNT_POINT" 2>/dev/null; then + log_info "Unmounting $MOUNT_POINT..." + sudo $UMOUNT "$MOUNT_POINT" || true + fi + + # Detach loopback device if still attached + if [[ -n "${LOOP_DEV:-}" ]] && [[ -b "$LOOP_DEV" ]]; then + log_info "Detaching loopback device $LOOP_DEV..." + sudo $LOSETUP -d "$LOOP_DEV" || true + fi + + # Remove working directory + if [[ -n "${WORK_DIR:-}" ]] && [[ -d "$WORK_DIR" ]]; then + log_info "Removing working directory $WORK_DIR..." + sudo rm -rf "$WORK_DIR" || true + fi +} + +trap cleanup EXIT + +# Check if container image exists locally +log_info "Checking if container image exists locally: $CONTAINER_IMAGE" +if podman images --format "{{.Repository}}:{{.Tag}}" | grep -q "^$CONTAINER_IMAGE$"; then + log_info "Container image found locally, skipping pull" +elif podman images --format "{{.Repository}}:{{.Tag}}" | grep -q "simple-cli:latest"; then + log_info "Found simple-cli:latest locally, using that instead" + CONTAINER_IMAGE="simple-cli:latest" +elif podman images --format "{{.Repository}}:{{.Tag}}" | grep -q "localhost/simple-cli:latest"; then + log_info "Found localhost/simple-cli:latest locally, using that instead" + CONTAINER_IMAGE="localhost/simple-cli:latest" +else + log_info "Container image not found locally, attempting to pull: $CONTAINER_IMAGE" + podman pull "$CONTAINER_IMAGE" || { + log_error "Failed to pull container image. Please ensure the image is available." + log_error "You can either:" + log_error "1. Build the image locally first" + log_error "2. Use a fully qualified registry URL" + exit 1 + } +fi + +# Export the container to a tar file +log_info "Exporting container to tar..." +podman export "$(podman create --rm "$CONTAINER_IMAGE" true)" > "$WORK_DIR/container.tar" + +# Create a loopback device and mount the container +log_info "Setting up loopback device..." +cd "$WORK_DIR" + +# Extract the container +log_info "Extracting container filesystem..." +mkdir -p container-fs +if ! tar -xf container.tar -C container-fs; then + log_error "Failed to extract container. This might be due to:" + log_error "1. Insufficient disk space" + log_error "2. Corrupted container archive" + log_error "3. Permission issues" + exit 1 +fi + +# Create disk image using qemu-img (no sudo required) +log_info "Creating disk image..." +IMAGE_SIZE="2G" # Default size, could be made configurable + +# Create a proper disk image with partitions for bootloader installation +log_info "Creating partitioned disk image for bootloader installation..." + +# Create raw image file +$QEMU_IMG create -f raw disk.raw "$IMAGE_SIZE" + +# For bootloader installation, we need to create a proper disk structure +# This requires sudo for partitioning and mounting +log_info "Setting up disk partitions for bootloader installation..." +log_info "Note: This step requires sudo for disk operations" + +# Create partition table and filesystem +log_info "Creating partition table and filesystem..." +sudo $PARTED disk.raw mklabel msdos +sudo $PARTED disk.raw mkpart primary ext4 1MiB 100% +sudo $PARTED disk.raw set 1 boot on + +# Setup loopback device +log_info "Setting up loopback device..." +LOOP_DEV=$(sudo $LOSETUP --find --show disk.raw) +log_info "Using loopback device: $LOOP_DEV" + +# Force kernel to reread partition table +log_info "Rereading partition table..." +sudo partprobe "$LOOP_DEV" || true + +# Get the partition device +PART_DEV="${LOOP_DEV}p1" +log_info "Partition device: $PART_DEV" + +# Wait for partition to be available and verify it exists +log_info "Waiting for partition to be available..." +for i in {1..10}; do + if [[ -b "$PART_DEV" ]]; then + log_info "Partition device found: $PART_DEV" + break + fi + log_info "Waiting for partition device... (attempt $i/10)" + sleep 1 +done + +if [[ ! -b "$PART_DEV" ]]; then + log_error "Partition device not found: $PART_DEV" + log_error "Available devices:" + ls -la /dev/loop* || true + exit 1 +fi + +# Create filesystem +log_info "Creating ext4 filesystem..." +sudo mkfs.ext4 "$PART_DEV" + +# Mount the partition +MOUNT_POINT="$WORK_DIR/mount-point" +mkdir -p "$MOUNT_POINT" +sudo $MOUNT "$PART_DEV" "$MOUNT_POINT" + +# Copy container filesystem +log_info "Copying container filesystem to disk image..." +sudo rsync -a container-fs/ "$MOUNT_POINT/" + +# Install bootloader manually since deb-bootupd doesn't support non-default sysroots +log_info "Installing bootloader manually..." +if [[ -x "$GRUB_INSTALL" ]]; then + log_info "Found grub-install, installing GRUB bootloader..." + + # Install GRUB to the mounted filesystem + # Use --root-directory to specify the mount point + # Use --target=i386-pc for BIOS systems + # Use --boot-directory to specify where boot files are located + log_info "Installing GRUB to $MOUNT_POINT..." + sudo "$GRUB_INSTALL" \ + --root-directory="$MOUNT_POINT" \ + --target=i386-pc \ + --boot-directory="$MOUNT_POINT/boot" \ + --force \ + "$LOOP_DEV" || { + log_warn "GRUB installation failed, continuing without bootloader" + } + + # Generate GRUB configuration manually since grub-mkconfig needs host environment + log_info "Generating GRUB configuration manually..." + + # Create a basic GRUB configuration for the container + cat > "$MOUNT_POINT/boot/grub/grub.cfg" << 'EOF' +# GRUB configuration for simple-cli container +set timeout=5 +set default=0 + +menuentry "Simple CLI" { + set root=(hd0,msdos1) + linux /boot/vmlinuz-6.12.38+deb13-amd64 root=/dev/sda1 rw console=ttyS0 + initrd /boot/initrd.img-6.12.38+deb13-amd64 +} + +menuentry "Simple CLI (Recovery)" { + set root=(hd0,msdos1) + linux /boot/vmlinuz-6.12.38+deb13-amd64 root=/dev/sda1 rw single console=ttyS0 + initrd /boot/initrd.img-6.12.38+deb13-amd64 +} +EOF + + log_info "GRUB configuration created at $MOUNT_POINT/boot/grub/grub.cfg" + +else + log_warn "grub-install not found at $GRUB_INSTALL, skipping bootloader installation" + log_warn "Image will not be bootable without bootloader" +fi + +# Unmount +sudo $UMOUNT "$MOUNT_POINT" + +# Detach loopback device +sudo $LOSETUP -d "$LOOP_DEV" + +# Convert to requested format +log_info "Converting to $OUTPUT_FORMAT format..." +case "$OUTPUT_FORMAT" in + qcow2) + $QEMU_IMG convert -f raw -O qcow2 disk.raw "$OUTPUT_DIR/$(basename "$CONTAINER_IMAGE" | tr ':' '_').qcow2" + ;; + raw) + cp disk.raw "$OUTPUT_DIR/$(basename "$CONTAINER_IMAGE" | tr ':' '_').raw" + ;; + img) + cp disk.raw "$OUTPUT_DIR/$(basename "$CONTAINER_IMAGE" | tr ':' '_').img" + ;; +esac + +log_info "Image creation completed successfully!" +log_info "Output file: $OUTPUT_DIR/$(basename "$CONTAINER_IMAGE" | tr ':' '_').$OUTPUT_FORMAT" + +# List output files +log_info "Output directory contents:" +ls -la "$OUTPUT_DIR"