# EcoOS ISO Builder # Build from eco_os directory: # docker build --build-arg TARGET_ARCH=amd64 -t ecoos-builder -f isobuild/Dockerfile . # docker run --privileged -v $(pwd)/isobuild/output:/output ecoos-builder # # Supported architectures: amd64, arm64, rpi FROM ubuntu:24.04 ARG TARGET_ARCH=amd64 ENV TARGET_ARCH=${TARGET_ARCH} ENV DEBIAN_FRONTEND=noninteractive # Install common build dependencies RUN apt-get update && apt-get install -y \ live-build \ debootstrap \ xorriso \ squashfs-tools \ mtools \ dosfstools \ curl \ unzip \ git \ parted \ fdisk \ e2fsprogs \ && rm -rf /var/lib/apt/lists/* # Install architecture-specific packages RUN apt-get update && \ if [ "$TARGET_ARCH" = "amd64" ]; then \ apt-get install -y \ grub-efi-amd64-bin \ grub-efi-amd64-signed \ grub-pc-bin \ shim-signed \ syslinux-utils \ syslinux \ syslinux-common \ isolinux; \ elif [ "$TARGET_ARCH" = "arm64" ]; then \ apt-get install -y \ grub-efi-arm64-bin \ grub-efi-arm64-signed; \ elif [ "$TARGET_ARCH" = "rpi" ]; then \ apt-get install -y \ grub-efi-arm64-bin; \ fi && rm -rf /var/lib/apt/lists/* # Install Deno RUN curl -fsSL https://deno.land/install.sh | DENO_INSTALL=/usr/local sh # Create build directory WORKDIR /build # Copy isobuild configuration COPY isobuild/config/ /build/config/ COPY isobuild/scripts/ /build/scripts/ # Copy hooks to enable services (already in config/, but put in separate dir for build script) COPY isobuild/config/hooks/ /build/hooks/ # Copy daemon source (for bundling) COPY ecoos_daemon/ /daemon/ # Bundle the daemon - cross-compile for target architecture RUN cd /daemon && \ if [ "$TARGET_ARCH" = "amd64" ]; then \ deno compile --allow-all --target x86_64-unknown-linux-gnu --output /build/daemon-bundle/eco-daemon mod.ts; \ else \ deno compile --allow-all --target aarch64-unknown-linux-gnu --output /build/daemon-bundle/eco-daemon mod.ts; \ fi # Download Chromium during Docker build (network works here, not in chroot hooks) # Note: ARM64 Chromium snapshots may be less reliable, fallback to known version RUN echo "Downloading Chromium for $TARGET_ARCH..." && \ cd /tmp && \ if [ "$TARGET_ARCH" = "amd64" ]; then \ PLATFORM="Linux_x64"; \ FALLBACK_VERSION="1368529"; \ else \ PLATFORM="Linux_ARM64"; \ FALLBACK_VERSION="1368529"; \ fi && \ LATEST=$(curl -fsSL "https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/${PLATFORM}%2FLAST_CHANGE?alt=media" 2>/dev/null || echo "$FALLBACK_VERSION") && \ echo "Using Chromium build: $LATEST for platform $PLATFORM" && \ curl -fsSL "https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/${PLATFORM}%2F${LATEST}%2Fchrome-linux.zip?alt=media" -o chromium.zip || \ curl -fsSL "https://www.googleapis.com/download/storage/v1/b/chromium-browser-snapshots/o/${PLATFORM}%2F${FALLBACK_VERSION}%2Fchrome-linux.zip?alt=media" -o chromium.zip && \ mkdir -p /build/chromium && \ unzip -q chromium.zip -d /tmp && \ mv /tmp/chrome-linux/* /build/chromium/ && \ rm -rf chromium.zip /tmp/chrome-linux && \ chmod +x /build/chromium/chrome && \ echo "Chromium downloaded to /build/chromium/" # Make scripts executable RUN chmod +x /build/scripts/*.sh # Create dummy isohybrid for UEFI-only builds (real isohybrid needs BIOS boot record) RUN echo '#!/bin/sh' > /usr/local/bin/isohybrid && \ echo 'echo "Skipping isohybrid (UEFI-only build)"' >> /usr/local/bin/isohybrid && \ echo 'exit 0' >> /usr/local/bin/isohybrid && \ chmod +x /usr/local/bin/isohybrid # Build script - parameterized for architecture COPY <<'EOF' /build/docker-build.sh #!/bin/bash set -e export PATH="/usr/local/bin:/usr/bin:/usr/sbin:/bin:/sbin:$PATH" # Get architecture from environment (passed from docker run -e) TARGET_ARCH="${TARGET_ARCH:-amd64}" echo "=== EcoOS ISO Builder (Docker) ===" echo "Target architecture: $TARGET_ARCH" cd /build # Determine live-build architecture and image format case "$TARGET_ARCH" in amd64) LB_ARCH="amd64" IMAGE_FORMAT="iso-hybrid" BOOTLOADER_OPT="--bootloader grub-efi" ;; arm64) LB_ARCH="arm64" IMAGE_FORMAT="iso-hybrid" BOOTLOADER_OPT="--bootloader grub-efi" ;; rpi) LB_ARCH="arm64" IMAGE_FORMAT="hdd" BOOTLOADER_OPT="" # RPi uses native bootloader ;; esac echo "Live-build arch: $LB_ARCH, format: $IMAGE_FORMAT" # Initialize live-build lb config \ --architectures $LB_ARCH \ --distribution noble \ --archive-areas "main restricted universe multiverse" \ --mirror-bootstrap "http://ftp.halifax.rwth-aachen.de/ubuntu/" \ --mirror-chroot "http://ftp.halifax.rwth-aachen.de/ubuntu/" \ --mirror-chroot-security "http://ftp.halifax.rwth-aachen.de/ubuntu/" \ --mirror-binary "http://ftp.halifax.rwth-aachen.de/ubuntu/" \ --mirror-binary-security "http://ftp.halifax.rwth-aachen.de/ubuntu/" \ --binary-images $IMAGE_FORMAT \ --debian-installer false \ --memtest none \ $BOOTLOADER_OPT \ --iso-application "EcoOS" \ --iso-publisher "EcoBridge" \ --iso-volume "EcoOS" # Copy common package lists (excluding architecture-specific ones) for f in /build/config/live-build/package-lists/*.list.chroot; do filename=$(basename "$f") # Skip architecture-specific files (base-amd64, base-arm64, base-rpi) case "$filename" in base-amd64.list.chroot|base-arm64.list.chroot|base-rpi.list.chroot) echo "Skipping arch-specific list: $filename" ;; *) cp "$f" config/package-lists/ ;; esac done # Append architecture-specific packages to base.list.chroot if [ -f "/build/config/live-build/package-lists/base-${TARGET_ARCH}.list.chroot" ]; then echo "Adding architecture-specific packages for $TARGET_ARCH..." cat "/build/config/live-build/package-lists/base-${TARGET_ARCH}.list.chroot" >> config/package-lists/base.list.chroot fi # Prepare includes.chroot mkdir -p config/includes.chroot/opt/eco/bin mkdir -p config/includes.chroot/opt/eco/daemon mkdir -p config/includes.chroot/etc/systemd/system # Copy daemon bundle cp /build/daemon-bundle/eco-daemon config/includes.chroot/opt/eco/bin/ chmod +x config/includes.chroot/opt/eco/bin/eco-daemon # Copy pre-downloaded Chromium echo "Installing pre-downloaded Chromium into chroot..." mkdir -p config/includes.chroot/opt/chromium cp -r /build/chromium/* config/includes.chroot/opt/chromium/ chmod +x config/includes.chroot/opt/chromium/chrome # Create symlinks for chromium-browser command mkdir -p config/includes.chroot/usr/bin cat > config/includes.chroot/usr/bin/chromium-browser << 'CHROMEWRAPPER' #!/bin/sh exec /opt/chromium/chrome "$@" CHROMEWRAPPER chmod +x config/includes.chroot/usr/bin/chromium-browser ln -sf /opt/chromium/chrome config/includes.chroot/usr/bin/chromium echo "Chromium installed to /opt/chromium/" # Create dummy isohybrid in chroot (UEFI-only, no BIOS boot record) mkdir -p config/includes.chroot/usr/bin cat > config/includes.chroot/usr/bin/isohybrid << 'ISOHYBRID' #!/bin/sh echo "Skipping isohybrid (UEFI-only build)" exit 0 ISOHYBRID chmod +x config/includes.chroot/usr/bin/isohybrid # Copy systemd services cp /build/config/systemd/eco-daemon.service config/includes.chroot/etc/systemd/system/ # Copy installer (files are already in config/includes.chroot via COPY) chmod +x config/includes.chroot/opt/eco/installer/install.sh 2>/dev/null || true # Copy hooks to enable services mkdir -p config/hooks/normal cp /build/hooks/normal/*.hook.chroot config/hooks/normal/ chmod +x config/hooks/normal/*.hook.chroot # Copy autoinstall config mkdir -p config/includes.binary/autoinstall cp /build/config/autoinstall/user-data config/includes.binary/autoinstall/ touch config/includes.binary/autoinstall/meta-data # Architecture-specific EFI/boot setup if [ "$TARGET_ARCH" = "amd64" ]; then # AMD64 EFI boot setup echo "Preparing AMD64 EFI boot structure..." mkdir -p config/includes.binary/EFI/BOOT mkdir -p config/includes.binary/boot/grub # Copy signed EFI files from host (installed in Docker image) cp /usr/lib/shim/shimx64.efi.signed.latest config/includes.binary/EFI/BOOT/BOOTX64.EFI || \ cp /usr/lib/shim/shimx64.efi.signed config/includes.binary/EFI/BOOT/BOOTX64.EFI || \ cp /usr/lib/shim/shimx64.efi config/includes.binary/EFI/BOOT/BOOTX64.EFI || true cp /usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed config/includes.binary/EFI/BOOT/grubx64.efi || \ cp /usr/lib/grub/x86_64-efi/grubx64.efi config/includes.binary/EFI/BOOT/grubx64.efi || true # Also provide mmx64.efi for some UEFI implementations if [ -f config/includes.binary/EFI/BOOT/grubx64.efi ]; then cp config/includes.binary/EFI/BOOT/grubx64.efi config/includes.binary/EFI/BOOT/mmx64.efi fi # Create grub.cfg for live boot with installer option cat > config/includes.binary/boot/grub/grub.cfg << 'GRUBCFG' set default=0 set timeout=10 insmod part_gpt insmod fat insmod efi_gop insmod efi_uga menuentry "Install EcoOS (auto-selects in 10s)" { linux /casper/vmlinuz boot=casper noprompt quiet splash ecoos_install=1 --- initrd /casper/initrd } menuentry "EcoOS Live (Try without installing)" { linux /casper/vmlinuz boot=casper noprompt quiet splash --- initrd /casper/initrd } menuentry "EcoOS Live (Safe Mode)" { linux /casper/vmlinuz boot=casper noprompt nomodeset --- initrd /casper/initrd } GRUBCFG # Also put grub.cfg in EFI/BOOT for fallback cp config/includes.binary/boot/grub/grub.cfg config/includes.binary/EFI/BOOT/grub.cfg elif [ "$TARGET_ARCH" = "arm64" ]; then # ARM64 EFI boot setup echo "Preparing ARM64 EFI boot structure..." mkdir -p config/includes.binary/EFI/BOOT mkdir -p config/includes.binary/boot/grub # Copy ARM64 GRUB EFI cp /usr/lib/grub/arm64-efi-signed/grubaa64.efi.signed config/includes.binary/EFI/BOOT/BOOTAA64.EFI || \ cp /usr/lib/grub/arm64-efi/grubaa64.efi config/includes.binary/EFI/BOOT/BOOTAA64.EFI || true # Create grub.cfg for ARM64 cat > config/includes.binary/boot/grub/grub.cfg << 'GRUBCFG' set default=0 set timeout=10 insmod part_gpt insmod fat insmod efi_gop menuentry "Install EcoOS (auto-selects in 10s)" { linux /casper/vmlinuz boot=casper noprompt quiet splash ecoos_install=1 --- initrd /casper/initrd } menuentry "EcoOS Live (Try without installing)" { linux /casper/vmlinuz boot=casper noprompt quiet splash --- initrd /casper/initrd } menuentry "EcoOS Live (Safe Mode)" { linux /casper/vmlinuz boot=casper noprompt nomodeset --- initrd /casper/initrd } GRUBCFG cp config/includes.binary/boot/grub/grub.cfg config/includes.binary/EFI/BOOT/grub.cfg elif [ "$TARGET_ARCH" = "rpi" ]; then # Raspberry Pi boot setup (native bootloader, no GRUB) echo "Preparing Raspberry Pi boot structure..." mkdir -p config/includes.binary/boot # Create config.txt for Raspberry Pi cat > config/includes.binary/boot/config.txt << 'PICFG' # EcoOS Raspberry Pi Configuration # Supports Pi 3, 4, and 5 # Enable 64-bit mode arm_64bit=1 # Kernel and initrd kernel=vmlinuz initramfs initrd.img followkernel # Enable serial console for debugging enable_uart=1 # GPU/display settings dtoverlay=vc4-kms-v3d gpu_mem=256 # USB and power settings (Pi 4/5) max_usb_current=1 # Audio dtparam=audio=on # Camera/display interfaces camera_auto_detect=1 display_auto_detect=1 # Pi 5 specific (ignored on older models) [pi5] dtoverlay=dwc2,dr_mode=host PICFG # Create cmdline.txt cat > config/includes.binary/boot/cmdline.txt << 'CMDLINE' console=serial0,115200 console=tty1 root=LABEL=EcoOS rootfstype=ext4 fsck.repair=yes rootwait quiet splash CMDLINE fi # Build - use individual lb stages to control the process echo "Running lb bootstrap..." lb bootstrap echo "Running lb chroot..." lb chroot # Try lb binary, but continue even if isohybrid fails echo "Running lb binary..." lb binary || { echo "lb binary had errors, checking if output was created anyway..." if ls /build/*.iso 2>/dev/null || ls /build/*.img 2>/dev/null; then echo "Output exists despite errors, continuing..." else echo "No output found, build truly failed" exit 1 fi } # Post-processing based on architecture if [ "$TARGET_ARCH" = "amd64" ] || [ "$TARGET_ARCH" = "arm64" ]; then # Find the ISO file echo "Searching for ISO file..." find /build -name "*.iso" -type f 2>/dev/null ls -la /build/*.iso 2>/dev/null || true ISO_FILE=$(find /build -name "*.iso" -type f 2>/dev/null | head -1) if [ -z "$ISO_FILE" ]; then echo "ERROR: No ISO file found in build directory" ls -la /build/ exit 1 fi echo "Found ISO: $ISO_FILE" # Rebuild ISO with proper EFI boot support echo "Creating UEFI-bootable ISO..." # Extract ISO contents mkdir -p /tmp/iso_extract xorriso -osirrox on -indev "$ISO_FILE" -extract / /tmp/iso_extract # Find the actual kernel and initrd names VMLINUZ=$(ls /tmp/iso_extract/casper/vmlinuz* 2>/dev/null | head -1 | xargs basename) INITRD=$(ls /tmp/iso_extract/casper/initrd* 2>/dev/null | head -1 | xargs basename) echo "Found kernel: $VMLINUZ, initrd: $INITRD" # Ensure EFI structure exists with proper files mkdir -p /tmp/iso_extract/EFI/BOOT mkdir -p /tmp/iso_extract/boot/grub if [ "$TARGET_ARCH" = "amd64" ]; then # Copy AMD64 EFI files cp /usr/lib/shim/shimx64.efi.signed.latest /tmp/iso_extract/EFI/BOOT/BOOTX64.EFI 2>/dev/null || \ cp /usr/lib/shim/shimx64.efi.signed /tmp/iso_extract/EFI/BOOT/BOOTX64.EFI 2>/dev/null || \ cp /usr/lib/shim/shimx64.efi /tmp/iso_extract/EFI/BOOT/BOOTX64.EFI 2>/dev/null || true cp /usr/lib/grub/x86_64-efi-signed/grubx64.efi.signed /tmp/iso_extract/EFI/BOOT/grubx64.efi 2>/dev/null || \ cp /usr/lib/grub/x86_64-efi/grubx64.efi /tmp/iso_extract/EFI/BOOT/grubx64.efi 2>/dev/null || true if [ -f /tmp/iso_extract/EFI/BOOT/grubx64.efi ]; then cp /tmp/iso_extract/EFI/BOOT/grubx64.efi /tmp/iso_extract/EFI/BOOT/mmx64.efi fi EFI_BOOT_FILE="BOOTX64.EFI" else # Copy ARM64 EFI files cp /usr/lib/grub/arm64-efi-signed/grubaa64.efi.signed /tmp/iso_extract/EFI/BOOT/BOOTAA64.EFI 2>/dev/null || \ cp /usr/lib/grub/arm64-efi/grubaa64.efi /tmp/iso_extract/EFI/BOOT/BOOTAA64.EFI 2>/dev/null || true EFI_BOOT_FILE="BOOTAA64.EFI" fi # Update grub.cfg with correct filenames cat > /tmp/iso_extract/boot/grub/grub.cfg << GRUBCFG2 set default=0 set timeout=10 insmod part_gpt insmod fat insmod efi_gop $([ "$TARGET_ARCH" = "amd64" ] && echo "insmod efi_uga") menuentry "Install EcoOS (auto-selects in 10s)" { linux /casper/${VMLINUZ} boot=casper noprompt quiet splash ecoos_install=1 --- initrd /casper/${INITRD} } menuentry "EcoOS Live (Try without installing)" { linux /casper/${VMLINUZ} boot=casper noprompt quiet splash --- initrd /casper/${INITRD} } menuentry "EcoOS Live (Safe Mode)" { linux /casper/${VMLINUZ} boot=casper noprompt nomodeset --- initrd /casper/${INITRD} } GRUBCFG2 cp /tmp/iso_extract/boot/grub/grub.cfg /tmp/iso_extract/EFI/BOOT/grub.cfg # Create EFI boot image (FAT filesystem for UEFI El Torito boot) echo "Creating EFI boot image..." dd if=/dev/zero of=/tmp/efi.img bs=1M count=10 mkfs.fat -F 12 /tmp/efi.img mmd -i /tmp/efi.img ::/EFI mmd -i /tmp/efi.img ::/EFI/BOOT mcopy -i /tmp/efi.img /tmp/iso_extract/EFI/BOOT/$EFI_BOOT_FILE ::/EFI/BOOT/ if [ "$TARGET_ARCH" = "amd64" ]; then mcopy -i /tmp/efi.img /tmp/iso_extract/EFI/BOOT/grubx64.efi ::/EFI/BOOT/ 2>/dev/null || true mcopy -i /tmp/efi.img /tmp/iso_extract/EFI/BOOT/mmx64.efi ::/EFI/BOOT/ 2>/dev/null || true fi mcopy -i /tmp/efi.img /tmp/iso_extract/EFI/BOOT/grub.cfg ::/EFI/BOOT/ # Rebuild ISO with EFI boot support echo "Rebuilding ISO with UEFI boot support..." xorriso -as mkisofs \ -r -V "EcoOS" \ -o /tmp/ecoos-efi.iso \ -J -joliet-long \ -eltorito-alt-boot \ -e --interval:appended_partition_2:all:: \ -no-emul-boot -isohybrid-gpt-basdat \ -append_partition 2 0xef /tmp/efi.img \ /tmp/iso_extract if [ -f /tmp/ecoos-efi.iso ]; then ISO_FILE=/tmp/ecoos-efi.iso echo "Created UEFI-bootable ISO: $ISO_FILE" else echo "ERROR: Failed to create EFI ISO" exit 1 fi rm -rf /tmp/iso_extract # Determine output filename if [ "$TARGET_ARCH" = "amd64" ]; then OUTPUT_NAME="ecoos.iso" else OUTPUT_NAME="ecoos-arm64.iso" fi # Copy to output mkdir -p /output cp "$ISO_FILE" /output/$OUTPUT_NAME echo "" echo "=== Final ISO EFI check ===" xorriso -indev /output/$OUTPUT_NAME -find / -maxdepth 2 -type d 2>/dev/null || true echo "" echo "=== Build Complete ===" echo "ISO: /output/$OUTPUT_NAME" ls -lh /output/$OUTPUT_NAME elif [ "$TARGET_ARCH" = "rpi" ]; then # Raspberry Pi image creation echo "Creating Raspberry Pi bootable image..." # Find the live-build output HDD_FILE=$(find /build -name "*.img" -type f 2>/dev/null | head -1) SQUASHFS_FILE=$(find /build -name "filesystem.squashfs" -type f 2>/dev/null | head -1) if [ -z "$SQUASHFS_FILE" ]; then echo "Looking for squashfs in chroot..." SQUASHFS_FILE=$(find /build/chroot -name "filesystem.squashfs" -type f 2>/dev/null | head -1) fi echo "Found squashfs: $SQUASHFS_FILE" # Create RPi image using the helper script /build/scripts/create-rpi-image.sh "$SQUASHFS_FILE" /output/ecoos-rpi.img echo "" echo "=== Build Complete ===" echo "Image: /output/ecoos-rpi.img" ls -lh /output/ecoos-rpi.img fi EOF RUN chmod +x /build/docker-build.sh CMD ["/build/docker-build.sh"]