diff options
| author | Viacheslav Hletenko <v.gletenko@vyos.io> | 2026-09-07 13:10:47 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-07 13:10:47 +0300 |
| commit | 210f88164d634c8da78d8e7db4f694af8b29a8f3 (patch) | |
| tree | 17d1c18cff02cbd99bf83d292a84e3a8fb546617 /scripts | |
| parent | d2c5731563c7ca0b7eeeb3c6537ad0d9fb504ec3 (diff) | |
| parent | f4a7506710b172267e02ce6dd146066f61b5850c (diff) | |
| download | vyos-build-210f88164d634c8da78d8e7db4f694af8b29a8f3.tar.gz vyos-build-210f88164d634c8da78d8e7db4f694af8b29a8f3.zip | |
Merge pull request #1289 from c-po/containerlab-cleanup
oci: T9269: mask systemd services for container startup and add healthcheck
Diffstat (limited to 'scripts')
| -rwxr-xr-x | scripts/iso-to-oci | 126 |
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 |
