diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f122d5d..8765b36 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,44 +14,80 @@ concurrency: cancel-in-progress: true jobs: - build: + builder: strategy: max-parallel: 2 fail-fast: true matrix: arch: [x86_64, aarch64] - runtime: [none, docker, containerd, incus] include: - arch: x86_64 os: ubuntu-latest - arch: aarch64 os: ubuntu-24.04-arm runs-on: ${{ matrix.os }} - name: build-${{ matrix.arch }}-${{ matrix.runtime }} + name: builder-${{ matrix.arch }} timeout-minutes: 180 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 1 persist-credentials: false + - name: Build + run: make save-builder + + - name: Upload artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: builder-${{ matrix.arch }} + path: builder-${{ matrix.arch }}.tar + overwrite: true + + image: + strategy: + max-parallel: 4 + fail-fast: true + matrix: + arch: [x86_64, aarch64] + dist: [ubuntu, debian] + include: + - arch: x86_64 + os: ubuntu-latest + - arch: aarch64 + os: ubuntu-24.04-arm + runs-on: ${{ matrix.os }} + needs: [builder] + name: images-${{ matrix.arch }}-${{ matrix.dist }} + timeout-minutes: 180 + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 1 + persist-credentials: false + + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: builder-${{ matrix.arch }} + - name: Build run: | - make image OS_ARCH=${{ matrix.arch }} RUNTIME=${{ matrix.runtime }} + docker load -i builder-${{ matrix.arch }}.tar + make DIST=${{ matrix.dist }} none docker containerd incus ls -lah dist/img - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: img-${{ matrix.arch }}-${{ matrix.runtime }} - path: dist/img/*-${{ matrix.runtime }}.* + name: img-${{ matrix.arch }}-${{ matrix.dist}} + path: dist/img/${{ matrix.dist}}-*-*.raw.* overwrite: true retention-days: 1 compression-level: 0 release: runs-on: ubuntu-latest - needs: [build] + needs: [image] if: github.event_name != 'pull_request' permissions: contents: write @@ -59,12 +95,12 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 1 persist-credentials: false - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 with: path: dist/img diff --git a/.gitignore b/.gitignore index 849ddff..4fc8f1f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ dist/ +scripts/.build-image-stamp* diff --git a/Makefile b/Makefile index c38eba1..79b1639 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,41 @@ # image and tool versions include dependencies.env +UNAME_S := $(shell uname -s) + +to_upper = $(shell echo '$1' | tr '[:lower:]' '[:upper:]') + +ifeq ($(MAKE_TERMOUT),) + # Make < 4 doesn't set MAKE_TERMOUT + INTERACTIVE ?= $(shell [ -t 0 ] && echo 1) +else + INTERACTIVE ?= 1 +endif + +ifeq ($(INTERACTIVE),1) +DOCKER_RUN_ARGS := -i --tty +endif + +ifeq ($(UNAME_S),Darwin) + # macOS uses BSD stat + PRINT_STATS_CMD = stat -f "%N%n |- %z bytes%n '- %Sm" +else + # Linux (and most others) use GNU stat + PRINT_STATS_CMD = stat -c "%N\n |- %s bytes\n '- %y" +endif + +# quiet target commands by default +ifeq ($(filter $(DEBUG),1 true),) +MAKEFLAGS += -s +else +TARV = v +endif + +# which dist image to build (debian or ubuntu) +DIST ?= ubuntu + # runtime +RUNTIMES = docker containerd incus none RUNTIME ?= docker # architecture defaults to the current system's. @@ -14,7 +48,7 @@ endif # is required for Docker and asset downloads. ARCH_x86_64 = amd64 ARCH_aarch64 = arm64 -ARCH = $(shell echo "$(ARCH_$(OS_ARCH))") +ARCH = $(ARCH_$(OS_ARCH)) # binfmt needs the opposite of OS_ARCH BINFMT_ARCH = aarch64 @@ -22,24 +56,160 @@ ifeq ($(strip $(OS_ARCH)),aarch64) BINFMT_ARCH = x86_64 endif +export ARCH +export BINFMT_ARCH + +# containerd +CONTAINERD_ARCHIVE ?= dist/containerd/containerd-utils-$(ARCH).tar.gz +NERDCTL_FILE ?= dist/containerd/nerdctl-full-$(NERDCTL_VERSION)-linux-$(ARCH).tar.gz +NERDCTL_URL ?= https://github.com/containerd/nerdctl/releases/download/v$(NERDCTL_VERSION)/$(notdir $(NERDCTL_FILE)) +FLANNEL_FILE ?= dist/containerd/cni-plugin-flannel-linux-$(ARCH)-v$(FLANNEL_MINI_VERSION).tgz +FLANNEL_URL ?= https://github.com/flannel-io/cni-plugin/releases/download/v$(FLANNEL_VERSION)/$(notdir $(FLANNEL_FILE)) + +# binfmt +BINFMT_ARCHIVE = dist/binfmt/binfmt-$(ARCH).tar.gz +BINFMT_DOWNLOAD_URL ?= https://github.com/tonistiigi/binfmt/releases/download/$(BINFMT_VERSION) +BINFMT_FILE ?= dist/binfmt/$(BINFMT_VERSION)/binfmt_linux-$(ARCH).tar.gz +BINFMT_URL ?= $(BINFMT_DOWNLOAD_URL)/$(notdir $(BINFMT_FILE)) +BINFMT_QEMU_FILE ?= dist/binfmt/$(BINFMT_QEMU_VERSION)/qemu_v$(BINFMT_QEMU_VERSION)_linux-$(ARCH).tar.gz +BINFMT_QEMU_URL ?= $(BINFMT_DOWNLOAD_URL)/$(notdir $(BINFMT_QEMU_FILE)) + +# ubuntu +UBUNTU_IMAGE_BASE_URL ?= https://cloud-images.ubuntu.com/minimal/releases/$(UBUNTU_CODENAME)/release-$(UBUNTU_BUILD) +UBUNTU_IMAGE_FILE ?= dist/img/ubuntu-$(UBUNTU_VERSION)-minimal-cloudimg-$(ARCH).img +UBUNTU_IMAGE_SHA_FILE ?= $(UBUNTU_IMAGE_FILE).sha256sum + +# debian +DEBIAN_IMAGE_BASE_URL ?= https://cloud.debian.org/images/cloud/$(DEBIAN_CODENAME)/$(DEBIAN_BUILD) +DEBIAN_IMAGE_FILE ?= dist/img/debian-$(DEBIAN_VERSION)-genericcloud-$(ARCH)-$(DEBIAN_BUILD).qcow2 +DEBIAN_IMAGE_SHA_FILE ?= $(DEBIAN_IMAGE_FILE).sha512sum + +# DIST resolved variables +IMAGE_BASE_URL ?= $($(call to_upper,$(DIST))_IMAGE_BASE_URL) +IMAGE_FILE ?= $($(call to_upper,$(DIST))_IMAGE_FILE) +IMAGE_SHA_FILE ?= $($(call to_upper,$(DIST))_IMAGE_SHA_FILE) +IMAGE_SHA_SIZE ?= $(patsubst .sha%sum,%,$(suffix $(IMAGE_SHA_FILE))) + +# +# defines +# + +define download_and_verify +$(1): + @echo "downloading $(2)" + mkdir -p $$(@D) && \ + curl --retry 3 --connect-timeout 10 -o$$@.download -L $(2) && \ + $$(PRINT_STATS_CMD) $$@.download && \ + tar -xzOf $$@.download >/dev/null || { \ + echo >&2 "error downloading"; \ + exit 1; \ + } + mv -f $$@.download $$@ +endef + +# image builder container image +DOCKER_BUILD_IMAGE = scripts/.build-image-stamp-$(ARCH) +DOCKER_BUILD_IMAGE_SOURCES = scripts/Dockerfile scripts/image.sh +DOCKER_BUILD_IMAGE_TAG = colima-core-builder:$(ARCH) + +IMAGE_DEPENDENCIES = $(DOCKER_BUILD_IMAGE) $(CONTAINERD_ARCHIVE).sha512sum $(BINFMT_ARCHIVE).sha512sum # # targets # -all: image +.PHONY: clean distclean save-builder image $(RUNTIMES) + +# deprecated (default) target +image: $(RUNTIME) + +all: $(RUNTIMES) -.PHONY: clean +# rm build targets clean: + rm -rf $(IMAGE_DEPENDENCIES) dist/img/*.raw.gz* + +# rm + cache +distclean: clean rm -rf dist -cloud-image: - ARCH=$(ARCH) UBUNTU_VERSION=$(UBUNTU_VERSION) UBUNTU_CODENAME=$(UBUNTU_CODENAME) scripts/cloud-image.sh +# base image +$(IMAGE_FILE): + @echo "target: $@" + mkdir -p $(@D) && curl --retry 5 --connect-timeout 10 -o"$@" -L $(IMAGE_BASE_URL)/$(notdir $@) + +$(IMAGE_SHA_FILE): $(IMAGE_FILE) + @echo "target: $@" + shasum -a $(IMAGE_SHA_SIZE) $< > $@.tmp + cd dist/img && ( \ + curl --retry 5 --connect-timeout 10 -L $(IMAGE_BASE_URL)/SHA$(IMAGE_SHA_SIZE)SUMS | \ + grep $(notdir $<) | \ + shasum -a $(IMAGE_SHA_SIZE) --check --status \ + ) || { \ + echo >&2 "checksum did not match!"; \ + rm -f $@.tmp; \ + mv -f $< $<.invalid; \ + exit 1; \ + } + mv $@.tmp $@ + +# checksum +%.sha512sum: % + @echo "target: $@" + shasum -a 512 $< > $@ + +# binfmt +$(BINFMT_ARCHIVE): $(BINFMT_FILE) $(BINFMT_QEMU_FILE) + @echo "target: $@" + rm -f '$@' + TMP_DIR=$$(mktemp -d); \ + trap 'rm -rf "$$TMP_DIR"' EXIT; \ + for f in $^; do tar $(TARV)zxf "$$f" -C "$$TMP_DIR"; done; \ + cd "$$TMP_DIR" && tar $(TARV)czf '$(CURDIR)/$@' binfmt qemu-i386 qemu-$(BINFMT_ARCH) || { \ + echo >&2 "failed to create $@" ; \ + rm -f '$(CURDIR)/$@'; exit 1; \ + } + +$(eval $(call download_and_verify,$(BINFMT_FILE),$(BINFMT_URL))) +$(eval $(call download_and_verify,$(BINFMT_QEMU_FILE),$(BINFMT_QEMU_URL))) + +# containerd +$(CONTAINERD_ARCHIVE): $(NERDCTL_FILE) $(FLANNEL_FILE) + @echo "target: $@" + rm -f '$@' + TMP_DIR=$$(mktemp -d); \ + trap 'rm -rf "$$TMP_DIR"' EXIT; \ + for f in $^; do tar $(TARV)xzf "$$f" -C "$$TMP_DIR"; done; \ + cd "$$TMP_DIR" && tar $(TARV)czf '$(CURDIR)/$@' bin lib libexec share || { \ + echo >&2 "failed to create $@" ; \ + rm -f '$(CURDIR)/$@'; exit 1; \ + } + +$(eval $(call download_and_verify,$(NERDCTL_FILE),$(NERDCTL_URL))) +$(eval $(call download_and_verify,$(FLANNEL_FILE),$(FLANNEL_URL))) + +# builder +save-builder: $(DOCKER_BUILD_IMAGE) + docker save $(DOCKER_BUILD_IMAGE_TAG) -o builder-$(OS_ARCH).tar -binfmt: - ARCH=$(ARCH) BINFMT_ARCH=$(BINFMT_ARCH) BINFMT_VERSION=$(BINFMT_VERSION) BINFMT_QEMU_VERSION=$(BINFMT_QEMU_VERSION) scripts/binfmt.sh +$(DOCKER_BUILD_IMAGE): $(DOCKER_BUILD_IMAGE_SOURCES) Makefile + docker build --platform linux/$(ARCH) --build-arg UBUNTU_VERSION=$(UBUNTU_VERSION) -t $(DOCKER_BUILD_IMAGE_TAG) --iidfile $@ $(dir $@) -containerd: - ARCH=$(ARCH) NERDCTL_VERSION=$(NERDCTL_VERSION) FLANNEL_VERSION=$(FLANNEL_VERSION) FLANNEL_MINI_VERSION=$(FLANNEL_MINI_VERSION) RUNTIME=$(RUNTIME) scripts/containerd.sh +# images +$(basename $(IMAGE_FILE))-%.raw.gz: $(IMAGE_SHA_FILE) $(IMAGE_DEPENDENCIES) $(DOCKER_BUILD_IMAGE) Makefile + if [ $(OS_ARCH) != $(ARCH) ] ; then docker run $(DOCKER_RUN_ARGS) --privileged --rm tonistiigi/binfmt --install $(BINFMT_ARCH); fi + docker run $(DOCKER_RUN_ARGS) --rm --privileged \ + --platform linux/$(ARCH) \ + --volume $(CURDIR):/build \ + --env DIST=$(DIST) \ + --env BINFMT_ARCHIVE=$(BINFMT_ARCHIVE) \ + --env CONTAINERD_ARCHIVE=$(CONTAINERD_ARCHIVE) \ + --env IMAGE_FILE=$(IMAGE_FILE) \ + --env DOCKER_VERSION=$(DOCKER_VERSION) \ + --env RUNTIME=$* \ + $(DOCKER_BUILD_IMAGE_TAG) || { \ + echo >&2 "failed to create $@"; \ + rm -f '$@'* ; exit 1; \ + } -image: cloud-image binfmt containerd - ARCH=$(ARCH) BINFMT_ARCH=$(BINFMT_ARCH) UBUNTU_VERSION=$(UBUNTU_VERSION) DOCKER_VERSION=$(DOCKER_VERSION) RUNTIME=$(RUNTIME) scripts/image.docker.sh +$(RUNTIMES): %: $(basename $(IMAGE_FILE))-%.raw.gz + $(PRINT_STATS_CMD) $< diff --git a/README.md b/README.md index e61a59e..5d77b3b 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,35 @@ Dependencies for Colima ## Generating image -Generate a raw disk image compressed with gzip (`.raw.gz`) for the OS architecture and default runtime (docker). +Generate a raw disk image compressed with gzip (`.raw.gz`) for the OS architecture, default runtime (docker), and default OS (Ubuntu). ```sh -make image +make ``` Generate a `.raw.gz` image for another architecture. `OS_ARCH` must be one of `aarch64`, `x86_64` ```sh -OS_ARCH=x86_64 make image +OS_ARCH=x86_64 make ``` -Generate a `.raw.gz` image for another runtime. `RUNTIME` must be one of `docker`, `containerd`, `incus`, `none` +Generate a `.raw.gz` image for another runtime. ```sh -RUNTIME=containerd make image +make docker # default make target +make containerd +make incus +make none +``` + +Generate `.raw.gz` images for all runtimes. + +```sh +make all +``` + +Generate images based on Debian. + +```sh +DIST=debian make ``` diff --git a/dependencies.env b/dependencies.env index 9572740..3f762a1 100644 --- a/dependencies.env +++ b/dependencies.env @@ -1,8 +1,12 @@ -UBUNTU_VERSION="24.04" -UBUNTU_CODENAME="noble" -BINFMT_VERSION="deploy/v7.0.0-28" -BINFMT_QEMU_VERSION="7.0.0" -DOCKER_VERSION="29.5.2" -NERDCTL_VERSION="2.3.1" -FLANNEL_VERSION="1.9.1-flannel1" -FLANNEL_MINI_VERSION="1.9.1" +UBUNTU_VERSION=24.04 +UBUNTU_CODENAME=noble +UBUNTU_BUILD=20260617 +DEBIAN_VERSION=13 +DEBIAN_CODENAME=trixie +DEBIAN_BUILD=20260525-2489 +BINFMT_VERSION=deploy/v7.0.0-28 +BINFMT_QEMU_VERSION=7.0.0 +DOCKER_VERSION=29.5.2 +NERDCTL_VERSION=2.3.1 +FLANNEL_VERSION=1.9.1-flannel1 +FLANNEL_MINI_VERSION=1.9.1 diff --git a/scripts/Dockerfile b/scripts/Dockerfile new file mode 100644 index 0000000..a1e46ae --- /dev/null +++ b/scripts/Dockerfile @@ -0,0 +1,23 @@ +ARG UBUNTU_VERSION + +FROM ubuntu:${UBUNTU_VERSION?} AS os + +ENV DEBIAN_FRONTEND=noninteractive +# https://bugs.launchpad.net/ubuntu/+source/openssl/+bug/2141933 +ENV OPENSSL_FORCE_FIPS_MODE=0 +SHELL ["/bin/bash", "-e", "-o", "pipefail", "-c"] + +RUN --mount=type=cache,target=/var/cache/apt,rw --mount=type=cache,target=/var/lib/apt,rw < /etc/apt/apt.conf.d/colima +apt-get -q update +apt-get -q install -y file libdigest-sha-perl qemu-utils pigz +apt-get -q autoremove -y +apt-get -q clean -y +rm -rf /var/lib/apt/lists/* /var/cache/apt/* +EOD + +COPY image.sh /image.sh + +VOLUME /build + +CMD ["/image.sh"] diff --git a/scripts/binfmt.sh b/scripts/binfmt.sh deleted file mode 100755 index 2c492d0..0000000 --- a/scripts/binfmt.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env bash - -set -eux - -# external variables that must be set -echo vars: $ARCH $BINFMT_ARCH - -SCRIPT_DIR=$(realpath "$(dirname "$(dirname $0)")") -DIST_DIR="${SCRIPT_DIR}/dist/binfmt" -mkdir -p $DIST_DIR - -TEMP_DIR=/tmp/binfmt -mkdir -p $TEMP_DIR -cd $TEMP_DIR - -download_binfmt() ( - # download archive - FILE="binfmt_linux-${1}.tar.gz" - URL="https://github.com/tonistiigi/binfmt/releases/download/${BINFMT_VERSION}/${FILE}" - curl -LO $URL - - # extract - tar xvfz $FILE -) - -download_qemu() ( - # download archive - FILE="qemu_v${BINFMT_QEMU_VERSION}_linux-${1}.tar.gz" - URL="https://github.com/tonistiigi/binfmt/releases/download/${BINFMT_VERSION}/${FILE}" - curl -LO $URL - - # extract - tar xvfz $FILE -) - -create_archive() ( - # move required components - FILE="binfmt-${1}.tar.gz" - tar cfz $FILE \ - binfmt \ - qemu-i386 \ - qemu-${2} - - shasum -a 512 "${FILE}" >"${FILE}.sha512sum" -) - -copy_to_dist() ( - # copy to dist dir - cp "binfmt-${1}.tar.gz" $DIST_DIR - cp "binfmt-${1}.tar.gz.sha512sum" $DIST_DIR - - # cleanup - rm -rf "$TEMP_DIR/*" -) - -download() ( - download_binfmt "${1}" - download_qemu "${1}" - create_archive "${1}" "${2}" - copy_to_dist "${1}" -) - -# download -download $ARCH $BINFMT_ARCH - -echo download successful -ls -lh $DIST_DIR diff --git a/scripts/cloud-image.sh b/scripts/cloud-image.sh deleted file mode 100755 index fc674a8..0000000 --- a/scripts/cloud-image.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash - -set -eux - -# external variables that must be set -echo vars: $ARCH - -# switch to dist dir -SCRIPT_DIR=$(realpath "$(dirname "$(dirname $0)")") -DIST_DIR="${SCRIPT_DIR}/dist/img" -mkdir -p $DIST_DIR - -cd $DIST_DIR - -download() ( - FILE="ubuntu-${UBUNTU_VERSION}-minimal-cloudimg-${1}.img" - URL="https://cloud-images.ubuntu.com/minimal/releases/${UBUNTU_CODENAME}/release/${FILE}" - curl -LO $URL - - shasum -a 512 "${FILE}" >"${FILE}.sha512sum" -) - -# download -download $ARCH - -# validate -( - curl -sL https://cloud-images.ubuntu.com/minimal/releases/${UBUNTU_CODENAME}/release/SHA256SUMS | grep "${ARCH}\.img$" | shasum -a 256 --check --status -) - -echo download successful -ls -lh . diff --git a/scripts/containerd.sh b/scripts/containerd.sh deleted file mode 100755 index 5e467bc..0000000 --- a/scripts/containerd.sh +++ /dev/null @@ -1,76 +0,0 @@ -#!/usr/bin/env bash - -if [ "$RUNTIME" != "containerd" ]; then - exit 0 -fi - -set -ex - -SCRIPT_DIR=$(realpath "$(dirname "$(dirname $0)")") -DIST_DIR="${SCRIPT_DIR}/dist/containerd" -mkdir -p $DIST_DIR - -TEMP_DIR=/tmp/containerd -mkdir -p $TEMP_DIR -cd $TEMP_DIR - -download_containerd() ( - # download archive - FILE="nerdctl-full-${NERDCTL_VERSION}-linux-${1}.tar.gz" - URL="https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/${FILE}" - curl -LO $URL - - # validate - curl -sL "https://github.com/containerd/nerdctl/releases/download/v${NERDCTL_VERSION}/SHA256SUMS" | grep "${FILE}" | shasum -a 256 --check --status - - # extract - tar xvfz $FILE -) - -download_flannel() ( - # download archive - FILE="cni-plugin-flannel-linux-${1}-v${FLANNEL_MINI_VERSION}.tgz" - URL="https://github.com/flannel-io/cni-plugin/releases/download/v${FLANNEL_VERSION}/${FILE}" - curl -LO $URL - - # validate - curl -sL "${URL}.sha512" | shasum -a 512 --check --status - - # extract - tar xvfz "$FILE" - mv "flannel-${1}" libexec/cni/flannel -) - -create_archive() ( - # move required components - FILE="containerd-utils-${1}.tar.gz" - tar cfz $FILE \ - bin \ - lib \ - libexec \ - share - - shasum -a 512 "${FILE}" >"${FILE}.sha512sum" -) - -copy_to_dist() ( - # copy to dist dir - cp "containerd-utils-${1}.tar.gz" $DIST_DIR - cp "containerd-utils-${1}.tar.gz.sha512sum" $DIST_DIR - - # cleanup - rm -rf "$TEMP_DIR/*" -) - -download() ( - download_containerd "${1}" - download_flannel "${1}" - create_archive "${1}" - copy_to_dist "${1}" -) - -# download -download $ARCH - -echo download successful -ls -lh $DIST_DIR diff --git a/scripts/image.docker.sh b/scripts/image.docker.sh deleted file mode 100755 index c5bf403..0000000 --- a/scripts/image.docker.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -set -eux - -# disable apt prompts -export DEBIAN_FRONTEND=noninteractive - -# external variables that must be set -echo vars: $ARCH $BINFMT_ARCH $UBUNTU_VERSION $DOCKER_VERSION $RUNTIME - -# computed variables -SCRIPT_DIR=$(realpath "$(dirname "$(dirname $0)")") - -# dependencies in case of cross-arch -docker run --privileged --rm tonistiigi/binfmt --install $BINFMT_ARCH - -# build disk image -docker run --rm --privileged \ - --platform linux/$ARCH \ - --volume $SCRIPT_DIR:/build \ - --env ARCH \ - --env BINFMT_ARCH \ - --env UBUNTU_VERSION \ - --env DOCKER_VERSION \ - --env RUNTIME \ - ubuntu:${UBUNTU_VERSION} /build/scripts/image.sh diff --git a/scripts/image.sh b/scripts/image.sh index 48a0c25..2f7518c 100755 --- a/scripts/image.sh +++ b/scripts/image.sh @@ -2,66 +2,171 @@ set -eux -# disable apt prompts -export DEBIAN_FRONTEND=noninteractive - # external variables that must be set -echo vars: $ARCH $BINFMT_ARCH $UBUNTU_VERSION $DOCKER_VERSION $RUNTIME - -FILENAME="ubuntu-${UBUNTU_VERSION}-minimal-cloudimg-${ARCH}" +echo vars: $DOCKER_VERSION $RUNTIME -SCRIPT_DIR=$(realpath "$(dirname "$(dirname $0)")") -IMG_DIR="$SCRIPT_DIR/dist/img" +BUILD_DIR="/build" CHROOT_DIR=/mnt/colima-img -FILE="$IMG_DIR/$FILENAME" - -install_dependencies() ( - apt-get update - apt-get install -y file fdisk libdigest-sha-perl qemu-utils -) +IMAGE_FILE="${BUILD_DIR}/${IMAGE_FILE}" +RAW_FILE="${IMAGE_FILE%.*}-${RUNTIME}.raw" convert_file() ( - qemu-img convert -p -f qcow2 -O raw $FILE.img $FILE.raw + qemu-img convert -p -f qcow2 -O raw "${IMAGE_FILE}" "${RAW_FILE}" ) -extract_partition_offset() ( - fdisk -l $FILE.raw | grep "$FILE.raw1 " | awk -F' ' '{print $2}' -) +mount_partitions() { + mkdir -p "$CHROOT_DIR" -mount_partition() ( - mkdir -p $CHROOT_DIR - mount -o loop,offset=$(($1 * 512)) $FILE.raw $CHROOT_DIR -) + LOOP_DEV=$(losetup -Pf --show "${RAW_FILE}") + LOOP_NAME="${LOOP_DEV##*/}" + FOUND_ROOT="0:0" + + # generate partition device nodes + for part_sys_path in "/sys/class/block/${LOOP_NAME}/${LOOP_NAME}p"*; do + PART_PART_ENTRY_UUID="" + if [ -e "$part_sys_path" ]; then + PART_NAME="${part_sys_path##*/}" + read -r PART_MAJ_MIN < "$part_sys_path/dev" + if [ ! -e "/dev/$PART_NAME" ]; then + mknod "/dev/$PART_NAME" b "${PART_MAJ_MIN%:*}" "${PART_MAJ_MIN#*:}" + fi + # create part uuid dev entries so mounting is easy later + eval "$(blkid -p -o export /dev/"${PART_NAME}" | sed 's/^/PART_/')" + # Ubuntu uses labels, make fstab consistent with Debian, which uses partuuid + if [[ -n "${PART_PART_ENTRY_UUID:-}" ]] ; then + mkdir -p /dev/disk/by-partuuid + ln -s ../../"${PART_NAME}" /dev/disk/by-partuuid/"${PART_PART_ENTRY_UUID}" + # stage fstab updates so Ubuntu fstab (labels) is like Debian (partuuids) + if [[ -n "${PART_LABEL:-}" ]] ; then + echo "sed -i 's/LABEL=${PART_LABEL}/PARTUUID=${PART_PART_ENTRY_UUID}/' ${CHROOT_DIR}/etc/fstab" >> /tmp/fstab_update + fi + fi + # guess which is the root dev (usually it's 1), but the largest ext4 partition makes more sense + if [[ "${PART_TYPE}" == "ext4" ]] && [[ "${FOUND_ROOT##*:}" -lt "${PART_FSSIZE}" ]] ; then + FOUND_ROOT="/dev/${PART_NAME}:${PART_FSSIZE}" + fi + fi + done + + # mount root so we can mount everything else + mount "${FOUND_ROOT%%:*}" "${CHROOT_DIR}" -unmount_partition() ( + # update fstab with partuuids + if [ -f /tmp/fstab_update ]; then + source /tmp/fstab_update + fi + + mount --bind /proc "$CHROOT_DIR/proc" + mount --bind /dev "$CHROOT_DIR/dev" + mount --bind /sys "$CHROOT_DIR/sys" + + chroot_exec mount -t devpts devpts /dev/pts + chroot_exec mount -a +} + +cleanup() { + rm -f "${RAW_FILE}" + if [ -n "$CHROOT_DIR" ] && mountpoint -q "$CHROOT_DIR"; then + unmount_partitions ||: + fi + rm -rf /dev/disk + if [ -n "$LOOP_DEV" ] && losetup "$LOOP_DEV" >/dev/null 2>&1; then + losetup -d "$LOOP_DEV" + fi +} +trap 'cleanup' EXIT + +unmount_partitions() ( + chroot_exec umount -a -t nocgroup,nodevtmpfs,noproc,nosysfs + umount $CHROOT_DIR/sys + umount $CHROOT_DIR/proc umount $CHROOT_DIR ) chroot_exec() ( - chroot $CHROOT_DIR "$@" + chroot $CHROOT_DIR env DEBIAN_FRONTEND=noninteractive "$@" ) + +apt_update_source() { + chroot_exec apt-get -q update -y -o Dir::Etc::sourcelist="sources.list.d/${1}" -o Dir::Etc::sourceparts="-" -o APT::Get::List-Cleanup="0" +} + +apt_keyring_zabbly() { + test -f "$CHROOT_DIR/etc/apt/keyrings/zabbly.asc" || { + chroot_exec install -m 0755 -d /etc/apt/keyrings + chroot_exec curl -fsSLo zabbly.asc https://pkgs.zabbly.com/key.asc + chroot_exec install -m 0644 zabbly.asc /etc/apt/keyrings/zabbly.asc + chroot_exec rm -f zabbly.asc + } +} + +apt_source_zabbly_incus() { + apt_keyring_zabbly + chroot_exec sh -c 'cat < /etc/apt/sources.list.d/zabbly-incus-stable.sources +Enabled: yes +Types: deb +URIs: https://pkgs.zabbly.com/incus/stable +Suites: $(. /etc/os-release && echo ${VERSION_CODENAME}) +Components: main +Architectures: $(dpkg --print-architecture) +Signed-By: /etc/apt/keyrings/zabbly.asc +EOF' + apt_update_source zabbly-incus-stable.sources +} + +apt_source_zabbly_kernel() { + apt_keyring_zabbly + chroot_exec sh -c 'cat < /etc/apt/sources.list.d/zabbly-kernel-stable.sources +Enabled: yes +Types: deb +URIs: https://pkgs.zabbly.com/kernel/stable +Suites: $(. /etc/os-release && echo ${VERSION_CODENAME}) +Components: main +Architectures: $(dpkg --print-architecture) +Signed-By: /etc/apt/keyrings/zabbly.asc +EOF' + apt_update_source zabbly-kernel-stable.sources +} + install_packages() ( - # necessary - chroot_exec mount -t proc proc /proc - chroot_exec mount -t devpts devpts /dev/pts + echo 'Dpkg::Use-Pty "0"; Dpkg::Progress-Fancy="0";' > $CHROOT_DIR/etc/apt/apt.conf.d/colima + DPKG_ARCH=$(chroot_exec dpkg --print-architecture) # internet chroot_exec mv /etc/resolv.conf /etc/resolv.conf.bak echo 'nameserver 1.1.1.1' >$CHROOT_DIR/etc/resolv.conf + # minimal + echo 'Binary::apt::APT::Keep-Downloaded-Packages "0";' > $CHROOT_DIR/etc/apt/apt.conf.d/01_nocache + echo 'APT::Install-Recommends "0"; APT::Install-Suggests "0"; Acquire::Retries "5"; Acquire::Retries::Delay::Maximum "30";' >> $CHROOT_DIR/etc/apt/apt.conf.d/colima + if [ "${DIST}" == "debian" ] ; then + chroot_exec sed -i 's/Components: main$/Components: main contrib non-free-firmware/' /etc/apt/sources.list.d/debian.sources + chroot_exec apt-get -q purge -y groff-base man-db manpages + fi + cat >$CHROOT_DIR/etc/dpkg/dpkg.cfg.d/01_nodoc <<"EOF" +path-exclude=/usr/share/locale/*; +path-exclude=/usr/share/man/*; +path-exclude=/usr/share/doc/*; +path-include=/usr/share/doc/*/copyright; +EOF + pushd $CHROOT_DIR + find usr/share/doc/ usr/share/man usr/share/locale/ -mindepth 1 -delete + popd + # prepare packages - chroot_exec apt-get update + chroot_exec apt-get -q update # packages common to all runtimes, to prevent from final purging - chroot_exec apt-get install -y iptables socat sshfs cloud-init lsb-release python3-apt gnupg curl wget dnsmasq + chroot_exec apt-get -q install -y \ + iptables socat sshfs cloud-init lsb-release python3-apt gnupg curl wget dnsmasq rsync \ + htop inetutils-ping dnsutils net-tools netcat-openbsd telnet vim-tiny nano # none if [ "$RUNTIME" == "none" ]; then ( - chroot_exec apt-get install -y htop inetutils-ping dnsutils net-tools netcat-openbsd telnet vim-tiny nano - chroot_exec apt-get purge -y dmsetup xz-utils + chroot_exec apt-get -q purge -y xz-utils ) fi @@ -72,7 +177,7 @@ install_packages() ( chroot_exec sh /tmp/get-docker.sh --version $DOCKER_VERSION chroot_exec rm /tmp/get-docker.sh chroot_exec apt-mark hold docker-ce docker-ce-cli containerd.io - chroot_exec apt-get purge -y dmsetup xz-utils + chroot_exec apt-get -q purge -y xz-utils ) fi @@ -80,37 +185,48 @@ install_packages() ( if [ "$RUNTIME" == "containerd" ]; then ( cd /tmp - tar Cxfz ${CHROOT_DIR}/usr/local /build/dist/containerd/containerd-utils-${ARCH}.tar.gz + tar Cxfz ${CHROOT_DIR}/usr/local "${BUILD_DIR}/${CONTAINERD_ARCHIVE}" chroot_exec mkdir -p /opt/cni chroot_exec mv /usr/local/libexec/cni /opt/cni/bin - chroot_exec apt-get purge -y dmsetup xz-utils + chroot_exec apt-get -q purge -y xz-utils ) fi # incus if [ "$RUNTIME" == "incus" ]; then ( - chroot_exec mkdir -p /etc/apt/keyrings/ - chroot_exec curl -fsSL https://pkgs.zabbly.com/key.asc -o /etc/apt/keyrings/zabbly.asc - chroot_exec sh -c 'cat < /etc/apt/sources.list.d/zabbly-incus-stable.sources -Enabled: yes -Types: deb -URIs: https://pkgs.zabbly.com/incus/stable -Suites: $(. /etc/os-release && echo ${VERSION_CODENAME}) -Components: main -Architectures: $(dpkg --print-architecture) -Signed-By: /etc/apt/keyrings/zabbly.asc + apt_source_zabbly_incus -EOF' - chroot_exec apt-get update - chroot_exec apt-get install -y htop inetutils-ping dnsutils net-tools netcat-openbsd telnet vim-tiny nano - chroot_exec apt-get install -y incus incus-base incus-client incus-extra incus-ui-canonical zfsutils-linux btrfs-progs lvm2 thin-provisioning-tools - chroot_exec apt-mark hold incus incus-base incus-client incus-extra incus-ui-canonical zfsutils-linux btrfs-progs lvm2 thin-provisioning-tools + if [ "${DIST}" == "debian" ] ; then + chroot_exec apt-get -q install -y dpkg-dev "linux-headers-cloud-${DPKG_ARCH}" + chroot_exec apt-get -q install -y zfs-dkms + fi + + chroot_exec apt-get -q install -y incus incus-base incus-client incus-extra incus-ui-canonical zfsutils-linux btrfs-progs lvm2 thin-provisioning-tools nftables + chroot_exec apt-mark hold incus incus-base incus-client incus-extra incus-ui-canonical zfsutils-linux btrfs-progs lvm2 thin-provisioning-tools nftables ) fi - chroot_exec apt-get purge -y apport console-setup-linux dbus-user-session liblocale-gettext-perl lxd-agent-loader lxd-installer parted pciutils pollinate python3-gi snapd ssh-import-id - chroot_exec apt-get purge -y ubuntu-advantage-tools ubuntu-cloud-minimal ubuntu-drivers-common ubuntu-release-upgrader-core unattended-upgrades systemd-resolved + chroot_exec apt-get -q purge -y apport console-setup-linux dbus-user-session liblocale-gettext-perl parted pciutils pollinate python3-gi snapd ssh-import-id + chroot_exec apt-get -q purge -y unattended-upgrades systemd-resolved + chroot_exec apt-get -q purge -y apt-listchanges apt-utils reportbug screen whiptail xml-core + chroot_exec apt-get -q purge -y bash-completion + + if [ "$DIST" == "ubuntu" ] ; then + chroot_exec apt-get -q purge -y lxd-agent-loader lxd-installer ubuntu-advantage-tools ubuntu-cloud-minimal ubuntu-drivers-common ubuntu-release-upgrader-core + fi + + # cleanup any old kernel versions that may be been upgraded + KERNEL_VERSIONS=$(chroot_exec dpkg-query -W -f='${Package}\n' | grep -E '^linux-image-[0-9]' | sed 's/^linux-image-//' | sort -V) + LATEST_VERSION=$(echo "$KERNEL_VERSIONS" | tail -n 1) + for V in $KERNEL_VERSIONS; do + if [ "$V" != "$LATEST_VERSION" ]; then + chroot_exec apt-get -q purge -y "linux-image-$V" + chroot_exec apt-get -q purge -y "linux-headers-$V" ||: + chroot_exec apt-get -q purge -y "linux-modules-$V" ||: + chroot_exec apt-get -q purge -y "linux-modules-extra-$V" ||: + fi + done chroot_exec apt-get autoremove -y chroot_exec apt-get clean -y @@ -119,41 +235,57 @@ EOF' # binfmt ( cd /tmp - tar xfz /build/dist/binfmt/binfmt-${ARCH}.tar.gz - chown root:root binfmt qemu-i386 qemu-${BINFMT_ARCH} - mv binfmt qemu-i386 qemu-${BINFMT_ARCH} ${CHROOT_DIR}/usr/bin + tar xfz "${BUILD_DIR}/${BINFMT_ARCHIVE}" + chown root:root binfmt qemu-* + mv binfmt qemu-* ${CHROOT_DIR}/usr/bin ) + # console messages to lima serial log + # https://github.com/lima-vm/lima/issues/1659 + mkdir -p $CHROOT_DIR/etc/default/grub.d + if [[ "$DIST" == "ubuntu" ]]; then + cat >"$CHROOT_DIR"/etc/default/grub.d/99-lima-console.cfg <<"EOF" +GRUB_CMDLINE_LINUX_DEFAULT="console=hvc0 earlycon" +EOF + elif [[ "$DIST" == "debian" ]]; then + cat >"$CHROOT_DIR"/etc/default/grub.d/99-lima-console.cfg <<"EOF" +GRUB_CMDLINE_LINUX="$GRUB_CMDLINE_LINUX console=hvc0 earlycon" +EOF + fi + + chroot_exec update-grub + # enable vsock modules at boot cat >${CHROOT_DIR}/etc/modules-load.d/vsock.conf <"${filename}.gz.sha512sum") + pigz -9 -n -f "${RAW_FILE}" + shasum -a 512 "${RAW_FILE}.gz" >"${RAW_FILE}.gz.sha512sum" ) # perform all actions -install_dependencies convert_file -mount_partition "$(extract_partition_offset)" +mount_partitions install_packages -unmount_partition +unmount_partitions compress_file diff --git a/scripts/test.sh b/scripts/test.sh new file mode 100755 index 0000000..c692921 --- /dev/null +++ b/scripts/test.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash + +set -eu + +IMAGE_FILE=$1 +BN_IMAGE=$(basename "$IMAGE_FILE") +DIST=${BN_IMAGE%%-*} +COMPRESSION=${BN_IMAGE##*.} + +BN_IMAGE_T=$(basename -s ".$COMPRESSION" "$BN_IMAGE") +IMAGE_TYPE=${BN_IMAGE_T##*.} + +BN_IMAGE_T=$(basename -s ".$IMAGE_TYPE" "$BN_IMAGE_T") +RUNTIME=${BN_IMAGE_T##*-} + +echo "DIST=$DIST RUNTIME=$RUNTIME IMAGE=$BN_IMAGE TYPE=$IMAGE_TYPE " + +export COLIMA_PROFILE="test" + +cleanup() { + read -r -t 10 -n 1 -p "Press any key to keep test instance running (deleting in 10s)..." && exit + echo "==== Delete runtime" + colima delete -d -v -f ||: +} +trap 'cleanup' EXIT + +echo "==== Starting runtime" +# gnu timeout (coreutils) is not available everywhere, this perl does the same thing +perl -e 'alarm 60; exec @ARGV' colima start \ + -r "$RUNTIME" \ + --disk-image "$IMAGE_FILE" \ + --force-disk-image + +# check for failed services +echo "==== Test: systemd" +colima exec -- systemctl --failed + +echo "==== Test: $RUNTIME" + +if [ "$RUNTIME" == "incus" ] ; then + colima exec -- incus info + + echo "==== Test: launch, exec, stop, delete" + colima exec -- incus launch images:debian/13 debian13 + colima exec -- incus list + colima exec -- incus exec debian13 cat /etc/os-release + colima exec -- incus stop debian13 + colima exec -- incus delete debian13 + colima exec -- incus list +fi + +if [ "$RUNTIME" == "docker" ] ; then + colima exec -- docker system info +fi