summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rwxr-xr-xscripts/iso-to-oci126
1 files changed, 116 insertions, 10 deletions
diff --git a/scripts/iso-to-oci b/scripts/iso-to-oci
index 86dd8687..9f1b2a82 100755
--- a/scripts/iso-to-oci
+++ b/scripts/iso-to-oci
@@ -1,11 +1,58 @@
#!/bin/bash
cleanup() {
+ # spin() hides the cursor while animating - make sure it comes back if we
+ # are interrupted mid step
+ if [[ -t 1 ]]; then
+ tput cnorm 2>/dev/null || true
+ fi
if [[ -n "${WORKDIR:-}" && -d "${WORKDIR:-}" ]]; then
rm -rf "${WORKDIR}"
fi
}
+# Run a long running command while animating a spinner - unpacking an ISO and
+# compressing the rootfs keep the script silent for minutes and it looks hung.
+# Only animate when stdout is a terminal, "make oci" in CI or a piped log gets
+# the plain message instead. Output of the wrapped command is discarded as
+# before, but unlike before its exit code is checked.
+spin() {
+ # exit codes accepted as success in addition to 0 - unsquashfs(1) returns 2
+ # for non fatal errors, which is what we always get when running as a
+ # regular user (device nodes and xattrs can not be restored)
+ local allow_rc=""
+ if [[ "$1" == "--allow-rc" ]]; then
+ allow_rc="$2"; shift 2
+ fi
+ local message="$1"; shift
+ local frames='|/-\'
+ local rc=0 i=0
+
+ if [[ ! -t 1 ]]; then
+ echo "I: ${message}"
+ "$@" >/dev/null 2>&1 || rc=$?
+ else
+ "$@" >/dev/null 2>&1 &
+ local pid=$!
+ tput civis 2>/dev/null
+ while kill -0 "${pid}" 2>/dev/null; do
+ printf '\r%s %s' "${frames:i++%4:1}" "${message}"
+ sleep 0.1
+ done
+ wait "${pid}" || rc=$?
+ tput cnorm 2>/dev/null
+ # replace the spinner with the very same line the non interactive run
+ # would have printed
+ printf '\r\033[K'
+ echo "I: ${message}"
+ fi
+
+ if (( rc )) && [[ " ${allow_rc} " != *" ${rc} "* ]]; then
+ echo "E: ${message} failed with exit code ${rc}"
+ exit 1
+ fi
+}
+
if [[ "$#" -ne 1 ]]; then
echo "Usage: $0 <path-to-iso>"
exit 2
@@ -44,15 +91,15 @@ ROOTFS="${WORKDIR}/iso"
UNSQUASHFS="${WORKDIR}/unsquashfs"
mkdir -p "${ROOTFS}/live" "${UNSQUASHFS}"
-echo "I: extracting ISO metadata"
-xorriso -osirrox on -indev "${ISO}" -extract /version.json "${ROOTFS}/version.json" >/dev/null 2>&1
+spin "extracting ISO metadata - version.json" \
+ xorriso -osirrox on -indev "${ISO}" -extract /version.json "${ROOTFS}/version.json"
-echo "I: extracting squashfs image"
-xorriso -osirrox on -indev "${ISO}" -extract /live/filesystem.squashfs "${ROOTFS}/live/filesystem.squashfs" >/dev/null 2>&1
+spin "extracting ISO data - squashfs image" \
+ xorriso -osirrox on -indev "${ISO}" -extract /live/filesystem.squashfs "${ROOTFS}/live/filesystem.squashfs"
# create directory, unpack squashfs filesystem, get ISO version
-echo "I: extracting squashfs content"
-unsquashfs -follow -dest "${UNSQUASHFS}/" "${ROOTFS}/live/filesystem.squashfs" >/dev/null 2>&1
+spin --allow-rc 2 "extracting squashfs content" \
+ unsquashfs -follow -dest "${UNSQUASHFS}/" "${ROOTFS}/live/filesystem.squashfs"
VERSION="$(jq --raw-output .version "${ROOTFS}/version.json")"
# older ISOs predate ARM64 support and carry no architecture in version.json
ARCH="$(jq --raw-output '.architecture // "amd64"' "${ROOTFS}/version.json")"
@@ -69,6 +116,29 @@ sed -i 's/^LANG=.*$/LANG=C.UTF-8/' "${UNSQUASHFS}/etc/default/locale"
printf 'Welcome to VyOS - \\n \\l\n\n' > "${UNSQUASHFS}/etc/issue"
: > "${UNSQUASHFS}/etc/issue.net"
+# adjust systemd units for containerized operation - this is what the
+# containerlab documentation asks users to do in their Dockerfile, but as we
+# ship a ready made rootfs there is no build stage where "systemctl" could be
+# run. Masking and disabling is nothing but symlink handling below /etc/systemd,
+# so we can do it offline (and cross-architecture) right here.
+#
+# masked units: getty(8) would fight with the container console, auditd(8) has
+# no business inside a container as it requires the audit netlink socket and
+# atopacctd(8) enables BSD process accounting via acct(2) which is global to
+# the kernel and not namespaced - the first container to start it wins, every
+# other one fails and turns "systemctl is-system-running" into "degraded",
+# which is exactly what the containerlab healthcheck looks at
+for unit in getty.target auditd.service atopacct.service; do
+ ln -sf /dev/null "${UNSQUASHFS}/etc/systemd/system/${unit}"
+done
+
+# disabled units: kea-dhcp-ddns-server(8) is started via a "WantedBy" symlink
+# and would fail on boot, VyOS enables it on demand from the CLI
+for unit in kea-dhcp-ddns-server.service; do
+ find "${UNSQUASHFS}/etc/systemd/system" -name "${unit}" \
+ \( -path '*.wants/*' -o -path '*.requires/*' \) -delete
+done
+
# optional step: Decrease docker image size by deleting not necessary files for container
rm -rf "${UNSQUASHFS}/boot"
rm -rf "${UNSQUASHFS}/lib/firmware/"
@@ -76,9 +146,38 @@ rm -rf "${UNSQUASHFS}/usr/lib/x86_64-linux-gnu/libwireshark.so*"
rm -rf "${UNSQUASHFS}/lib/modules/*-vyos"
rm -rf "${UNSQUASHFS}/root/.gnupg"
+# podman(8) is useless inside the container as we do not support running
+# containers in containers - the CLI nodes are removed below anyway. Dropping
+# the runtime and its network helpers (netavark, aardvark-dns) saves about
+# 110 MiB of the uncompressed rootfs. Nothing but the container CLI calls
+# these, so they can go entirely - including the systemd units, the quadlet
+# generators and the configuration shipped by containers-common
+rm -f "${UNSQUASHFS}/etc/systemd/system/default.target.wants/podman.service"
+rm -f "${UNSQUASHFS}/usr/lib/systemd/system/podman.service"
+rm -f "${UNSQUASHFS}/usr/lib/systemd/system/podman.socket"
+rm -f "${UNSQUASHFS}/usr/lib/systemd/system/netavark-dhcp-proxy.service"
+rm -f "${UNSQUASHFS}/usr/lib/systemd/system/netavark-dhcp-proxy.socket"
+rm -f "${UNSQUASHFS}/usr/lib/systemd/system/netavark-firewalld-reload.service"
+rm -f "${UNSQUASHFS}/usr/lib/systemd/system-generators/podman-system-generator"
+rm -f "${UNSQUASHFS}/usr/lib/systemd/user-generators/podman-user-generator"
+rm -f "${UNSQUASHFS}/usr/lib/tmpfiles.d/podman.conf"
+rm -rf "${UNSQUASHFS}/usr/lib/podman"
+rm -rf "${UNSQUASHFS}/usr/libexec/podman"
+rm -f "${UNSQUASHFS}/usr/bin/podman" "${UNSQUASHFS}/usr/bin/podman-remote" \
+ "${UNSQUASHFS}/usr/bin/podmansh" "${UNSQUASHFS}/usr/bin/conmon" \
+ "${UNSQUASHFS}/usr/bin/fuse-overlayfs" "${UNSQUASHFS}/usr/bin/runc" \
+ "${UNSQUASHFS}/usr/sbin/runc"
+rm -rf "${UNSQUASHFS}/etc/containers"
+rm -rf "${UNSQUASHFS}/usr/share/containers"
+
# delete features not supported in container - only remove the node.def files,
-# this is sufficient to not make the feature pop up on the CLI
+# this is sufficient to not make the feature pop up on the CLI. The container
+# operational mode commands go as well, they would only greet the user with a
+# traceback now that podman is gone
rm -rf "${UNSQUASHFS}/opt/vyatta/share/vyatta-cfg/templates/container"
+for tree in add connect delete generate restart show update; do
+ rm -rf "${UNSQUASHFS}/opt/vyatta/share/vyatta-op/templates/${tree}/container"
+done
rm -rf "${UNSQUASHFS}/opt/vyatta/share/vyatta-cfg/templates/system/console"
rm -rf "${UNSQUASHFS}/opt/vyatta/share/vyatta-cfg/templates/system/option/kernel"
rm -rf "${UNSQUASHFS}/opt/vyatta/share/vyatta-cfg/templates/system/option/startup-beep"
@@ -93,12 +192,19 @@ rmdir "${UNSQUASHFS}/config" || { echo "E: /config is not an empty directory"; e
ln -s /opt/vyatta/etc/config "${UNSQUASHFS}/config"
# create docker image
-echo "I: generate OCI container image ${OCI_IMAGE}"
-XZ_OPT="-T0" tar --directory "${UNSQUASHFS}" --create . --xz --file "${OCI_IMAGE}"
+spin "generating OCI container image ${OCI_IMAGE}" \
+ env XZ_OPT=-T0 tar --directory "${UNSQUASHFS}" --create . --xz --file "${OCI_IMAGE}"
+
+# containerlab uses the image healthcheck to determine when a VyOS node is
+# ready, see https://containerlab.dev/manual/kinds/vyosnetworks_vyos/
+# A rootfs tarball carries no OCI image configuration, so this can only be
+# attached when the tarball is imported
+HEALTHCHECK='HEALTHCHECK --start-period=10s CMD systemctl is-system-running'
echo "I: to import the previously generated OCI image to your local images run:"
echo ""
-echo " docker import --platform=linux/$ARCH $OCI_IMAGE vyos/vyos:$VERSION --change 'CMD [\"/sbin/init\"]'"
+echo " docker import --platform=linux/$ARCH $OCI_IMAGE vyos/vyos:$VERSION \\"
+echo " --change 'CMD [\"/sbin/init\"]' --change '$HEALTHCHECK'"
echo ""
cleanup