#!/bin/bash set -euo pipefail # --------------------------------------------------------------------------- # create.sh — Build a bootable Alpine Linux rescue ISO # # Run inside the Docker build container: # docker compose run --rm alpine bash /scripts/create.sh # # The resulting ISO is a hybrid image: it can be burned to a CD/DVD or # written directly to a USB stick with dd. # --------------------------------------------------------------------------- ALPINE_MIRROR="https://dl-cdn.alpinelinux.org/alpine" ALPINE_BRANCH="latest-stable" ALPINE_ARCH="x86_64" MINIROOTFS_BASE="${ALPINE_MIRROR}/${ALPINE_BRANCH}/releases/${ALPINE_ARCH}" ISO="/output/rescue-alpine.iso" CHROOT="/mnt/alpine-chroot" ISOROOT="/tmp/isoroot" mkdir -p /output "$CHROOT" "$ISOROOT" # --------------------------------------------------------------------------- # 1. Fetch latest Alpine minirootfs # --------------------------------------------------------------------------- echo "==> Fetching latest Alpine minirootfs index" TARBALL=$(curl -fsSL "${MINIROOTFS_BASE}/" \ | grep -oP 'alpine-minirootfs-[0-9.]+-x86_64\.tar\.gz' \ | sort -V | tail -1) [[ -z "$TARBALL" ]] && { echo "ERROR: could not determine minirootfs tarball" >&2; exit 1; } TARBALL_LOCAL="/tmp/${TARBALL}" if [[ ! -f "$TARBALL_LOCAL" ]]; then echo "==> Downloading ${MINIROOTFS_BASE}/${TARBALL}" curl -fsSL -o "$TARBALL_LOCAL" "${MINIROOTFS_BASE}/${TARBALL}" else echo "==> Using cached ${TARBALL_LOCAL}" fi # --------------------------------------------------------------------------- # 2. Extract minirootfs into chroot # --------------------------------------------------------------------------- echo "==> Extracting minirootfs into ${CHROOT}" tar -xzf "$TARBALL_LOCAL" -C "$CHROOT" # --------------------------------------------------------------------------- # 3. Copy Nebula binary and config into chroot # --------------------------------------------------------------------------- echo "==> Installing Nebula binary" mkdir -p "${CHROOT}/usr/local/bin" cp /nebula/nebula "${CHROOT}/usr/local/bin/nebula" chmod +x "${CHROOT}/usr/local/bin/nebula" echo "==> Installing Nebula config and certificates" mkdir -p "${CHROOT}/etc/nebula" cp /nebula/config.yml "${CHROOT}/etc/nebula/config.yml" for f in /nebula/*.crt /nebula/*.key; do [[ -f "$f" ]] && cp "$f" "${CHROOT}/etc/nebula/" done # --------------------------------------------------------------------------- # 4. Bind-mount virtual filesystems for chroot # --------------------------------------------------------------------------- echo "==> Bind-mounting virtual filesystems" mount --bind /dev "${CHROOT}/dev" mount --bind /proc "${CHROOT}/proc" mount --bind /sys "${CHROOT}/sys" mkdir -p "${CHROOT}/run" mount --bind /run "${CHROOT}/run" cp /etc/resolv.conf "${CHROOT}/etc/resolv.conf" # Make scripts available inside chroot mkdir -p "${CHROOT}/scripts" mount --bind /scripts "${CHROOT}/scripts" # --------------------------------------------------------------------------- # 5. Run in-chroot configuration # --------------------------------------------------------------------------- echo "==> Running chroot-setup.sh inside chroot" chroot "$CHROOT" /bin/sh /scripts/chroot-setup.sh # --------------------------------------------------------------------------- # 6. Unmount virtual filesystems # --------------------------------------------------------------------------- echo "==> Unmounting virtual filesystems" umount "${CHROOT}/scripts" || true umount "${CHROOT}/run" || true umount "${CHROOT}/sys" || true umount "${CHROOT}/proc" || true umount "${CHROOT}/dev" || true # --------------------------------------------------------------------------- # 7. Pack the full system as a squashfs (xz compressed) # --------------------------------------------------------------------------- echo "==> Creating squashfs from chroot (xz — this may take a while)" mksquashfs "$CHROOT" "${ISOROOT}/rootfs.squashfs" \ -comp xz -Xdict-size 100% -noappend \ -e boot/initramfs-lts 2>/dev/null || \ mksquashfs "$CHROOT" "${ISOROOT}/rootfs.squashfs" \ -comp xz -noappend echo " squashfs size: $(du -sh "${ISOROOT}/rootfs.squashfs" | cut -f1)" # --------------------------------------------------------------------------- # 8. Copy kernel into ISO root # --------------------------------------------------------------------------- echo "==> Copying kernel" cp "${CHROOT}/boot/vmlinuz-lts" "${ISOROOT}/vmlinuz" # --------------------------------------------------------------------------- # 9. Build tiny initramfs: busybox + init script that mounts the squashfs # --------------------------------------------------------------------------- echo "==> Building tiny initramfs" INITRD="/tmp/initrd" rm -rf "$INITRD" mkdir -p "${INITRD}"/{bin,dev,proc,sys,mnt/boot,mnt/squash,mnt/upper,mnt/work,mnt/newroot} # Static busybox — no external library deps BBOX=$(find /bin /usr/bin /usr/sbin -name 'busybox.static' -o -name 'busybox' 2>/dev/null \ | xargs -r file 2>/dev/null | grep -i 'statically linked' | cut -d: -f1 | head -1) [[ -z "$BBOX" ]] && { echo "ERROR: cannot find a statically linked busybox" >&2; exit 1; } echo " using static busybox: $BBOX" cp "$BBOX" "${INITRD}/bin/busybox" chmod +x "${INITRD}/bin/busybox" for cmd in sh mount umount mkdir mknod switch_root mdev sleep; do ln -sf busybox "${INITRD}/bin/${cmd}" done cat > "${INITRD}/init" <<'INIT_EOF' #!/bin/sh mount -t proc none /proc mount -t sysfs none /sys mount -t devtmpfs none /dev 2>/dev/null || mdev -s # Scan block devices for the ISO containing rootfs.squashfs found=0 for try in 1 2 3 4 5; do for dev in /dev/sr0 /dev/sda /dev/sda1 /dev/sdb /dev/sdb1 /dev/sdc /dev/sdc1; do [ -b "$dev" ] || continue mount -t iso9660 -o ro "$dev" /mnt/boot 2>/dev/null || \ mount -o ro "$dev" /mnt/boot 2>/dev/null || continue if [ -f /mnt/boot/rootfs.squashfs ]; then found=1 break 2 fi umount /mnt/boot 2>/dev/null done sleep 1 done if [ "$found" != "1" ]; then echo "ERROR: rootfs.squashfs not found — dropping to shell" exec sh fi mount -t squashfs -o ro /mnt/boot/rootfs.squashfs /mnt/squash mount -t tmpfs tmpfs /mnt/upper mkdir -p /mnt/upper/rw /mnt/upper/work mount -t overlay overlay \ -o lowerdir=/mnt/squash,upperdir=/mnt/upper/rw,workdir=/mnt/upper/work \ /mnt/newroot exec switch_root /mnt/newroot /sbin/init INIT_EOF chmod +x "${INITRD}/init" ( cd "$INITRD" && find . | cpio --quiet -oH newc | gzip -9 ) > "${ISOROOT}/initramfs.gz" echo " initramfs size: $(du -sh "${ISOROOT}/initramfs.gz" | cut -f1)" echo " initramfs contents:" zcat "${ISOROOT}/initramfs.gz" | cpio -t 2>/dev/null # --------------------------------------------------------------------------- # 10. Build ISO with grub-mkrescue (BIOS + UEFI hybrid) # grub-mkrescue uses its own file loader — no BIOS INT 13h size limits. # --------------------------------------------------------------------------- echo "==> Writing GRUB config" mkdir -p "${ISOROOT}/boot/grub" cat > "${ISOROOT}/boot/grub/grub.cfg" <<'EOF' set timeout=5 set default=0 menuentry "Alpine Linux USB Rescue" { linux /vmlinuz quiet init=/sbin/init initrd /initramfs.gz } EOF echo "==> Building hybrid ISO with grub-mkrescue: ${ISO}" grub-mkrescue -o "$ISO" "$ISOROOT" -- -V ALPINE_RESCUE # --------------------------------------------------------------------------- # Done # --------------------------------------------------------------------------- ISO_SIZE=$(du -sh "$ISO" | cut -f1) echo "" echo "================================================================" echo " Build complete!" echo " ISO : ${ISO} (${ISO_SIZE})" echo "" echo " Burn to USB:" echo " dd if=${ISO} of=/dev/sdX bs=4M status=progress && sync" echo "" echo " Burn to CD/DVD:" echo " cdrecord dev=/dev/sr0 ${ISO}" echo "" echo " Default login : root / alpine" echo " Nebula tunnel : auto-starts on boot (needs certs in nebula/)" echo "================================================================"