From bb40933184e6cd184be46996268e0f34c75de8fc Mon Sep 17 00:00:00 2001 From: "synacktra.work@gmail.com" Date: Tue, 18 Nov 2025 17:42:19 +0530 Subject: [PATCH 01/11] feat: allow local iso boot and enable cloud-init for auto installation --- Dockerfile | 10 +- assets/meta-data | 0 assets/user-data | 30 ++++++ src/define.sh | 235 -------------------------------------------- src/entry.sh | 1 - src/install.sh | 143 +++++++-------------------- src/remaster_iso.py | 118 ++++++++++++++++++++++ 7 files changed, 191 insertions(+), 346 deletions(-) create mode 100644 assets/meta-data create mode 100644 assets/user-data delete mode 100644 src/define.sh create mode 100644 src/remaster_iso.py diff --git a/Dockerfile b/Dockerfile index 2496aea0..0f216fc7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -59,8 +59,17 @@ RUN set -eu && \ echo "$VERSION_ARG" > /run/version && \ rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* +RUN apt-get update && \ + apt-get install -y python3 python3-venv && \ + python3 -m venv /opt/isoenv && \ + /opt/isoenv/bin/pip install --no-cache-dir pycdlib && \ + ln -sf /opt/isoenv/bin/python /usr/local/bin/pycdlib-python && \ + ln -sf /opt/isoenv/bin/pycdlib /usr/local/bin/pycdlib-python && \ + rm -rf /var/lib/apt/lists/* + COPY --chmod=755 ./src /run/ COPY --chmod=755 ./web /var/www/ +COPY --chmod=644 ./assets /run/assets COPY --chmod=664 ./web/conf/defaults.json /usr/share/novnc COPY --chmod=664 ./web/conf/mandatory.json /usr/share/novnc COPY --chmod=744 ./web/conf/nginx.conf /etc/nginx/default.conf @@ -70,7 +79,6 @@ ADD --chmod=755 "https://github.com/qemus/fiano/releases/download/v${VERSION_UTK VOLUME /storage EXPOSE 22 5900 8006 -ENV BOOT="alpine" ENV CPU_CORES="2" ENV RAM_SIZE="2G" ENV DISK_SIZE="64G" diff --git a/assets/meta-data b/assets/meta-data new file mode 100644 index 00000000..e69de29b diff --git a/assets/user-data b/assets/user-data new file mode 100644 index 00000000..3887a822 --- /dev/null +++ b/assets/user-data @@ -0,0 +1,30 @@ +#cloud-config +autoinstall: + version: 1 + locale: en_US.UTF-8 + keyboard: + layout: us + identity: + hostname: ubuntu + realname: 'Docker' + username: docker + password: '$6$6D0zLV90NlsL5iPm$BPmXpp1oryQ1/FfonDMRELPxuJrZ7O0KJAcK5pJ8q0jXQcf7YGZiI6C5rPf.1toXHBfuq5Qx3JlQsajSfifxP.' + storage: + layout: + name: lvm + sizing-policy: all + + ssh: + install-server: true + allow-pw: true + + user-data: + disable_root: false + + packages: + - qemu-guest-agent + - ubuntu-desktop-minimal + + late-commands: + - curtin in-target --target=/target -- systemctl enable gdm + - curtin in-target --target=/target -- systemctl set-default graphical.target diff --git a/src/define.sh b/src/define.sh deleted file mode 100644 index a091aa95..00000000 --- a/src/define.sh +++ /dev/null @@ -1,235 +0,0 @@ -#!/usr/bin/env bash -set -Eeuo pipefail - -pipe() { - local code="99" - msg="Failed to connect to $1, reason:" - - curl --disable --silent --max-time 15 --fail --location "${1}" || { - code="$?" - } - - case "${code,,}" in - "6" ) error "$msg could not resolve host!" ;; - "7" ) error "$msg no internet connection available!" ;; - "28" ) error "$msg connection timed out!" ;; - "99" ) return 0 ;; - *) error "$msg $code" ;; - esac - - return 1 -} - -getURL() { - local id="${1/ /}" - local ret="$2" - local url="" - local arm="" - local name="" - local body="" - local version="" - - case "${id,,}" in - "alma" | "almalinux" | "alma-linux" ) - version="9" - name="AlmaLinux" - if [[ "$ret" == "url" ]]; then - url="https://repo.almalinux.org/almalinux/${version}/live/x86_64/AlmaLinux-${version}-latest-x86_64-Live-GNOME.iso" - arm="https://repo.almalinux.org/almalinux/${version}/live/aarch64/AlmaLinux-${version}-latest-aarch64-Live-GNOME.iso" - fi ;; - "alpine" | "alpinelinux" | "alpine-linux" ) - name="Alpine Linux" - if [[ "$ret" == "url" ]]; then - body=$(pipe "https://dl-cdn.alpinelinux.org/alpine/latest-stable/releases/x86_64/latest-releases.yaml") || exit 65 - version=$(echo "$body" | awk '/"Xen"/{found=0} {if(found) print} /"Virtual"/{found=1}' | grep 'version:' | awk '{print $2}') - url="https://dl-cdn.alpinelinux.org/alpine/latest-stable/releases/x86_64/alpine-virt-$version-x86_64.iso" - arm="https://dl-cdn.alpinelinux.org/alpine/latest-stable/releases/aarch64/alpine-virt-$version-aarch64.iso" - fi ;; - "arch" | "archlinux" | "arch-linux" ) - name="Arch Linux" - if [[ "$ret" == "url" ]]; then - url="https://geo.mirror.pkgbuild.com/iso/latest/archlinux-x86_64.iso" - fi ;; - "cachy" | "cachyos" ) - name="CachyOS" - if [[ "$ret" == "url" ]]; then - body=$(pipe "https://cachyos.org/download/") || exit 65 - url=$(echo "$body" | tr '&' '\n' | grep "ISO/desktop" | grep -v 'iso.sha' | grep -v 'iso.sig' | cut -d';' -f2) - arm=$(echo "$body" | tr '&' '\n' | grep "ISO/handheld" | grep -v 'iso.sha' | grep -v 'iso.sig' | cut -d';' -f2) - fi ;; - "centos" | "centosstream" | "centos-stream" ) - name="CentOS Stream" - if [[ "$ret" == "url" ]]; then - body=$(pipe "https://linuxsoft.cern.ch/centos-stream/") || exit 65 - version=$(echo "$body" | grep "\-stream" | cut -d'"' -f 6 | cut -d'-' -f 1 | head -n 1) - url="https://mirrors.xtom.de/centos-stream/$version-stream/BaseOS/x86_64/iso/CentOS-Stream-$version-latest-x86_64-dvd1.iso" - arm="https://mirrors.xtom.de/centos-stream/$version-stream/BaseOS/aarch64/iso/CentOS-Stream-$version-latest-aarch64-dvd1.iso" - fi ;; - "debian" ) - name="Debian" - if [[ "$ret" == "url" ]]; then - body=$(pipe "https://cdimage.debian.org/debian-cd/") || exit 65 - version=$(echo "$body" | grep '\.[0-9]/' | cut -d'>' -f 9 | cut -d'/' -f 1) - url="https://cdimage.debian.org/debian-cd/current-live/amd64/iso-hybrid/debian-live-$version-amd64-standard.iso" - arm="https://cdimage.debian.org/debian-cd/current/arm64/iso-dvd/debian-$version-arm64-DVD-1.iso" - fi ;; - "fedora" | "fedoralinux" | "fedora-linux" ) - name="Fedora Linux" - if [[ "$ret" == "url" ]]; then - body=$(pipe "https://getfedora.org/releases.json") || exit 65 - version=$(echo "$body" | jq -r 'map(.version) | unique | .[]' | sed 's/ /_/g' | sed '/_Beta/d' | sort -r | head -n 1) - url=$(echo "$body" | jq -r "map(select(.arch==\"x86_64\" and .version==\"${version}\" and .variant==\"Workstation\" and .subvariant==\"Workstation\" )) | .[].link" | grep -m 1 .iso) - arm=$(echo "$body" | jq -r "map(select(.arch==\"aarch64\" and .version==\"${version}\" and .variant==\"Workstation\" and .subvariant==\"Workstation\" )) | .[].link" | grep -m 1 .iso) - fi ;; - "gentoo" | "gentoolinux" | "gentoo-linux" ) - name="Gentoo Linux" - if [[ "$ret" == "url" ]]; then - if [[ "${PLATFORM,,}" != "arm64" ]]; then - body=$(pipe "https://distfiles.gentoo.org/releases/amd64/autobuilds/latest-iso.txt") || exit 65 - version=$(echo "$body" | grep livegui | cut -d' ' -f1) - url="https://distfiles.gentoo.org/releases/amd64/autobuilds/$version" - else - body=$(pipe "https://distfiles.gentoo.org/releases/arm64/autobuilds/latest-qcow2.txt") || exit 65 - version=$(echo "$body" | grep cloudinit | cut -d' ' -f1) - arm="https://distfiles.gentoo.org/releases/arm64/autobuilds/$version" - fi - fi ;; - "kali" | "kalilinux" | "kali-linux" ) - name="Kali Linux" - if [[ "$ret" == "url" ]]; then - body=$(pipe "https://cdimage.kali.org/current/?C=M;O=D") || exit 65 - version=$(echo "$body" | grep -o ">kali-linux-.*-live-amd64.iso" | head -n 1 | cut -c 2-) - url="https://cdimage.kali.org/current/$version" - version=$(echo "$body" | grep -o ">kali-linux-.*-live-arm64.iso" | head -n 1 | cut -c 2-) - arm="https://cdimage.kali.org/current/$version" - fi ;; - "kubuntu" ) - name="Kubuntu" - if [[ "$ret" == "url" ]]; then - body=$(pipe "https://api.launchpad.net/devel/ubuntu/series") || exit 65 - version=$(echo "$body" | jq -r '.entries | .[] | select(.status=="Current Stable Release").version') - url="https://cdimage.ubuntu.com/kubuntu/releases/${version}/release/kubuntu-${version}-desktop-amd64.iso" - fi ;; - "lmde" ) - version="6" - name="Linux Mint Debian Edition" - if [[ "$ret" == "url" ]]; then - url="https://pub.linuxmint.io/debian/lmde-${version}-cinnamon-64bit.iso" - fi ;; - "macos" | "osx" ) - name="macOS" - error "To install $name use: https://github.com/dockur/macos" && return 1 ;; - "mint" | "linuxmint" | "linux-mint" ) - version="22.2" - name="Linux Mint" - if [[ "$ret" == "url" ]]; then - url="https://pub.linuxmint.io/stable/${version}/linuxmint-${version}-cinnamon-64bit.iso" - fi ;; - "manjaro" ) - name="Manjaro" - if [[ "$ret" == "url" ]]; then - body=$(pipe "https://gitlab.manjaro.org/web/iso-info/-/raw/master/file-info.json") || exit 65 - url=$(echo "$body" | jq -r .official.plasma.image) - fi ;; - "mx" | "mxlinux" | "mx-linux" ) - name="MX Linux" - if [[ "$ret" == "url" ]]; then - version=$(curl --disable -Ils "https://sourceforge.net/projects/mx-linux/files/latest/download" | grep -i 'location:' | cut -d? -f1 | cut -d_ -f1 | cut -d- -f3) || exit 65 - url="https://mirror.umd.edu/mxlinux-iso/MX/Final/Xfce/MX-${version}_x64.iso" - fi ;; - "nixos" ) - name="NixOS" - if [[ "$ret" == "url" ]]; then - body=$(pipe "https://nix-channels.s3.amazonaws.com/?delimiter=/") || exit 65 - version=$(echo "$body" | grep -o -E 'nixos-[[:digit:]]+\.[[:digit:]]+' | cut -d- -f2 | sort -nru | head -n 1) - url="https://channels.nixos.org/nixos-$version/latest-nixos-gnome-x86_64-linux.iso" - arm="https://channels.nixos.org/nixos-$version/latest-nixos-gnome-aarch64-linux.iso" - fi ;; - "opensuse" | "open-suse" | "suse" ) - name="OpenSUSE" - if [[ "$ret" == "url" ]]; then - body=$(pipe "https://download.opensuse.org/distribution/leap/") || exit 65 - version=$(echo "$body" | grep 'class="name"' | cut -d '/' -f2 | grep -v 42 | sort -r | head -n 1) - url="https://download.opensuse.org/distribution/leap/$version/installer/iso/agama-installer.x86_64-Leap_${version}.iso" - arm="https://download.opensuse.org/distribution/leap/$version/installer/iso/agama-installer.aarch64-Leap_${version}.iso" - fi ;; - "rocky" | "rockylinux" | "rocky-linux" ) - version="9" - name="Rocky Linux" - if [[ "$ret" == "url" ]]; then - url="https://dl.rockylinux.org/pub/rocky/${version}/live/x86_64/Rocky-${version}-Workstation-x86_64-latest.iso" - arm="https://dl.rockylinux.org/pub/rocky/${version}/live/aarch64/Rocky-${version}-Workstation-aarch64-latest.iso" - fi ;; - "slack" | "slackware" ) - name="Slackware" - if [[ "$ret" == "url" ]]; then - url="https://slackware.nl/slackware-live/slackware64-current-live/slackware64-live-current.iso" - fi ;; - "tails" ) - name="Tails" - if [[ "$ret" == "url" ]]; then - body=$(pipe "https://tails.net/install/v2/Tails/amd64/stable/latest.json") || exit 65 - url=$(echo "$body" | jq -r '.installations[0]."installation-paths"[]|select(.type=="iso")|."target-files"[0].url') - fi ;; - "ubuntu" | "ubuntu-desktop" ) - name="Ubuntu Desktop" - if [[ "$ret" == "url" ]]; then - body=$(pipe "https://api.launchpad.net/devel/ubuntu/series") || exit 65 - version=$(echo "$body" | jq -r '.entries | .[] | select(.status=="Current Stable Release").version') - url="https://releases.ubuntu.com/${version}/ubuntu-${version}-desktop-amd64.iso" - arm="https://cdimage.ubuntu.com/releases/${version}/release/ubuntu-${version}-desktop-arm64.iso" - fi ;; - "ubuntus" | "ubuntu-server") - name="Ubuntu Server" - if [[ "$ret" == "url" ]]; then - body=$(pipe "https://api.launchpad.net/devel/ubuntu/series") || exit 65 - version=$(echo "$body" | jq -r '.entries | .[] | select(.status=="Current Stable Release").version') - url="https://releases.ubuntu.com/${version}/ubuntu-${version}-live-server-amd64.iso" - arm="https://cdimage.ubuntu.com/releases/${version}/release/ubuntu-${version}-live-server-arm64.iso" - fi ;; - "windows" ) - name="Windows" - error "To install $name use: https://github.com/dockur/windows" && return 1 ;; - "xubuntu" ) - name="Xubuntu" - if [[ "$ret" == "url" ]]; then - body=$(pipe "https://api.launchpad.net/devel/ubuntu/series") || exit 65 - version=$(echo "$body" | jq -r '.entries | .[] | select(.status=="Current Stable Release").version') - url="https://cdimages.ubuntu.com/xubuntu/releases/${version}/release/xubuntu-${version}-desktop-amd64.iso" - fi ;; - "zorin" | "zorinos" | "zorin-os" ) - name="Zorin OS" - if [[ "$ret" == "url" ]]; then - url="https://mirrors.edge.kernel.org/zorinos-isos/18/Zorin-OS-18-Core-64-bit.iso" - fi ;; - esac - - case "${ret,,}" in - "name" ) - echo "$name" - ;; - "url" ) - - if [[ "${PLATFORM,,}" != "arm64" ]]; then - if [ -n "$name" ] && [ -z "$url" ]; then - error "No image for $name available!" - return 1 - fi - else - if [ -n "$name" ] && [ -z "$arm" ]; then - error "No image for $name is available for ARM64 yet! " - return 1 - fi - fi - - if [[ "${PLATFORM,,}" != "arm64" ]]; then - echo "$url" - else - echo "$arm" - fi ;; - esac - - return 0 -} - -return 0 diff --git a/src/entry.sh b/src/entry.sh index 184dade1..b5bdbfaf 100755 --- a/src/entry.sh +++ b/src/entry.sh @@ -11,7 +11,6 @@ cd /run . utils.sh # Load functions . reset.sh # Initialize system . server.sh # Start webserver -. define.sh # Define images . install.sh # Download image . disk.sh # Initialize disks . display.sh # Initialize graphics diff --git a/src/install.sh b/src/install.sh index a289fa68..bc880796 100644 --- a/src/install.sh +++ b/src/install.sh @@ -1,6 +1,32 @@ #!/usr/bin/env bash set -Eeuo pipefail +echo "DEBUG: install.sh is running, BOOT=$BOOT" + +if [ -f "/custom.iso" ]; then + echo "DEBUG: Found /custom.iso, remastering with pycdlib..." + + REMASTERED_ISO="/tmp/ubuntu-autoinstall.iso" + + info "Remastering Ubuntu ISO for automated installation..." + /opt/isoenv/bin/python /run/remaster_iso.py \ + --src /custom.iso \ + --dst "$REMASTERED_ISO" \ + --config-dir /run/assets + + if [ ! -f "$REMASTERED_ISO" ]; then + error "Remastered ISO not created at $REMASTERED_ISO" + exit 42 + fi + + info "Remastered ISO created successfully at $REMASTERED_ISO" + BOOT="$REMASTERED_ISO" + REMASTERED=1 + echo "DEBUG: BOOT updated to $BOOT" + return 0 +fi + + getBase() { local base="${1%%\?*}" @@ -115,73 +141,6 @@ detectType() { return 1 } -delay() { - - local i - local delay="$1" - local msg="Retrying failed download in X seconds..." - - info "${msg/X/$delay}" - - for i in $(seq "$delay" -1 1); do - html "${msg/X/$i}" - sleep 1 - done - - return 0 -} - -downloadFile() { - - local url="$1" - local base="$2" - local name="$3" - local msg rc total size progress - - local dest="$STORAGE/$base" - - # Check if running with interactive TTY or redirected to docker log - if [ -t 1 ]; then - progress="--progress=bar:noscroll" - else - progress="--progress=dot:giga" - fi - - if [ -z "$name" ]; then - msg="Downloading image" - info "Downloading $base..." - else - msg="Downloading $name" - info "Downloading $name..." - fi - - html "$msg..." - - /run/progress.sh "$dest" "0" "$msg ([P])..." & - - { wget "$url" -O "$dest" --continue -q --timeout=30 --no-http-keep-alive --show-progress "$progress"; rc=$?; } || : - - fKill "progress.sh" - - if (( rc == 0 )) && [ -f "$dest" ]; then - total=$(stat -c%s "$dest") - size=$(formatBytes "$total") - if [ "$total" -lt 100000 ]; then - error "Invalid image file: is only $size ?" && return 1 - fi - html "Download finished successfully..." - return 0 - fi - - msg="Failed to download $url" - (( rc == 3 )) && error "$msg , cannot write file (disk full?)" && return 1 - (( rc == 4 )) && error "$msg , network failure!" && return 1 - (( rc == 8 )) && error "$msg , server issued an error response!" && return 1 - - error "$msg , reason: $rc" - return 1 -} - convertImage() { local source_file=$1 @@ -301,24 +260,22 @@ findFile "boot" "img" && return 0 findFile "boot" "raw" && return 0 findFile "boot" "iso" && return 0 findFile "boot" "qcow2" && return 0 -findFile "custom" "iso" && return 0 + +# Skip custom.iso check if we already remastered it +if [ -z "${REMASTERED:-}" ]; then + findFile "custom" "iso" && return 0 +fi if hasDisk; then BOOT="none" return 0 fi -if [[ "${BOOT}" == \"*\" || "${BOOT}" == \'*\' ]]; then - VERSION="${BOOT:1:-1}" -fi - BOOT=$(expr "$BOOT" : "^\ *\(.*[^ ]\)\ *$") -if [ -z "$BOOT" ] || [[ "$BOOT" == *"example.com/"* ]]; then - - BOOT="alpine" - warn "no value specified for the BOOT variable, defaulting to \"${BOOT}\"." - +if [ -z "$BOOT" ]; then + error "No BOOT value specified! Provide Ubuntu ISO via volume mount." + exit 64 fi folder=$(getFolder "$BOOT") @@ -339,19 +296,6 @@ if [ -d "$STORAGE" ]; then fi -name=$(getURL "$BOOT" "name") || exit 34 - -if [ -n "$name" ]; then - - msg="Retrieving latest $name version..." - info "$msg" && html "$msg..." - - url=$(getURL "$BOOT" "url") || exit 34 - - [ -n "$url" ] && BOOT="$url" - -fi - if [[ "$BOOT" != *"."* ]]; then if [ -z "$BOOT" ]; then error "No BOOT value specified!" @@ -361,31 +305,12 @@ if [[ "$BOOT" != *"."* ]]; then exit 64 fi -if [[ "${BOOT,,}" != "http"* ]]; then - error "Invalid BOOT value specified, \"$BOOT\" is not a valid URL!" && exit 64 -fi - if ! makeDir "$STORAGE"; then error "Failed to create directory \"$STORAGE\" !" && exit 33 fi -find "$STORAGE" -maxdepth 1 -type f \( -iname '*.rom' -or -iname '*.vars' \) -delete -find "$STORAGE" -maxdepth 1 -type f \( -iname 'data.*' -or -iname 'qemu.*' \) -delete - base=$(getBase "$BOOT") -rm -f "$STORAGE/$base" - -if ! downloadFile "$BOOT" "$base" "$name"; then - delay 5 - if ! downloadFile "$BOOT" "$base" "$name"; then - delay 10 - if ! downloadFile "$BOOT" "$base" "$name"; then - rm -f "$STORAGE/$base" && exit 60 - fi - fi -fi - case "${base,,}" in *".gz" | *".gzip" | *".xz" | *".7z" | *".zip" | *".rar" | *".lzma" | *".bz" | *".bz2" ) info "Extracting $base..." diff --git a/src/remaster_iso.py b/src/remaster_iso.py new file mode 100644 index 00000000..2dbc343f --- /dev/null +++ b/src/remaster_iso.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +import argparse +import io +from pathlib import Path + +import pycdlib +from pycdlib.pycdlibexception import PyCdlibException + + +def remaster_iso(src_iso: Path, dst_iso: Path, config_dir: Path): + user_data_file = config_dir / "user-data" + meta_data_file = config_dir / "meta-data" + + if not src_iso.is_file(): + raise FileNotFoundError(f"Source ISO not found: {src_iso}") + + if not dst_iso.parent.is_dir(): + try: + dst_iso.parent.mkdir(parents=True) + except Exception: + raise NotADirectoryError(f"Destination ISO directory not found: {dst_iso.parent}") + + if not user_data_file.is_file(): + raise FileNotFoundError(f"user-data not found in {config_dir}") + + if not meta_data_file.is_file(): + raise FileNotFoundError(f"meta-data not found in {config_dir}") + + print(f"Opening source ISO: {src_iso}") + iso = pycdlib.PyCdlib() + iso.open(str(src_iso)) + + # Read existing GRUB config (ISO9660 path, uppercase + ;1) + buf = io.BytesIO() + iso.get_file_from_iso_fp(buf, iso_path="/BOOT/GRUB/GRUB.CFG;1") + data = buf.getvalue() + + # Patch kernel cmdline + needle = b" quiet ---" + replacement = b" quiet autoinstall ds=nocloud\\;s=/cdrom/nocloud/ ---" + + if needle not in data: + needle = b" ---" + new = data.replace(needle, replacement) + + # Replace GRUB.CFG in ISO + iso.rm_file(iso_path="/BOOT/GRUB/GRUB.CFG;1", rr_name="grub.cfg") + iso.add_fp(io.BytesIO(new), len(new), iso_path="/BOOT/GRUB/GRUB.CFG;1", rr_name="grub.cfg") + + # Add NoCloud seed at /cdrom/nocloud/{user-data,meta-data} + print("Adding cloud-init configuration...") + try: + iso.add_directory(iso_path="/NOCLOUD", rr_name="nocloud") + except PyCdlibException: + # Directory may already exist; ignore + pass + + user_data = user_data_file.read_bytes() + meta_data = meta_data_file.read_bytes() + + # Add user-data + try: + iso.rm_file(iso_path="/NOCLOUD/USER_DATA;1", rr_name="user-data") + except PyCdlibException: + pass + iso.add_fp( + io.BytesIO(user_data), + len(user_data), + iso_path="/NOCLOUD/USER_DATA;1", + rr_name="user-data" + ) + + # Add meta-data + try: + iso.rm_file(iso_path="/NOCLOUD/META_DATA;1", rr_name="meta-data") + except PyCdlibException: + pass + iso.add_fp( + io.BytesIO(meta_data), + len(meta_data), + iso_path="/NOCLOUD/META_DATA;1", + rr_name="meta-data" + ) + + # Write remastered ISO + print(f"Writing remastered ISO to: {dst_iso}") + iso.write(str(dst_iso)) + iso.close() + + print("ISO remastering completed successfully!") + + +def main(): + parser = argparse.ArgumentParser( + description="Remaster Ubuntu ISO with autoinstall configuration using pycdlib" + ) + parser.add_argument( + "--src", + required=True, + help="Source Ubuntu ISO file path" + ) + parser.add_argument( + "--dst", + required=True, + help="Destination path for remastered ISO" + ) + parser.add_argument( + "--config-dir", + required=True, + help="Directory containing user-data and meta-data" + ) + + args = parser.parse_args() + remaster_iso(Path(args.src), Path(args.dst), Path(args.config_dir)) + + +if __name__ == "__main__": + main() From d547ced48002c372b18911627158f239d15f8bc5 Mon Sep 17 00:00:00 2001 From: "synacktra.work@gmail.com" Date: Tue, 18 Nov 2025 17:44:03 +0530 Subject: [PATCH 02/11] chore: add compose configuration and update readme --- compose.prepare.yml | 18 +++ compose.yml | 14 +- readme.md | 376 +++++++------------------------------------- 3 files changed, 83 insertions(+), 325 deletions(-) create mode 100644 compose.prepare.yml diff --git a/compose.prepare.yml b/compose.prepare.yml new file mode 100644 index 00000000..860c39e8 --- /dev/null +++ b/compose.prepare.yml @@ -0,0 +1,18 @@ +services: + prepare-ubuntu: + image: qemu-local:latest + container_name: prepare-ubuntu + environment: + STORAGE_DIR: "${STORAGE_DIR:?Error: required env var not set}" + ISO_FILE: "${ISO_FILE:?Error: required env var not set}" + BOOT: "/custom.iso" + devices: + - /dev/kvm + cap_add: + - NET_ADMIN + ports: + - 8006:8006 + stop_grace_period: 2m + volumes: + - ${ISO_FILE}:/custom.iso + - ${STORAGE_DIR}:/storage diff --git a/compose.yml b/compose.yml index 7b70341c..203ef8a1 100644 --- a/compose.yml +++ b/compose.yml @@ -1,17 +1,15 @@ services: - qemu: - image: qemux/qemu - container_name: qemu + ubuntu: + image: qemu-local:latest + container_name: ubuntu environment: - BOOT: "mint" + STORAGE_DIR: "${STORAGE_DIR:?Error: required env var not set}" devices: - /dev/kvm - - /dev/net/tun cap_add: - NET_ADMIN ports: - 8006:8006 - volumes: - - ./qemu:/storage - restart: always stop_grace_period: 2m + volumes: + - ${STORAGE_DIR}:/storage diff --git a/readme.md b/readme.md index ce9d5daf..f67fd5fb 100644 --- a/readme.md +++ b/readme.md @@ -1,230 +1,125 @@ -

QEMU
-
-
+

Local Ubuntu on Docker
-[![Build]][build_url] -[![Version]][tag_url] -[![Size]][tag_url] -[![Package]][pkg_url] -[![Pulls]][hub_url] -

-Docker container for running virtual machines using QEMU. - -## Features ✨ - - - Web-based viewer to control the machine directly from your browser +Local Ubuntu Desktop inside a Docker container. - - Supports `.iso`, `.img`, `.qcow2`, `.vhd`, `.vhdx`, `.vdi`, `.vmdk` and `.raw` disk formats +## Usage 🐳 - - High-performance options (like KVM acceleration, kernel-mode networking, IO threading, etc.) to achieve near-native speed +### Via Docker Compose: -## Usage 🐳 +> See [compose.yml](compose.yml) for the complete configuration. -##### Via Docker Compose: - -```yaml -services: - qemu: - image: qemux/qemu - container_name: qemu - environment: - BOOT: "mint" - devices: - - /dev/kvm - - /dev/net/tun - cap_add: - - NET_ADMIN - ports: - - 8006:8006 - volumes: - - ./qemu:/storage - restart: always - stop_grace_period: 2m +To prepare a golden image from a custom ISO: +```bash +STORAGE_DIR=/path/to/storage ISO_FILE=/path/to/ubuntu.iso \ + docker compose -f compose.prepare.yml up ``` -##### Via Docker CLI: - +Start the container (using the golden image): ```bash -docker run -it --rm --name qemu -e "BOOT=mint" -p 8006:8006 --device=/dev/kvm --device=/dev/net/tun --cap-add NET_ADMIN -v "${PWD:-.}/qemu:/storage" --stop-timeout 120 docker.io/qemux/qemu +STORAGE_DIR=/path/to/storage docker compose up ``` -##### Via Kubernetes: +### Via Docker CLI: -```shell -kubectl apply -f https://raw.githubusercontent.com/qemus/qemu/refs/heads/master/kubernetes.yml +```bash +docker run -it --rm \ + -p 8006:8006 \ + --device=/dev/kvm \ + --cap-add NET_ADMIN \ + --mount type=bind,source=./ubuntu.iso,target=/custom.iso \ + --stop-timeout 120 \ + qemu-local:latest ``` -##### Via Github Codespaces: +## Compatibility ⚙️ -[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/qemus/qemu) +| **Product** | **Platform** | | +|---|---|---| +| Docker Engine | Linux| ✅ | +| Docker Desktop | Linux | ❌ | +| Docker Desktop | macOS | ❌ | +| Docker Desktop | Windows 11 | ✅ | +| Docker Desktop | Windows 10 | ❌ | ## FAQ 💬 ### How do I use it? - Very simple! These are the steps: + **Download Ubuntu LTS Server ISO:** - - Set the `BOOT` variable to the [operating system](#how-do-i-select-the-operating-system) you want to install. + 1. Visit [Ubuntu Server Downloads](https://ubuntu.com/download/server) + 2. Download Server ISO file [~3GB] - - Start the container and connect to [port 8006](http://127.0.0.1:8006/) using your web browser. + **Then follow these steps:** - - You will see the screen and can now install the OS of your choice using your keyboard and mouse. - - Enjoy your brand new machine, and don't forget to star this repo! + - Start the container and connect to [port 8006](http://localhost:8006) using your web browser. -### How do I select the operating system? + - Sit back and relax while the magic happens, the whole installation will be performed fully automatic with cloud-init autoinstall. - You can use the `BOOT` environment variable in order to specify the operating system that will be downloaded: + - Once you see the desktop, your Ubuntu installation is ready for use. - ```yaml - environment: - BOOT: "mint" - ``` - - Select from the values below: - - | **Value** | **Operating System** | **Size** | - |---|---|---| - | `alma` | Alma Linux | 2.2 GB | - | `alpine` | Alpine Linux | 60 MB | - | `arch` | Arch Linux | 1.2 GB | - | `cachy` | CachyOS | 2.6 GB | - | `centos` | CentOS | 7.0 GB | - | `debian` | Debian | 3.3 GB | - | `fedora` | Fedora | 2.3 GB | - | `gentoo` | Gentoo | 3.6 GB | - | `kali` | Kali Linux | 3.8 GB | - | `kubuntu` | Kubuntu | 4.4 GB | - | `mint` | Linux Mint | 2.8 GB | - | `manjaro` | Manjaro | 4.1 GB | - | `mx` | MX Linux | 2.2 GB | - | `nixos` | NixOS | 2.4 GB | - | `suse` | OpenSUSE | 1.0 GB | - | `rocky` | Rocky Linux | 2.1 GB | - | `slack` | Slackware | 3.7 GB | - | `tails` | Tails | 1.5 GB | - | `ubuntu` | Ubuntu Desktop | 6.0 GB | - | `ubuntus` | Ubuntu Server | 3.0 GB | - | `xubuntu` | Xubuntu | 4.0 GB | - | `zorin` | Zorin OS | 3.8 GB | - -### How can I use my own image? - - If you want to download an operating system that is not in the list above, you can set the `BOOT` variable to the URL of the image: - - ```yaml - environment: - BOOT: "https://dl-cdn.alpinelinux.org/alpine/v3.19/releases/x86_64/alpine-virt-3.19.1-x86_64.iso" - ``` - - The `BOOT` URL accepts files in any of the following formats: - - | **Extension** | **Format** | - |---|---| - | `.img` | Raw | - | `.raw` | Raw | - | `.iso` | Optical | - | `.qcow2` | QEMU | - | `.vmdk` | VMware | - | `.vhd` | VirtualPC | - | `.vhdx` | Hyper-V | - | `.vdi` | VirtualBox | - - It will also accept files such as `.img.gz`, `.qcow2.xz`, `.iso.zip` and many more, because it will automatically extract compressed files. - - Alternatively you can use a local image file directly, by binding it in your compose file: - - ```yaml - volumes: - - ./example.iso:/boot.iso - ``` - - This way you can supply either a `/boot.iso`, `/boot.img` or a `/boot.qcow2` file. The value of `BOOT` will be ignored in this case. + Enjoy your brand new machine, and don't forget to star this repo! ### How do I change the storage location? - To change the storage location, include the following bind mount in your compose file: + To change the storage location, modify the `STORAGE_DIR` environment variable: - ```yaml - volumes: - - ./qemu:/storage + ```bash + STORAGE_DIR=./ubuntu docker compose up ``` - Replace the example path `./qemu` with the desired storage folder or named volume. - ### How do I change the size of the disk? To expand the default size of 64 GB, add the `DISK_SIZE` setting to your compose file and set it to your preferred capacity: ```yaml environment: - DISK_SIZE: "128G" + DISK_SIZE: "256G" ``` > [!TIP] > This can also be used to resize the existing disk to a larger capacity without any data loss. -### How do I change the amount of CPU or RAM? - - By default, the VM will be allowed to use 2 CPU cores and 2 GB of RAM. +### How do I share files with the host? - If you want to adjust this, you can specify the desired amount using the following environment variables: + To share files with the host, add the following volume to your compose file: ```yaml - environment: - RAM_SIZE: "8G" - CPU_CORES: "4" + volumes: + - /home/user/example:/shared ``` -### How do I boot ARM64 images? - - You can use the [qemu-arm](https://github.com/qemus/qemu-arm/) container to run ARM64-based images. - -### How do I boot Windows? - - Use [dockur/windows](https://github.com/dockur/windows) instead, as it includes all the drivers required during installation, amongst many other features. + Then start the container and execute the following command in Ubuntu: -### How do I boot macOS? - - Use [dockur/macos](https://github.com/dockur/macos) instead, as it uses all the right settings and automatically downloads the installation files. - -### How do I boot without UEFI? + ```shell + sudo mount -t 9p -o trans=virtio shared /mnt/example + ``` - By default, the machine will boot with UEFI enabled. If your OS does not support that, you can boot with a legacy BIOS: + Now the `/home/user/example` directory on the host will be available as `/mnt/example` in Ubuntu. - ```yaml - environment: - BOOT_MODE: "legacy" - ``` +> [!TIP] +> You can add this mount command to `/etc/fstab` for automatic mounting on boot. -### How do I boot without VirtIO drivers? +### How do I change the amount of CPU or RAM? - By default, the machine makes use of `virtio-scsi` drives for performance reasons, and even though most Linux kernels bundle the necessary driver for this device, that may not always be the case for other operating systems. + By default, the container will be allowed to use a maximum of 2 CPU cores and 4 GB of RAM. - If your machine fails to detect the hard drive, you can modify your compose file to use `virtio-blk` instead: + If you want to adjust this, you can specify the desired amount using the following environment variables: ```yaml environment: - DISK_TYPE: "blk" + RAM_SIZE: "8G" + CPU_CORES: "4" ``` - If it still fails to boot, you can set the value to `ide` to emulate a IDE drive, which is relatively slow but requires no drivers and is compatible with almost every system. - ### How do I verify if my system supports KVM? - First check if your software is compatible using this chart: - - | **Product** | **Linux** | **Win11** | **Win10** | **macOS** | - |---|---|---|---|---| - | Docker CLI | ✅ | ✅ | ❌ | ❌ | - | Docker Desktop | ❌ | ✅ | ❌ | ❌ | - | Podman CLI | ✅ | ✅ | ❌ | ❌ | - | Podman Desktop | ✅ | ✅ | ❌ | ❌ | + Only Linux and Windows 11 support KVM virtualization, macOS and Windows 10 do not unfortunately. - After that you can run the following commands in Linux to check your system: + You can run the following commands in Linux to check your system: ```bash sudo apt install cpu-checker @@ -239,157 +134,4 @@ kubectl apply -f https://raw.githubusercontent.com/qemus/qemu/refs/heads/master/ - you are not using a cloud provider, as most of them do not allow nested virtualization for their VPS's. - If you did not receive any error from `kvm-ok` but the container still complains about a missing KVM device, it could help to add `privileged: true` to your compose file (or `sudo` to your `docker` command) to rule out any permission issue. - -### How do I expose network ports? - - When using bridge networking, you can expose ports by adding them to your compose file. If you want to be able to connect to the SSH service of the machine for example, you would add it like this: - - ```yaml - ports: - - 2222:22 - ``` - - This will make port 2222 on your host redirect to port 22 of the virtual machine. - - When using user-mode networking (for example when running under Podman), you will also need to add those ports to the `USER_PORTS` variable like this: - - ```yaml - environment: - USER_PORTS: "22,80,443" - ``` - -### How do I assign an individual IP address to the container? - - By default, the container uses bridge networking, which shares the IP address with the host. - - If you want to assign an individual IP address to the container, you can create a macvlan network as follows: - - ```bash - docker network create -d macvlan \ - --subnet=192.168.0.0/24 \ - --gateway=192.168.0.1 \ - --ip-range=192.168.0.100/28 \ - -o parent=eth0 vlan - ``` - - Be sure to modify these values to match your local subnet. - - Once you have created the network, change your compose file to look as follows: - - ```yaml - services: - qemu: - container_name: qemu - .... - networks: - vlan: - ipv4_address: 192.168.0.100 - - networks: - vlan: - external: true - ``` - - An added benefit of this approach is that you won't have to perform any port mapping anymore, since all ports will be exposed by default. - -> [!IMPORTANT] -> This IP address won't be accessible from the Docker host due to the design of macvlan, which doesn't permit communication between the two. If this is a concern, you need to create a [second macvlan](https://blog.oddbit.com/post/2018-03-12-using-docker-macvlan-networks/#host-access) as a workaround. - -### How can the VM acquire an IP address from my router? - - After configuring the container for [macvlan](#how-do-i-assign-an-individual-ip-address-to-the-container), it is possible for the VM to become part of your home network by requesting an IP from your router, just like a real PC. - - To enable this mode, in which the container and the VM will have separate IP addresses, add the following lines to your compose file: - - ```yaml - environment: - DHCP: "Y" - devices: - - /dev/vhost-net - device_cgroup_rules: - - 'c *:* rwm' - ``` - -### How do I add multiple disks? - - To create additional disks, modify your compose file like this: - - ```yaml - environment: - DISK2_SIZE: "32G" - DISK3_SIZE: "64G" - volumes: - - ./example2:/storage2 - - ./example3:/storage3 - ``` - -### How do I pass-through a disk? - - It is possible to pass-through disk devices or partitions directly by adding them to your compose file in this way: - - ```yaml - devices: - - /dev/sdb:/disk1 - - /dev/sdc1:/disk2 - ``` - - Use `/disk1` if you want it to become your main drive, and use `/disk2` and higher to add them as secondary drives. - -### How do I pass-through a USB device? - - To pass-through a USB device, first lookup its vendor and product id via the `lsusb` command, then add them to your compose file like this: - - ```yaml - environment: - ARGUMENTS: "-device usb-host,vendorid=0x1234,productid=0x1234" - devices: - - /dev/bus/usb - ``` - -### How do I share files with the host? - - To share files with the host, first ensure that your guest OS has `9pfs` support compiled in or available as a kernel module. If so, add the following volume to your compose file: - - ```yaml - volumes: - - ./example:/shared - ``` - - Then start the container and execute the following command in the guest: - - ```shell - mount -t 9p -o trans=virtio shared /mnt/example - ``` - - Now the `./example` directory on the host will be available as `/mnt/example` in the guest. - -### How can I provide custom arguments to QEMU? - - You can create the `ARGUMENTS` environment variable to provide additional arguments to QEMU at runtime: - - ```yaml - environment: - ARGUMENTS: "-device usb-tablet" - ``` - - If you want to see the full command-line arguments used, you can set: - - ```yaml - environment: - DEBUG: "Y" - ``` - -## Stars 🌟 -[![Stars](https://starchart.cc/qemus/qemu.svg?variant=adaptive)](https://starchart.cc/qemus/qemu) - -[build_url]: https://github.com/qemus/qemu/ -[hub_url]: https://hub.docker.com/r/qemux/qemu/ -[tag_url]: https://hub.docker.com/r/qemux/qemu/tags -[pkg_url]: https://github.com/qemus/qemu/pkgs/container/qemu - -[Build]: https://github.com/qemus/qemu/actions/workflows/build.yml/badge.svg -[Size]: https://img.shields.io/docker/image-size/qemux/qemu/latest?color=066da5&label=size -[Pulls]: https://img.shields.io/docker/pulls/qemux/qemu.svg?style=flat&label=pulls&logo=docker -[Version]: https://img.shields.io/docker/v/qemux/qemu/latest?arch=amd64&sort=semver&color=066da5 -[Package]: https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fipitio.github.io%2Fbackage%2Fqemus%2Fqemu%2Fqemu.json&query=%24.downloads&logo=github&style=flat&color=066da5&label=pulls + If you didn't receive any error from `kvm-ok` at all, but the container still complains that `/dev/kvm` is missing, it might help to add `privileged: true` to your compose file (or `--privileged` to your `run` command), to rule out any permission issue. From ffcefba8ee30769520c9afe07d1bf154828def31 Mon Sep 17 00:00:00 2001 From: "synacktra.work@gmail.com" Date: Sat, 22 Nov 2025 18:07:40 +0530 Subject: [PATCH 03/11] feat: allow custom grub configuration --- assets/grub.cfg | 12 ++++++ src/remaster_iso.py | 96 +++++++++++++++++++++++---------------------- 2 files changed, 61 insertions(+), 47 deletions(-) create mode 100644 assets/grub.cfg diff --git a/assets/grub.cfg b/assets/grub.cfg new file mode 100644 index 00000000..af808d33 --- /dev/null +++ b/assets/grub.cfg @@ -0,0 +1,12 @@ +set timeout=0 + +loadfont unicode + +set menu_color_normal=white/black +set menu_color_highlight=black/light-gray + +menuentry "Auto-Install Ubuntu Server" { + set gfxpayload=keep + linux /casper/vmlinuz quiet autoinstall ds=nocloud\;s=/cdrom/nocloud/ --- + initrd /casper/initrd +} \ No newline at end of file diff --git a/src/remaster_iso.py b/src/remaster_iso.py index 2dbc343f..d23cb6a2 100644 --- a/src/remaster_iso.py +++ b/src/remaster_iso.py @@ -1,15 +1,37 @@ #!/usr/bin/env python3 import argparse -import io +from contextlib import suppress +from io import BytesIO from pathlib import Path -import pycdlib +from pycdlib import PyCdlib from pycdlib.pycdlibexception import PyCdlibException +def add_directory(iso: PyCdlib, path_in_iso: str, name: str) -> None: + with suppress(PyCdlibException): + iso.add_directory(iso_path=path_in_iso, rr_name=name) + + +def replace_data( + iso: PyCdlib, + path_in_iso: str, + new_data: bytes, + name: str +) -> None: + """Replace a file in the ISO with new data.""" + kwargs = dict(iso_path=path_in_iso, rr_name=name) + + with suppress(PyCdlibException): + iso.rm_file(**kwargs) + + iso.add_fp(BytesIO(new_data), len(new_data), **kwargs) + + def remaster_iso(src_iso: Path, dst_iso: Path, config_dir: Path): user_data_file = config_dir / "user-data" meta_data_file = config_dir / "meta-data" + grub_cfg_file = config_dir / "grub.cfg" if not src_iso.is_file(): raise FileNotFoundError(f"Source ISO not found: {src_iso}") @@ -25,64 +47,44 @@ def remaster_iso(src_iso: Path, dst_iso: Path, config_dir: Path): if not meta_data_file.is_file(): raise FileNotFoundError(f"meta-data not found in {config_dir}") + + if not grub_cfg_file.is_file(): + raise FileNotFoundError(f"grub.cfg not found in {config_dir}") print(f"Opening source ISO: {src_iso}") - iso = pycdlib.PyCdlib() + iso = PyCdlib() iso.open(str(src_iso)) - # Read existing GRUB config (ISO9660 path, uppercase + ;1) - buf = io.BytesIO() - iso.get_file_from_iso_fp(buf, iso_path="/BOOT/GRUB/GRUB.CFG;1") - data = buf.getvalue() - - # Patch kernel cmdline - needle = b" quiet ---" - replacement = b" quiet autoinstall ds=nocloud\\;s=/cdrom/nocloud/ ---" - - if needle not in data: - needle = b" ---" - new = data.replace(needle, replacement) - - # Replace GRUB.CFG in ISO - iso.rm_file(iso_path="/BOOT/GRUB/GRUB.CFG;1", rr_name="grub.cfg") - iso.add_fp(io.BytesIO(new), len(new), iso_path="/BOOT/GRUB/GRUB.CFG;1", rr_name="grub.cfg") + grub_data = grub_cfg_file.read_bytes() + replace_data( + iso, + path_in_iso="/BOOT/GRUB/GRUB.CFG;1", + new_data=grub_data, + name="grub.cfg" + ) # Add NoCloud seed at /cdrom/nocloud/{user-data,meta-data} print("Adding cloud-init configuration...") - try: - iso.add_directory(iso_path="/NOCLOUD", rr_name="nocloud") - except PyCdlibException: - # Directory may already exist; ignore - pass - - user_data = user_data_file.read_bytes() - meta_data = meta_data_file.read_bytes() + add_directory(iso, path_in_iso="/NOCLOUD", name="nocloud") # Add user-data - try: - iso.rm_file(iso_path="/NOCLOUD/USER_DATA;1", rr_name="user-data") - except PyCdlibException: - pass - iso.add_fp( - io.BytesIO(user_data), - len(user_data), - iso_path="/NOCLOUD/USER_DATA;1", - rr_name="user-data" + user_data = user_data_file.read_bytes() + replace_data( + iso, + path_in_iso="/NOCLOUD/USER_DATA;1", + new_data=user_data, + name="user-data" ) # Add meta-data - try: - iso.rm_file(iso_path="/NOCLOUD/META_DATA;1", rr_name="meta-data") - except PyCdlibException: - pass - iso.add_fp( - io.BytesIO(meta_data), - len(meta_data), - iso_path="/NOCLOUD/META_DATA;1", - rr_name="meta-data" + meta_data = meta_data_file.read_bytes() + replace_data( + iso, + path_in_iso="/NOCLOUD/META_DATA;1", + new_data=meta_data, + name="meta-data" ) - # Write remastered ISO print(f"Writing remastered ISO to: {dst_iso}") iso.write(str(dst_iso)) iso.close() @@ -107,7 +109,7 @@ def main(): parser.add_argument( "--config-dir", required=True, - help="Directory containing user-data and meta-data" + help="Directory containing user-data, meta-data and grub.cfg files" ) args = parser.parse_args() From cf736c449c4d5e3cfa8a7eae77f78e527fd39155 Mon Sep 17 00:00:00 2001 From: "synacktra.work@gmail.com" Date: Sat, 22 Nov 2025 18:11:06 +0530 Subject: [PATCH 04/11] feat(autoinstall): enhance user-data with desktop setup and power management - Switch storage layout from LVM to direct - Remove SSH server - Install ubuntu-desktop-minimal via late-commands for better control - Enable boot splash screen - Remove unused server packages (ubuntu-server, byobu, vim, etc.) - Configure GDM auto-login for docker user - Disable LTS upgrade prompts - Setup unattended-upgrades for automatic security updates - Disable sleep, suspend, and hibernate via systemd - Configure GNOME power settings via dconf to prevent idle timeout, screensaver, and screen lock --- assets/user-data | 100 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 88 insertions(+), 12 deletions(-) diff --git a/assets/user-data b/assets/user-data index 3887a822..907f8d1c 100644 --- a/assets/user-data +++ b/assets/user-data @@ -9,22 +9,98 @@ autoinstall: realname: 'Docker' username: docker password: '$6$6D0zLV90NlsL5iPm$BPmXpp1oryQ1/FfonDMRELPxuJrZ7O0KJAcK5pJ8q0jXQcf7YGZiI6C5rPf.1toXHBfuq5Qx3JlQsajSfifxP.' + storage: layout: - name: lvm - sizing-policy: all + name: direct - ssh: - install-server: true - allow-pw: true + late-commands: + # Not sure if this is needed, but sometimes the apt-get + # update/install below fails because archive.ubuntu.com is not reachable yet + - bash -c 'for i in {1..30}; do ping -c1 archive.ubuntu.com &>/dev/null && break || sleep 2; done' + + - curtin in-target --target=/target -- apt-get update + - curtin in-target --target=/target -- apt-get upgrade -y + + # Disable needrestart during package installation to bypass kernel upgrade prompt + - curtin in-target --target=/target -- rm -f /etc/apt/apt.conf.d/99needrestart - user-data: - disable_root: false + # Install minimal Ubuntu Desktop + - curtin in-target --target=/target -- apt-get install -y --no-install-recommends ubuntu-desktop-minimal - packages: - - qemu-guest-agent - - ubuntu-desktop-minimal + # Enable the boot splash + - >- + curtin in-target --target=/target -- + sed -i /etc/default/grub -e + 's/GRUB_CMDLINE_LINUX_DEFAULT=".*/GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"/' + - curtin in-target --target=/target -- update-grub + + # Remove default filesystem and related tools not used with the suggested + # 'direct' storage layout. These may yet be required if different + # partitioning schemes are used. + - >- + curtin in-target --target=/target -- apt-get remove -y + btrfs-progs cryptsetup* lvm2 xfsprogs + + # Remove other packages present by default in Ubuntu Server but not + # normally present in Ubuntu Desktop. + - >- + curtin in-target --target=/target -- apt-get remove -y + ubuntu-server ubuntu-server-minimal + binutils byobu curl dmeventd finalrd gawk + kpartx mdadm ncurses-term needrestart open-iscsi openssh-server + sg3-utils ssh-import-id sssd thin-provisioning-tools vim tmux + sosreport screen open-vm-tools motd-news-config lxd-agent-loader + landscape-common htop git fonts-ubuntu-console ethtool - late-commands: - - curtin in-target --target=/target -- systemctl enable gdm + # Finally, remove things only installed as dependencies of other things + # we have already removed. + - curtin in-target --target=/target -- apt-get autoremove -y + + # Enable auto-login + - curtin in-target --target=/target -- mkdir -p /etc/gdm3 + - curtin in-target --target=/target -- bash -c 'echo "[daemon]" > /etc/gdm3/custom.conf' + - curtin in-target --target=/target -- bash -c 'echo "AutomaticLoginEnable=true" >> /etc/gdm3/custom.conf' + - curtin in-target --target=/target -- bash -c 'echo "AutomaticLogin=docker" >> /etc/gdm3/custom.conf' + + # Set graphical target - curtin in-target --target=/target -- systemctl set-default graphical.target + + # Disable LTS upgrade popup + - curtin in-target --target=/target -- sed -i 's/Prompt=lts/Prompt=never/' /etc/update-manager/release-upgrades + + # Setup automatic updates for the future + - curtin in-target --target=/target -- apt-get install -y unattended-upgrades + - curtin in-target --target=/target -- bash -c 'echo "APT::Periodic::Update-Package-Lists \"1\";" > /etc/apt/apt.conf.d/20auto-upgrades' + - curtin in-target --target=/target -- bash -c 'echo "APT::Periodic::Unattended-Upgrade \"1\";" >> /etc/apt/apt.conf.d/20auto-upgrades' + + # Disable systemd sleep targets + - curtin in-target --target=/target -- systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target + + # Setup dconf profile + - curtin in-target --target=/target -- mkdir -p /etc/dconf/profile + - curtin in-target --target=/target -- mkdir -p /etc/dconf/db/local.d + - curtin in-target --target=/target -- bash -c 'echo "user-db:user" > /etc/dconf/profile/user' + - curtin in-target --target=/target -- bash -c 'echo "system-db:local" >> /etc/dconf/profile/user' + + # Disable idle/session timeout + - curtin in-target --target=/target -- bash -c 'echo "[org/gnome/desktop/session]" > /etc/dconf/db/local.d/00-power' + - curtin in-target --target=/target -- bash -c 'echo "idle-delay=uint32 0" >> /etc/dconf/db/local.d/00-power' + + # Disable screensaver and lock + - curtin in-target --target=/target -- bash -c 'echo "[org/gnome/desktop/screensaver]" >> /etc/dconf/db/local.d/00-power' + - curtin in-target --target=/target -- bash -c 'echo "lock-enabled=false" >> /etc/dconf/db/local.d/00-power' + - curtin in-target --target=/target -- bash -c 'echo "idle-activation-enabled=false" >> /etc/dconf/db/local.d/00-power' + + # Disable lock screen + - curtin in-target --target=/target -- bash -c 'echo "[org/gnome/desktop/lockdown]" >> /etc/dconf/db/local.d/00-power' + - curtin in-target --target=/target -- bash -c 'echo "disable-lock-screen=true" >> /etc/dconf/db/local.d/00-power' + + # Disable power management sleep + - curtin in-target --target=/target -- bash -c 'echo "[org/gnome/settings-daemon/plugins/power]" >> /etc/dconf/db/local.d/00-power' + - curtin in-target --target=/target -- bash -c 'echo "sleep-inactive-ac-type=\"nothing\"" >> /etc/dconf/db/local.d/00-power' + - curtin in-target --target=/target -- bash -c 'echo "sleep-inactive-battery-type=\"nothing\"" >> /etc/dconf/db/local.d/00-power' + - curtin in-target --target=/target -- bash -c 'echo "idle-dim=false" >> /etc/dconf/db/local.d/00-power' + + # Update dconf database + - curtin in-target --target=/target -- dconf update \ No newline at end of file From a5d4ff3ddda865bf5c56e77a527ce617228cb173 Mon Sep 17 00:00:00 2001 From: "synacktra.work@gmail.com" Date: Sat, 22 Nov 2025 18:14:55 +0530 Subject: [PATCH 05/11] feat(install): add golden image support and persist remastered ISO - Check for ubuntu.boot marker to skip reinstallation when golden image exists - Save remastered ISO to storage as ubuntu..iso for reuse - Create ubuntu.base file with ISO filename for tracking - Create ubuntu.boot marker after remastering to enable golden image boot - Use dynamic $BOOT path instead of hardcoded /custom.iso --- src/install.sh | 47 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/src/install.sh b/src/install.sh index bc880796..4ffe94ca 100644 --- a/src/install.sh +++ b/src/install.sh @@ -3,14 +3,33 @@ set -Eeuo pipefail echo "DEBUG: install.sh is running, BOOT=$BOOT" -if [ -f "/custom.iso" ]; then - echo "DEBUG: Found /custom.iso, remastering with pycdlib..." +# Check if golden image already exists (ubuntu.boot marker) +if [ -f "$STORAGE/ubuntu.boot" ] && hasDisk; then + echo "DEBUG: Golden image ready, skipping installation" + BOOT="none" + return 0 +fi + +# Check if we have a custom ISO to remaster +if [ -f "$BOOT" ]; then + echo "DEBUG: Found $BOOT, checking for existing remastered ISO..." + + # Get ISO size for naming + ISO_SIZE="$(stat -c%s "$BOOT")" + STORAGE_ISO="$STORAGE/ubuntu.${ISO_SIZE}.iso" + + # Check if already remastered and saved + if [ -f "$STORAGE_ISO" ]; then + echo "DEBUG: Using existing remastered ISO at $STORAGE_ISO" + BOOT="$STORAGE_ISO" + return 0 + fi REMASTERED_ISO="/tmp/ubuntu-autoinstall.iso" info "Remastering Ubuntu ISO for automated installation..." /opt/isoenv/bin/python /run/remaster_iso.py \ - --src /custom.iso \ + --src "$BOOT" \ --dst "$REMASTERED_ISO" \ --config-dir /run/assets @@ -19,10 +38,24 @@ if [ -f "/custom.iso" ]; then exit 42 fi - info "Remastered ISO created successfully at $REMASTERED_ISO" - BOOT="$REMASTERED_ISO" - REMASTERED=1 - echo "DEBUG: BOOT updated to $BOOT" + # Move remastered ISO to storage + info "Saving remastered ISO to storage..." + if ! mv -f "$REMASTERED_ISO" "$STORAGE_ISO"; then + error "Failed to move ISO to storage" + exit 43 + fi + ! setOwner "$STORAGE_ISO" && error "Failed to set owner for $STORAGE_ISO" + + # Create ubuntu.base file with ISO filename + BASE_FILE="$STORAGE/ubuntu.base" + echo "ubuntu.${ISO_SIZE}.iso" > "$BASE_FILE" + ! setOwner "$BASE_FILE" && error "Failed to set owner for $BASE_FILE" + + touch "$STORAGE/ubuntu.boot" + ! setOwner "$STORAGE/ubuntu.boot" && error "Failed to set owner for ubuntu.boot" + + info "Remastered ISO saved to $STORAGE_ISO" + BOOT="$STORAGE_ISO" return 0 fi From 6a91935b6ae9ab4435a70773f13f47e557e4e205 Mon Sep 17 00:00:00 2001 From: "synacktra.work@gmail.com" Date: Sat, 22 Nov 2025 18:16:54 +0530 Subject: [PATCH 06/11] feat: add graceful shutdown support --- src/entry.sh | 19 ++++- src/power.sh | 215 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 232 insertions(+), 2 deletions(-) create mode 100644 src/power.sh diff --git a/src/entry.sh b/src/entry.sh index b5bdbfaf..2cfa1fa8 100755 --- a/src/entry.sh +++ b/src/entry.sh @@ -17,6 +17,7 @@ cd /run . network.sh # Initialize network . boot.sh # Configure boot . proc.sh # Initialize processor +. power.sh # Configure shutdown . memory.sh # Check available memory . config.sh # Configure arguments . finish.sh # Finish initialization @@ -24,6 +25,20 @@ cd /run trap - ERR version=$(qemu-system-x86_64 --version | head -n 1 | cut -d '(' -f 1 | awk '{ print $NF }') -info "Booting image${BOOT_DESC} using QEMU v$version..." +info "Booting ${APP}${BOOT_DESC} using QEMU v$version..." -exec qemu-system-x86_64 ${ARGS:+ $ARGS} +{ qemu-system-x86_64 ${ARGS:+ $ARGS} >"$QEMU_OUT" 2>"$QEMU_LOG"; rc=$?; } || : +(( rc != 0 )) && error "$(<"$QEMU_LOG")" && exit 15 + +terminal +( sleep 30; boot ) & +tail -fn +0 "$QEMU_LOG" --pid=$$ 2>/dev/null & +cat "$QEMU_TERM" 2> /dev/null | tee "$QEMU_PTY" | \ +sed -u -e 's/\x1B\[[=0-9;]*[a-z]//gi' \ +-e 's/\x1B\x63//g' -e 's/\x1B\[[=?]7l//g' \ +-e '/^$/d' -e 's/\x44\x53\x73//g' \ +-e 's/failed to load Boot/skipped Boot/g' \ +-e 's/0): Not Found/0)/g' & wait $! || : + +sleep 1 & wait $! +[ ! -f "$QEMU_END" ] && finish 0 diff --git a/src/power.sh b/src/power.sh new file mode 100644 index 00000000..86985314 --- /dev/null +++ b/src/power.sh @@ -0,0 +1,215 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +: "${QEMU_TIMEOUT:="110"}" # QEMU Termination timeout + +# Configure QEMU for graceful shutdown + +QEMU_TERM="" +QEMU_DIR="/run/shm" +QEMU_PID="$QEMU_DIR/qemu.pid" +QEMU_PTY="$QEMU_DIR/qemu.pty" +QEMU_LOG="$QEMU_DIR/qemu.log" +QEMU_OUT="$QEMU_DIR/qemu.out" +QEMU_END="$QEMU_DIR/qemu.end" + +rm -f "$QEMU_DIR/qemu.*" +touch "$QEMU_LOG" + +_trap() { + func="$1" ; shift + for sig ; do + trap "$func $sig" "$sig" + done +} + +boot() { + + [ -f "$QEMU_END" ] && return 0 + + if [ -s "$QEMU_PTY" ]; then + if [ "$(stat -c%s "$QEMU_PTY")" -gt 7 ]; then + info "Ubuntu started successfully, visit http://127.0.0.1:8006/ to view the screen..." + return 0 + fi + fi + + error "Timeout while waiting for QEMU to boot the machine!" + + local pid + pid=$(<"$QEMU_PID") + { kill -15 "$pid" || true; } 2>/dev/null + + return 0 +} + +ready() { + + [ -f "$STORAGE/ubuntu.boot" ] && return 0 + [ ! -s "$QEMU_PTY" ] && return 1 + + # Ubuntu uses UEFI boot with GRUB + local line="GNU GRUB" + grep -Fq "$line" "$QEMU_PTY" && return 0 + + # Also check for Ubuntu login prompt as indicator + grep -Fq "ubuntu login:" "$QEMU_PTY" && return 0 + + return 1 +} + +finish() { + + local pid + local cnt=0 + local reason=$1 + + touch "$QEMU_END" + + if [ -s "$QEMU_PID" ]; then + + pid=$(<"$QEMU_PID") + echo && error "Forcefully terminating Ubuntu, reason: $reason..." + { kill -15 "$pid" || true; } 2>/dev/null + + while isAlive "$pid"; do + + sleep 1 + cnt=$((cnt+1)) + + # Workaround for zombie pid + [ ! -s "$QEMU_PID" ] && break + + if [ "$cnt" == "5" ]; then + echo && error "QEMU did not terminate itself, forcefully killing process..." + { kill -9 "$pid" || true; } 2>/dev/null + fi + + done + + fi + + if [ ! -f "$STORAGE/ubuntu.boot" ] && [ -f "$BOOT" ]; then + # Create boot marker after install + if ready; then + local file="$STORAGE/ubuntu.boot" + touch "$file" + ! setOwner "$file" && error "Failed to set the owner for \"$file\" !" + if [[ "${REMOVE:-}" != [Nn]* ]]; then + rm -f "$BOOT" 2>/dev/null || true + fi + fi + fi + + closeNetwork + + sleep 0.5 + echo "❯ Shutdown completed!" + + exit "$reason" +} + +terminal() { + + local dev="" + + if [ -s "$QEMU_OUT" ]; then + + local msg + msg=$(<"$QEMU_OUT") + + if [ -n "$msg" ]; then + + if [[ "${msg,,}" != "char"* || "$msg" != *"serial0)" ]]; then + echo "$msg" + fi + + dev="${msg#*/dev/p}" + dev="/dev/p${dev%% *}" + + fi + fi + + if [ ! -c "$dev" ]; then + dev=$(echo 'info chardev' | nc -q 1 -w 1 localhost "$MON_PORT" | tr -d '\000') + dev="${dev#*serial0}" + dev="${dev#*pty:}" + dev="${dev%%$'\n'*}" + dev="${dev%%$'\r'*}" + fi + + if [ ! -c "$dev" ]; then + error "Device '$dev' not found!" + finish 34 && return 34 + fi + + QEMU_TERM="$dev" + return 0 +} + +_graceful_shutdown() { + + local code=$? + + set +e + + if [ -f "$QEMU_END" ]; then + info "Received $1 while already shutting down..." + return + fi + + touch "$QEMU_END" + info "Received $1, sending ACPI shutdown signal..." + + if [ ! -s "$QEMU_PID" ]; then + error "QEMU PID file does not exist?" + finish "$code" && return "$code" + fi + + local pid="" + pid=$(<"$QEMU_PID") + + if ! isAlive "$pid"; then + error "QEMU process does not exist?" + finish "$code" && return "$code" + fi + + if ! ready; then + info "Cannot send ACPI signal during Ubuntu setup, aborting..." + finish "$code" && return "$code" + fi + + # Send ACPI shutdown signal + echo 'system_powerdown' | nc -q 1 -w 1 localhost "$MON_PORT" > /dev/null + + local cnt=0 + while [ "$cnt" -lt "$QEMU_TIMEOUT" ]; do + + sleep 1 + cnt=$((cnt+1)) + + ! isAlive "$pid" && break + # Workaround for zombie pid + [ ! -s "$QEMU_PID" ] && break + + info "Waiting for Ubuntu to shutdown... ($cnt/$QEMU_TIMEOUT)" + + # Send ACPI shutdown signal + echo 'system_powerdown' | nc -q 1 -w 1 localhost "$MON_PORT" > /dev/null + + done + + if [ "$cnt" -ge "$QEMU_TIMEOUT" ]; then + error "Shutdown timeout reached, aborting..." + fi + + finish "$code" && return "$code" +} + +SERIAL="pty" +MONITOR="telnet:localhost:$MON_PORT,server,nowait,nodelay" +MONITOR+=" -daemonize -D $QEMU_LOG -pidfile $QEMU_PID" + +_trap _graceful_shutdown SIGTERM SIGHUP SIGINT SIGABRT SIGQUIT + +return 0 From 04eca2bdb9e67d209ac8cc3f9a0b3fbb0d018c61 Mon Sep 17 00:00:00 2001 From: "synacktra.work@gmail.com" Date: Sat, 22 Nov 2025 18:17:27 +0530 Subject: [PATCH 07/11] fix: run dos2unix on scripts and assets --- Dockerfile | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0f216fc7..8fdeb384 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,6 +33,7 @@ RUN set -eu && \ dnsmasq \ xz-utils \ apt-utils \ + dos2unix \ net-tools \ e2fsprogs \ qemu-utils \ @@ -68,8 +69,12 @@ RUN apt-get update && \ rm -rf /var/lib/apt/lists/* COPY --chmod=755 ./src /run/ -COPY --chmod=755 ./web /var/www/ +RUN dos2unix /run/* + COPY --chmod=644 ./assets /run/assets +RUN dos2unix /run/assets/* + +COPY --chmod=755 ./web /var/www/ COPY --chmod=664 ./web/conf/defaults.json /usr/share/novnc COPY --chmod=664 ./web/conf/mandatory.json /usr/share/novnc COPY --chmod=744 ./web/conf/nginx.conf /etc/nginx/default.conf @@ -79,8 +84,8 @@ ADD --chmod=755 "https://github.com/qemus/fiano/releases/download/v${VERSION_UTK VOLUME /storage EXPOSE 22 5900 8006 -ENV CPU_CORES="2" -ENV RAM_SIZE="2G" +ENV RAM_SIZE="8G" +ENV CPU_CORES="8" ENV DISK_SIZE="64G" ENTRYPOINT ["/usr/bin/tini", "-s", "/run/entry.sh"] From 2af17e2e2d28388453b19654ae305996f7949df2 Mon Sep 17 00:00:00 2001 From: "synacktra.work@gmail.com" Date: Sat, 22 Nov 2025 18:31:27 +0530 Subject: [PATCH 08/11] docs: update ubuntu server version to 22.04 --- readme.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index f67fd5fb..457b0353 100644 --- a/readme.md +++ b/readme.md @@ -48,10 +48,9 @@ docker run -it --rm \ ### How do I use it? - **Download Ubuntu LTS Server ISO:** + **Download Ubuntu 22.04 LTS Server ISO:** - 1. Visit [Ubuntu Server Downloads](https://ubuntu.com/download/server) - 2. Download Server ISO file [~3GB] + 1. Visit & download the [server ISO](https://releases.ubuntu.com/22.04/ubuntu-22.04.5-live-server-amd64.iso) **Then follow these steps:** From 76082ef683e560946980b9e8610f6bce930a7308 Mon Sep 17 00:00:00 2001 From: "synacktra.work@gmail.com" Date: Wed, 26 Nov 2025 13:18:30 +0530 Subject: [PATCH 09/11] Refactor install.sh and add OEM custom installation support - Simplify install.sh from by removing unused logic for image conversion, archive extraction, and complex file detection - Add OEM installation support via rc.local for first-boot setup - Force X11 session instead of Wayland (WaylandEnable=false) - Add remaster_iso.py OEM directory support with recursive file inclusion - Generate setup scripts (run-oem-install.sh, setup-oem-install.sh) dynamically --- assets/user-data | 6 +- src/install.sh | 439 ++++---------------------------------------- src/remaster_iso.py | 119 +++++++++++- 3 files changed, 158 insertions(+), 406 deletions(-) diff --git a/assets/user-data b/assets/user-data index 907f8d1c..b6038433 100644 --- a/assets/user-data +++ b/assets/user-data @@ -62,6 +62,7 @@ autoinstall: - curtin in-target --target=/target -- bash -c 'echo "[daemon]" > /etc/gdm3/custom.conf' - curtin in-target --target=/target -- bash -c 'echo "AutomaticLoginEnable=true" >> /etc/gdm3/custom.conf' - curtin in-target --target=/target -- bash -c 'echo "AutomaticLogin=docker" >> /etc/gdm3/custom.conf' + - curtin in-target --target=/target -- bash -c 'echo "WaylandEnable=false" >> /etc/gdm3/custom.conf' # Set graphical target - curtin in-target --target=/target -- systemctl set-default graphical.target @@ -103,4 +104,7 @@ autoinstall: - curtin in-target --target=/target -- bash -c 'echo "idle-dim=false" >> /etc/dconf/db/local.d/00-power' # Update dconf database - - curtin in-target --target=/target -- dconf update \ No newline at end of file + - curtin in-target --target=/target -- dconf update + + # OEM Installation Setup (if OEM folder exists on ISO) + - curtin in-target --target=/target -- bash -c 'if [ -f /cdrom/oem/setup-oem-install.sh ]; then bash /cdrom/oem/setup-oem-install.sh; fi' \ No newline at end of file diff --git a/src/install.sh b/src/install.sh index 4ffe94ca..72305b35 100644 --- a/src/install.sh +++ b/src/install.sh @@ -1,424 +1,57 @@ #!/usr/bin/env bash set -Eeuo pipefail -echo "DEBUG: install.sh is running, BOOT=$BOOT" +# 1. Find ISO in root directory +BOOT=$(find / -maxdepth 1 -type f -iname "*.iso" -print -quit 2>/dev/null || true) -# Check if golden image already exists (ubuntu.boot marker) -if [ -f "$STORAGE/ubuntu.boot" ] && hasDisk; then - echo "DEBUG: Golden image ready, skipping installation" - BOOT="none" - return 0 -fi - -# Check if we have a custom ISO to remaster -if [ -f "$BOOT" ]; then - echo "DEBUG: Found $BOOT, checking for existing remastered ISO..." - - # Get ISO size for naming - ISO_SIZE="$(stat -c%s "$BOOT")" - STORAGE_ISO="$STORAGE/ubuntu.${ISO_SIZE}.iso" - - # Check if already remastered and saved - if [ -f "$STORAGE_ISO" ]; then - echo "DEBUG: Using existing remastered ISO at $STORAGE_ISO" - BOOT="$STORAGE_ISO" - return 0 - fi - - REMASTERED_ISO="/tmp/ubuntu-autoinstall.iso" - - info "Remastering Ubuntu ISO for automated installation..." - /opt/isoenv/bin/python /run/remaster_iso.py \ - --src "$BOOT" \ - --dst "$REMASTERED_ISO" \ - --config-dir /run/assets - - if [ ! -f "$REMASTERED_ISO" ]; then - error "Remastered ISO not created at $REMASTERED_ISO" - exit 42 - fi - - # Move remastered ISO to storage - info "Saving remastered ISO to storage..." - if ! mv -f "$REMASTERED_ISO" "$STORAGE_ISO"; then - error "Failed to move ISO to storage" - exit 43 - fi - ! setOwner "$STORAGE_ISO" && error "Failed to set owner for $STORAGE_ISO" - - # Create ubuntu.base file with ISO filename +# 2. If no ISO found, check ubuntu.base for saved remastered ISO +if [ -z "$BOOT" ]; then BASE_FILE="$STORAGE/ubuntu.base" - echo "ubuntu.${ISO_SIZE}.iso" > "$BASE_FILE" - ! setOwner "$BASE_FILE" && error "Failed to set owner for $BASE_FILE" - - touch "$STORAGE/ubuntu.boot" - ! setOwner "$STORAGE/ubuntu.boot" && error "Failed to set owner for ubuntu.boot" - - info "Remastered ISO saved to $STORAGE_ISO" - BOOT="$STORAGE_ISO" - return 0 -fi - - -getBase() { - - local base="${1%%\?*}" - base=$(basename "$base") - printf -v base '%b' "${base//%/\\x}" - base="${base//[!A-Za-z0-9._-]/_}" - - echo "$base" - return 0 -} - -getFolder() { - - local base="" - local result="$1" - - if [[ "$result" != *"."* ]]; then - - result="${result,,}" - - else - - base=$(getBase "$result") - result="${base%.*}" - - case "${base,,}" in - - *".gz" | *".gzip" | *".xz" | *".7z" | *".zip" | *".rar" | *".lzma" | *".bz" | *".bz2" ) - - [[ "$result" == *"."* ]] && result="${result%.*}" ;; - - esac - - fi - - [ -z "$result" ] && result="unknown" - echo "$result" - - return 0 -} - -moveFile() { - - local file="$1" - local ext="${file##*.}" - local dest="$STORAGE/boot.$ext" - - if [[ "$file" == "$dest" ]]; then - BOOT="$file" - return 0 - fi - - if [[ "${file,,}" == "/boot.${ext,,}" || "${file,,}" == "/custom.${ext,,}" ]]; then - BOOT="$file" - return 0 - fi - - if ! mv -f "$file" "$dest"; then - error "Failed to move $file to $dest !" - return 1 - fi - - BOOT="$dest" - return 0 -} - -detectType() { - - local file="$1" - local result="" - local hybrid="" - - [ ! -f "$file" ] && return 1 - [ ! -s "$file" ] && return 1 - - case "${file,,}" in - *".iso" | *".img" | *".raw" | *".qcow2" ) ;; - * ) return 1 ;; - esac - - if [ -n "$BOOT_MODE" ] || [[ "${file,,}" == *".qcow2" ]]; then - moveFile "$file" && return 0 - return 1 - fi - - if [[ "${file,,}" == *".iso" ]]; then - - hybrid=$(head -c 512 "$file" | tail -c 2 | xxd -p) - - if [[ "$hybrid" != "0000" ]]; then - - result=$(isoinfo -f -i "$file" 2>/dev/null) - - if [ -z "$result" ]; then - error "Failed to read ISO file, invalid format!" - return 1 - fi - - result=$(echo "${result^^}" | grep "^/EFI") - [ -z "$result" ] && BOOT_MODE="legacy" - - moveFile "$file" && return 0 - return 1 - - fi - fi - - result=$(fdisk -l "$file" 2>/dev/null) - [[ "${result^^}" != *"EFI "* ]] && BOOT_MODE="legacy" - - moveFile "$file" && return 0 - return 1 -} - -convertImage() { - - local source_file=$1 - local source_fmt=$2 - local dst_file=$3 - local dst_fmt=$4 - local dir base fs fa space space_gb - local cur_size cur_gb src_size disk_param - - [ -f "$dst_file" ] && error "Conversion failed, destination file $dst_file already exists?" && return 1 - [ ! -f "$source_file" ] && error "Conversion failed, source file $source_file does not exists?" && return 1 - - if [[ "${source_fmt,,}" == "${dst_fmt,,}" ]]; then - mv -f "$source_file" "$dst_file" - return 0 - fi - - local tmp_file="$dst_file.tmp" - dir=$(dirname "$tmp_file") - - rm -f "$tmp_file" - - if [ -n "$ALLOCATE" ] && [[ "$ALLOCATE" != [Nn]* ]]; then - - # Check free diskspace - src_size=$(qemu-img info "$source_file" -f "$source_fmt" | grep '^virtual size: ' | sed 's/.*(\(.*\) bytes)/\1/') - space=$(df --output=avail -B 1 "$dir" | tail -n 1) - - if (( src_size > space )); then - space_gb=$(formatBytes "$space") - error "Not enough free space to convert image in $dir, it has only $space_gb available..." && return 1 - fi - fi - - base=$(basename "$source_file") - info "Converting $base..." - html "Converting image..." - - local conv_flags="-p" - - if [ -z "$ALLOCATE" ] || [[ "$ALLOCATE" == [Nn]* ]]; then - disk_param="preallocation=off" - else - disk_param="preallocation=falloc" - fi - - fs=$(stat -f -c %T "$dir") - [[ "${fs,,}" == "btrfs" ]] && disk_param+=",nocow=on" - - if [[ "$dst_fmt" != "raw" ]]; then - if [ -z "$ALLOCATE" ] || [[ "$ALLOCATE" == [Nn]* ]]; then - conv_flags+=" -c" - fi - [ -n "${DISK_FLAGS:-}" ] && disk_param+=",$DISK_FLAGS" - fi - - # shellcheck disable=SC2086 - if ! qemu-img convert -f "$source_fmt" $conv_flags -o "$disk_param" -O "$dst_fmt" -- "$source_file" "$tmp_file"; then - rm -f "$tmp_file" - error "Failed to convert image in $dir, is there enough space available?" && return 1 - fi - - if [[ "$dst_fmt" == "raw" ]]; then - if [ -n "$ALLOCATE" ] && [[ "$ALLOCATE" != [Nn]* ]]; then - # Work around qemu-img bug - cur_size=$(stat -c%s "$tmp_file") - cur_gb=$(formatBytes "$cur_size") - if ! fallocate -l "$cur_size" "$tmp_file" &>/dev/null; then - if ! fallocate -l -x "$cur_size" "$tmp_file"; then - error "Failed to allocate $cur_gb for image!" - fi - fi - fi - fi - - rm -f "$source_file" - mv "$tmp_file" "$dst_file" - - if [[ "${fs,,}" == "btrfs" ]]; then - fa=$(lsattr "$dst_file") - if [[ "$fa" != *"C"* ]]; then - error "Failed to disable COW for image on ${fs^^} filesystem!" - fi - fi - - html "Conversion completed..." - return 0 -} - -findFile() { - - local dir file - local base="$1" - local ext="$2" - local fname="${base}.${ext}" - - dir=$(find / -maxdepth 1 -type d -iname "$fname" -print -quit) - [ ! -d "$dir" ] && dir=$(find "$STORAGE" -maxdepth 1 -type d -iname "$fname" -print -quit) - - if [ -d "$dir" ]; then - if hasDisk; then - BOOT="none" + if [ -f "$BASE_FILE" ]; then + ISO_NAME=$(cat "$BASE_FILE") + STORAGE_ISO="$STORAGE/$ISO_NAME" + if [ -f "$STORAGE_ISO" ]; then + BOOT="$STORAGE_ISO" return 0 fi - error "The bind $dir maps to a file that does not exist!" && exit 37 + error "ISO file '$ISO_NAME' from ubuntu.base not found in storage" + exit 44 fi - - file=$(find / -maxdepth 1 -type f -iname "$fname" -print -quit) - [ ! -s "$file" ] && file=$(find "$STORAGE" -maxdepth 1 -type f -iname "$fname" -print -quit) - - detectType "$file" && return 0 - - return 1 -} - -findFile "boot" "img" && return 0 -findFile "boot" "raw" && return 0 -findFile "boot" "iso" && return 0 -findFile "boot" "qcow2" && return 0 - -# Skip custom.iso check if we already remastered it -if [ -z "${REMASTERED:-}" ]; then - findFile "custom" "iso" && return 0 -fi - -if hasDisk; then - BOOT="none" - return 0 -fi - -BOOT=$(expr "$BOOT" : "^\ *\(.*[^ ]\)\ *$") - -if [ -z "$BOOT" ]; then - error "No BOOT value specified! Provide Ubuntu ISO via volume mount." + error "No ISO file found. Mount Ubuntu ISO to container root." exit 64 fi -folder=$(getFolder "$BOOT") -STORAGE="$STORAGE/$folder" +# 3. Remaster ISO +REMASTERED_ISO="/tmp/ubuntu-autoinstall.iso" +OEM_ARG="" +[ -d "/oem" ] && OEM_ARG="--oem-dir /oem" -if [ -d "$STORAGE" ]; then - - findFile "boot" "img" && return 0 - findFile "boot" "raw" && return 0 - findFile "boot" "iso" && return 0 - findFile "boot" "qcow2" && return 0 - findFile "custom" "iso" && return 0 - - if hasDisk; then - BOOT="none" - return 0 - fi +info "Remastering Ubuntu ISO for automated installation..." +/opt/isoenv/bin/python /run/remaster_iso.py \ + --src "$BOOT" \ + --dst "$REMASTERED_ISO" \ + --config-dir /run/assets \ + $OEM_ARG +if [ ! -f "$REMASTERED_ISO" ]; then + error "Remastered ISO not created" + exit 42 fi -if [[ "$BOOT" != *"."* ]]; then - if [ -z "$BOOT" ]; then - error "No BOOT value specified!" - else - error "Invalid BOOT value specified, option \"$BOOT\" is not recognized!" - fi - exit 64 -fi - -if ! makeDir "$STORAGE"; then - error "Failed to create directory \"$STORAGE\" !" && exit 33 -fi - -base=$(getBase "$BOOT") - -case "${base,,}" in - *".gz" | *".gzip" | *".xz" | *".7z" | *".zip" | *".rar" | *".lzma" | *".bz" | *".bz2" ) - info "Extracting $base..." - html "Extracting image..." ;; -esac - -case "${base,,}" in - *".gz" | *".gzip" ) - - gzip -dc "$STORAGE/$base" > "$STORAGE/${base%.*}" - rm -f "$STORAGE/$base" - base="${base%.*}" - - ;; - *".xz" ) - - xz -dc "$STORAGE/$base" > "$STORAGE/${base%.*}" - rm -f "$STORAGE/$base" - base="${base%.*}" - - ;; - *".7z" | *".zip" | *".rar" | *".lzma" | *".bz" | *".bz2" ) - - tmp="$STORAGE/extract" - rm -rf "$tmp" - - if ! makeDir "$tmp"; then - error "Failed to create directory \"$tmp\" !" && exit 33 - fi - - 7z x "$STORAGE/$base" -o"$tmp" > /dev/null - - rm -f "$STORAGE/$base" - base="${base%.*}" - - if [ ! -s "$tmp/$base" ]; then - rm -rf "$tmp" - error "Cannot find file \"${base}\" in .${BOOT/*./} archive!" && exit 32 - fi - - mv "$tmp/$base" "$STORAGE/$base" - rm -rf "$tmp" - - ;; -esac - -case "${base,,}" in - *".iso" | *".img" | *".raw" | *".qcow2" ) - - ! setOwner "$STORAGE/$base" && error "Failed to set the owner for \"$STORAGE/$base\" !" - detectType "$STORAGE/$base" && return 0 - error "Cannot read file \"${base}\"" && exit 63 ;; -esac - -target_ext="img" -target_fmt="${DISK_FMT:-}" -[ -z "$target_fmt" ] && target_fmt="raw" -[[ "$target_fmt" != "raw" ]] && target_ext="qcow2" - -case "${base,,}" in - *".vdi" ) source_fmt="vdi" ;; - *".vhd" ) source_fmt="vpc" ;; - *".vhdx" ) source_fmt="vpc" ;; - *".vmdk" ) source_fmt="vmdk" ;; - * ) error "Unknown file extension, type \".${base/*./}\" is not recognized!" && exit 33 ;; -esac -dst="$STORAGE/${base%.*}.$target_ext" +# 4. Save to storage +ISO_SIZE="$(stat -c%s "$BOOT")" +ISO_NAME="ubuntu.${ISO_SIZE}.iso" +STORAGE_ISO="$STORAGE/$ISO_NAME" -! convertImage "$STORAGE/$base" "$source_fmt" "$dst" "$target_fmt" && exit 35 +mv -f "$REMASTERED_ISO" "$STORAGE_ISO" || { error "Failed to save ISO"; exit 43; } +setOwner "$STORAGE_ISO" || true -base=$(basename "$dst") +echo "$ISO_NAME" > "$STORAGE/ubuntu.base" +setOwner "$STORAGE/ubuntu.base" || true -! setOwner "$STORAGE/$base" && error "Failed to set the owner for \"$STORAGE/$base\" !" -detectType "$STORAGE/$base" && return 0 -error "Cannot convert file \"${base}\"" && exit 36 +touch "$STORAGE/ubuntu.boot" +setOwner "$STORAGE/ubuntu.boot" || true +BOOT="$STORAGE_ISO" return 0 diff --git a/src/remaster_iso.py b/src/remaster_iso.py index d23cb6a2..69c20280 100644 --- a/src/remaster_iso.py +++ b/src/remaster_iso.py @@ -8,6 +8,73 @@ from pycdlib.pycdlibexception import PyCdlibException +RUN_OEM_INSTALL_SH = """\ +#!/bin/bash +# OEM Installation Wrapper Script +# Runs user-provided /opt/oem/install.sh on first boot + +set -e + +OEM_DIR="/opt/oem" +LOG_FILE="$OEM_DIR/install.log" +USER_SCRIPT="$OEM_DIR/install.sh" +COMPLETED_MARKER="$OEM_DIR/.completed" + +# Skip if already completed +if [ -f "$COMPLETED_MARKER" ]; then + exit 0 +fi + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" +} + +log "Starting OEM installation..." + +if [ -f "$USER_SCRIPT" ]; then + log "Running user install script: $USER_SCRIPT" + bash "$USER_SCRIPT" >> "$LOG_FILE" 2>&1 + EXIT_CODE=$? + log "User install script finished with exit code: $EXIT_CODE" +else + log "No user install script found at $USER_SCRIPT, skipping..." + EXIT_CODE=0 +fi + +# Mark as completed +touch "$COMPLETED_MARKER" +log "OEM installation completed!" + +exit $EXIT_CODE +""" + +SETUP_OEM_INSTALL_SH = """\ +#!/bin/bash +# OEM Setup Script - called from user-data late-commands via curtin in-target +# Copies OEM folder and creates rc.local for first boot execution + +set -e + +if [ -d /cdrom/oem ]; then + mkdir -p /opt + cp -r /cdrom/oem /opt/oem + chmod +x /opt/oem/*.sh 2>/dev/null || true +fi + +# Create rc.local to run OEM install on first boot +cat > /etc/rc.local << 'EOF' +#!/bin/bash +if [ -f /opt/oem/run-oem-install.sh ] && [ ! -f /opt/oem/.completed ]; then + /opt/oem/run-oem-install.sh & +fi +exit 0 +EOF +chmod +x /etc/rc.local + +echo "OEM installation setup completed!" +""" + + def add_directory(iso: PyCdlib, path_in_iso: str, name: str) -> None: with suppress(PyCdlibException): iso.add_directory(iso_path=path_in_iso, rr_name=name) @@ -28,7 +95,34 @@ def replace_data( iso.add_fp(BytesIO(new_data), len(new_data), **kwargs) -def remaster_iso(src_iso: Path, dst_iso: Path, config_dir: Path): +def add_directory_contents(iso: PyCdlib, dir: Path, base_path_in_iso: str, base_name: str) -> None: + """Add directory contents to the ISO recursively.""" + if not dir.is_dir(): + return + + print(f"Adding folder to ISO: {dir}") + add_directory(iso, path_in_iso=base_path_in_iso, name=base_name) + + for item in dir.iterdir(): + if item.is_file(): + file_data = item.read_bytes() + # ISO9660 requires uppercase names with version suffix + iso_name = item.name.upper().replace(".", "_").replace("-", "_")[:8] + iso_path = f"{base_path_in_iso}/{iso_name};1" + + print(f" Adding file: {item.name} -> {iso_path}") + replace_data(iso, path_in_iso=iso_path, new_data=file_data, name=item.name) + + elif item.is_dir(): + # Recursively add subdirectory + subdir_iso_name = item.name.upper().replace("-", "_")[:8] + subdir_iso_path = f"{base_path_in_iso}/{subdir_iso_name}" + + print(f" Adding directory: {item.name} -> {subdir_iso_path}") + add_directory_contents(iso, item, base_path_in_iso=subdir_iso_path, base_name=item.name) + + +def remaster_iso(src_iso: Path, dst_iso: Path, config_dir: Path, oem_dir: Path | None = None): user_data_file = config_dir / "user-data" meta_data_file = config_dir / "meta-data" grub_cfg_file = config_dir / "grub.cfg" @@ -85,6 +179,17 @@ def remaster_iso(src_iso: Path, dst_iso: Path, config_dir: Path): name="meta-data" ) + # Add OEM folder if provided + if oem_dir: + print("Adding OEM folder and setup scripts...") + # Add user's OEM directory contents + add_directory_contents(iso, oem_dir, base_path_in_iso="/OEM", base_name="oem") + + # Add generated scripts + replace_data(iso, "/OEM/RUN_OEM_;1", RUN_OEM_INSTALL_SH.encode(), "run-oem-install.sh") + replace_data(iso, "/OEM/SETUP_OE;1", SETUP_OEM_INSTALL_SH.encode(), "setup-oem-install.sh") + + print(f"Writing remastered ISO to: {dst_iso}") iso.write(str(dst_iso)) iso.close() @@ -111,9 +216,19 @@ def main(): required=True, help="Directory containing user-data, meta-data and grub.cfg files" ) + parser.add_argument( + "--oem-dir", + required=False, + help="Optional OEM directory to include in the ISO" + ) args = parser.parse_args() - remaster_iso(Path(args.src), Path(args.dst), Path(args.config_dir)) + remaster_iso( + Path(args.src), + Path(args.dst), + Path(args.config_dir), + Path(args.oem_dir) if args.oem_dir else None, + ) if __name__ == "__main__": From dd06cae43c848d694a489b58fff9547fcaaa5da5 Mon Sep 17 00:00:00 2001 From: "synacktra.work@gmail.com" Date: Wed, 26 Nov 2025 13:26:41 +0530 Subject: [PATCH 10/11] chore: remove compose files --- compose.prepare.yml | 18 ------- compose.yml | 15 ------ readme.md | 119 +++++++++++++++++++++++++++++++------------- 3 files changed, 85 insertions(+), 67 deletions(-) delete mode 100644 compose.prepare.yml delete mode 100644 compose.yml diff --git a/compose.prepare.yml b/compose.prepare.yml deleted file mode 100644 index 860c39e8..00000000 --- a/compose.prepare.yml +++ /dev/null @@ -1,18 +0,0 @@ -services: - prepare-ubuntu: - image: qemu-local:latest - container_name: prepare-ubuntu - environment: - STORAGE_DIR: "${STORAGE_DIR:?Error: required env var not set}" - ISO_FILE: "${ISO_FILE:?Error: required env var not set}" - BOOT: "/custom.iso" - devices: - - /dev/kvm - cap_add: - - NET_ADMIN - ports: - - 8006:8006 - stop_grace_period: 2m - volumes: - - ${ISO_FILE}:/custom.iso - - ${STORAGE_DIR}:/storage diff --git a/compose.yml b/compose.yml deleted file mode 100644 index 203ef8a1..00000000 --- a/compose.yml +++ /dev/null @@ -1,15 +0,0 @@ -services: - ubuntu: - image: qemu-local:latest - container_name: ubuntu - environment: - STORAGE_DIR: "${STORAGE_DIR:?Error: required env var not set}" - devices: - - /dev/kvm - cap_add: - - NET_ADMIN - ports: - - 8006:8006 - stop_grace_period: 2m - volumes: - - ${STORAGE_DIR}:/storage diff --git a/readme.md b/readme.md index 457b0353..2192cde0 100644 --- a/readme.md +++ b/readme.md @@ -3,37 +3,89 @@ -Local Ubuntu Desktop inside a Docker container. +Local Ubuntu Desktop inside a Docker container with automated cloud-init installation. ## Usage 🐳 -### Via Docker Compose: +### Building the Image -> See [compose.yml](compose.yml) for the complete configuration. - -To prepare a golden image from a custom ISO: ```bash -STORAGE_DIR=/path/to/storage ISO_FILE=/path/to/ubuntu.iso \ - docker compose -f compose.prepare.yml up +docker build -t qemu-local:latest . ``` -Start the container (using the golden image): +### Preparing Golden Image (First Time) + +Mount your Ubuntu ISO and let it install automatically: + ```bash -STORAGE_DIR=/path/to/storage docker compose up +docker run -it --rm \ + --name prepare-ubuntu \ + --device=/dev/kvm \ + --cap-add NET_ADMIN \ + --mount type=bind,source=/path/to/ubuntu.iso,target=/custom.iso \ + -v /path/to/storage:/storage \ + -p 8006:8006 \ + -e RAM_SIZE=6G \ + -e CPU_CORES=4 \ + -e DISK_SIZE=64G \ + qemu-local:latest ``` -### Via Docker CLI: +The container will automatically: +- Remaster the ISO with cloud-init autoinstall configuration +- Install Ubuntu with desktop environment +- Create a golden image in `/storage` +- Exit when preparation is complete + +### Running from Golden Image + +After preparation, start Ubuntu from the saved golden image: ```bash docker run -it --rm \ + --name ubuntu \ + --device=/dev/kvm \ + --cap-add NET_ADMIN \ + -v /path/to/storage:/storage \ -p 8006:8006 \ + -p 5000:5000 \ + -e RAM_SIZE=8G \ + -e CPU_CORES=4 \ + qemu-local:latest +``` + +Access the desktop via browser at http://localhost:8006 + +### Custom Installation with OEM Scripts + +You can provide custom installation scripts that run on first boot: + +```bash +docker run -it --rm \ + --name prepare-ubuntu \ --device=/dev/kvm \ --cap-add NET_ADMIN \ - --mount type=bind,source=./ubuntu.iso,target=/custom.iso \ - --stop-timeout 120 \ + --mount type=bind,source=/path/to/ubuntu.iso,target=/custom.iso \ + --mount type=bind,source=/path/to/oem,target=/oem \ + -v /path/to/storage:/storage \ + -p 8006:8006 \ qemu-local:latest ``` +Create an `/oem/install.sh` script that will execute on first boot: + +```bash +#!/bin/bash +# Example OEM installation script + +# Install additional packages +apt-get update +apt-get install -y vim htop + +# Configure system +echo "Custom setup complete!" +``` + ## Compatibility ⚙️ | **Product** | **Platform** | | @@ -54,29 +106,30 @@ docker run -it --rm \ **Then follow these steps:** - - Start the container and connect to [port 8006](http://localhost:8006) using your web browser. + - Start the container with preparation command (see above) + + - Connect to [port 8006](http://localhost:8006) using your web browser - - Sit back and relax while the magic happens, the whole installation will be performed fully automatic with cloud-init autoinstall. + - Watch the automated installation with cloud-init autoinstall - - Once you see the desktop, your Ubuntu installation is ready for use. + - Once you see the desktop, your Ubuntu installation is ready Enjoy your brand new machine, and don't forget to star this repo! ### How do I change the storage location? - To change the storage location, modify the `STORAGE_DIR` environment variable: + To change the storage location, modify the volume mount: ```bash - STORAGE_DIR=./ubuntu docker compose up + -v /custom/storage/path:/storage ``` ### How do I change the size of the disk? - To expand the default size of 64 GB, add the `DISK_SIZE` setting to your compose file and set it to your preferred capacity: + To expand the default size of 64 GB, set the `DISK_SIZE` environment variable: - ```yaml - environment: - DISK_SIZE: "256G" + ```bash + -e DISK_SIZE="256G" ``` > [!TIP] @@ -84,16 +137,15 @@ docker run -it --rm \ ### How do I share files with the host? - To share files with the host, add the following volume to your compose file: + To share files with the host, add a volume mount: - ```yaml - volumes: - - /home/user/example:/shared + ```bash + --mount type=bind,source=/home/user/example,target=/shared ``` - Then start the container and execute the following command in Ubuntu: + Then execute the following command in Ubuntu: - ```shell + ```bash sudo mount -t 9p -o trans=virtio shared /mnt/example ``` @@ -104,14 +156,13 @@ docker run -it --rm \ ### How do I change the amount of CPU or RAM? - By default, the container will be allowed to use a maximum of 2 CPU cores and 4 GB of RAM. + By default, the container will be allowed to use a maximum of 8 CPU cores and 8 GB of RAM. - If you want to adjust this, you can specify the desired amount using the following environment variables: + If you want to adjust this, specify the desired amount: - ```yaml - environment: - RAM_SIZE: "8G" - CPU_CORES: "4" + ```bash + -e RAM_SIZE="16G" \ + -e CPU_CORES="8" ``` ### How do I verify if my system supports KVM? @@ -133,4 +184,4 @@ docker run -it --rm \ - you are not using a cloud provider, as most of them do not allow nested virtualization for their VPS's. - If you didn't receive any error from `kvm-ok` at all, but the container still complains that `/dev/kvm` is missing, it might help to add `privileged: true` to your compose file (or `--privileged` to your `run` command), to rule out any permission issue. + If you didn't receive any error from `kvm-ok` at all, but the container still complains that `/dev/kvm` is missing, try adding `--privileged` to your `run` command to rule out any permission issue. From 62df0bf9b2d9145f041fd89cce99080d1fc7b101 Mon Sep 17 00:00:00 2001 From: "synacktra.work@gmail.com" Date: Wed, 26 Nov 2025 13:29:40 +0530 Subject: [PATCH 11/11] chore: remove dev containers and update github workflow --- .devcontainer/alma/devcontainer.json | 20 ----- .devcontainer/alpine/devcontainer.json | 20 ----- .devcontainer/arch/devcontainer.json | 20 ----- .devcontainer/cachy/devcontainer.json | 20 ----- .devcontainer/centos/devcontainer.json | 20 ----- .devcontainer/codespaces.yml | 19 ----- .devcontainer/debian/devcontainer.json | 20 ----- .devcontainer/devcontainer.json | 20 ----- .devcontainer/fedora/devcontainer.json | 20 ----- .devcontainer/gentoo/devcontainer.json | 20 ----- .devcontainer/kali/devcontainer.json | 20 ----- .devcontainer/kubuntu/devcontainer.json | 20 ----- .devcontainer/manjaro/devcontainer.json | 20 ----- .devcontainer/mx/devcontainer.json | 20 ----- .devcontainer/nixos/devcontainer.json | 20 ----- .devcontainer/rocky/devcontainer.json | 20 ----- .devcontainer/slack/devcontainer.json | 20 ----- .devcontainer/suse/devcontainer.json | 20 ----- .devcontainer/tails/devcontainer.json | 20 ----- .devcontainer/ubuntu/devcontainer.json | 20 ----- .devcontainer/ubuntus/devcontainer.json | 20 ----- .devcontainer/xubuntu/devcontainer.json | 20 ----- .github/dependabot.yml | 10 --- .github/renovate.json | 4 - .github/workflows/build.yml | 103 ------------------------ .github/workflows/check.yml | 13 +-- .github/workflows/hub.yml | 24 ------ .github/workflows/publish-docker.yml | 53 ++++++++++++ .github/workflows/review.yml | 66 --------------- .github/workflows/test.yml | 11 --- kubernetes.yml | 86 -------------------- 31 files changed, 61 insertions(+), 748 deletions(-) delete mode 100644 .devcontainer/alma/devcontainer.json delete mode 100644 .devcontainer/alpine/devcontainer.json delete mode 100644 .devcontainer/arch/devcontainer.json delete mode 100644 .devcontainer/cachy/devcontainer.json delete mode 100644 .devcontainer/centos/devcontainer.json delete mode 100644 .devcontainer/codespaces.yml delete mode 100644 .devcontainer/debian/devcontainer.json delete mode 100644 .devcontainer/devcontainer.json delete mode 100644 .devcontainer/fedora/devcontainer.json delete mode 100644 .devcontainer/gentoo/devcontainer.json delete mode 100644 .devcontainer/kali/devcontainer.json delete mode 100644 .devcontainer/kubuntu/devcontainer.json delete mode 100644 .devcontainer/manjaro/devcontainer.json delete mode 100644 .devcontainer/mx/devcontainer.json delete mode 100644 .devcontainer/nixos/devcontainer.json delete mode 100644 .devcontainer/rocky/devcontainer.json delete mode 100644 .devcontainer/slack/devcontainer.json delete mode 100644 .devcontainer/suse/devcontainer.json delete mode 100644 .devcontainer/tails/devcontainer.json delete mode 100644 .devcontainer/ubuntu/devcontainer.json delete mode 100644 .devcontainer/ubuntus/devcontainer.json delete mode 100644 .devcontainer/xubuntu/devcontainer.json delete mode 100644 .github/dependabot.yml delete mode 100644 .github/renovate.json delete mode 100644 .github/workflows/build.yml delete mode 100644 .github/workflows/hub.yml create mode 100644 .github/workflows/publish-docker.yml delete mode 100644 .github/workflows/review.yml delete mode 100644 .github/workflows/test.yml delete mode 100644 kubernetes.yml diff --git a/.devcontainer/alma/devcontainer.json b/.devcontainer/alma/devcontainer.json deleted file mode 100644 index 6c9f3fd1..00000000 --- a/.devcontainer/alma/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Alma Linux", - "service": "qemu", - "containerEnv": { - "BOOT": "alma" - }, - "forwardPorts": [8006], - "portsAttributes": { - "8006": { - "label": "Web", - "onAutoForward": "notify" - } - }, - "otherPortsAttributes": { - "onAutoForward": "ignore" - }, - "dockerComposeFile": "../codespaces.yml", - "workspaceFolder": "/workspaces/qemu", - "initializeCommand": "docker system prune --all --force" -} diff --git a/.devcontainer/alpine/devcontainer.json b/.devcontainer/alpine/devcontainer.json deleted file mode 100644 index bbdbe32d..00000000 --- a/.devcontainer/alpine/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Alpine Linux", - "service": "qemu", - "containerEnv": { - "BOOT": "alpine" - }, - "forwardPorts": [8006], - "portsAttributes": { - "8006": { - "label": "Web", - "onAutoForward": "notify" - } - }, - "otherPortsAttributes": { - "onAutoForward": "ignore" - }, - "dockerComposeFile": "../codespaces.yml", - "workspaceFolder": "/workspaces/qemu", - "initializeCommand": "docker system prune --all --force" -} diff --git a/.devcontainer/arch/devcontainer.json b/.devcontainer/arch/devcontainer.json deleted file mode 100644 index cf189105..00000000 --- a/.devcontainer/arch/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Arch Linux", - "service": "qemu", - "containerEnv": { - "BOOT": "arch" - }, - "forwardPorts": [8006], - "portsAttributes": { - "8006": { - "label": "Web", - "onAutoForward": "notify" - } - }, - "otherPortsAttributes": { - "onAutoForward": "ignore" - }, - "dockerComposeFile": "../codespaces.yml", - "workspaceFolder": "/workspaces/qemu", - "initializeCommand": "docker system prune --all --force" -} diff --git a/.devcontainer/cachy/devcontainer.json b/.devcontainer/cachy/devcontainer.json deleted file mode 100644 index 60998de8..00000000 --- a/.devcontainer/cachy/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "CachyOS", - "service": "qemu", - "containerEnv": { - "BOOT": "cachy" - }, - "forwardPorts": [8006], - "portsAttributes": { - "8006": { - "label": "Web", - "onAutoForward": "notify" - } - }, - "otherPortsAttributes": { - "onAutoForward": "ignore" - }, - "dockerComposeFile": "../codespaces.yml", - "workspaceFolder": "/workspaces/qemu", - "initializeCommand": "docker system prune --all --force" -} diff --git a/.devcontainer/centos/devcontainer.json b/.devcontainer/centos/devcontainer.json deleted file mode 100644 index 9d6f21b7..00000000 --- a/.devcontainer/centos/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "CentOS", - "service": "qemu", - "containerEnv": { - "BOOT": "centos" - }, - "forwardPorts": [8006], - "portsAttributes": { - "8006": { - "label": "Web", - "onAutoForward": "notify" - } - }, - "otherPortsAttributes": { - "onAutoForward": "ignore" - }, - "dockerComposeFile": "../codespaces.yml", - "workspaceFolder": "/workspaces/qemu", - "initializeCommand": "docker system prune --all --force" -} diff --git a/.devcontainer/codespaces.yml b/.devcontainer/codespaces.yml deleted file mode 100644 index 423f0600..00000000 --- a/.devcontainer/codespaces.yml +++ /dev/null @@ -1,19 +0,0 @@ -services: - qemu: - container_name: qemu - image: ghcr.io/qemus/qemu - environment: - RAM_SIZE: "half" - DISK_SIZE: "max" - CPU_CORES: "max" - devices: - - /dev/kvm - - /dev/net/tun - cap_add: - - NET_ADMIN - ports: - - 8006:8006 - volumes: - - ./qemu:/storage - restart: on-failure - stop_grace_period: 2m diff --git a/.devcontainer/debian/devcontainer.json b/.devcontainer/debian/devcontainer.json deleted file mode 100644 index 9b2b3e11..00000000 --- a/.devcontainer/debian/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Debian", - "service": "qemu", - "containerEnv": { - "BOOT": "debian" - }, - "forwardPorts": [8006], - "portsAttributes": { - "8006": { - "label": "Web", - "onAutoForward": "notify" - } - }, - "otherPortsAttributes": { - "onAutoForward": "ignore" - }, - "dockerComposeFile": "../codespaces.yml", - "workspaceFolder": "/workspaces/qemu", - "initializeCommand": "docker system prune --all --force" -} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index d32ca427..00000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Linux Mint", - "service": "qemu", - "containerEnv": { - "BOOT": "mint" - }, - "forwardPorts": [8006], - "portsAttributes": { - "8006": { - "label": "Web", - "onAutoForward": "notify" - } - }, - "otherPortsAttributes": { - "onAutoForward": "ignore" - }, - "dockerComposeFile": "codespaces.yml", - "workspaceFolder": "/workspaces/qemu", - "initializeCommand": "docker system prune --all --force" -} diff --git a/.devcontainer/fedora/devcontainer.json b/.devcontainer/fedora/devcontainer.json deleted file mode 100644 index 85c033ce..00000000 --- a/.devcontainer/fedora/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Fedora", - "service": "qemu", - "containerEnv": { - "BOOT": "fedora" - }, - "forwardPorts": [8006], - "portsAttributes": { - "8006": { - "label": "Web", - "onAutoForward": "notify" - } - }, - "otherPortsAttributes": { - "onAutoForward": "ignore" - }, - "dockerComposeFile": "../codespaces.yml", - "workspaceFolder": "/workspaces/qemu", - "initializeCommand": "docker system prune --all --force" -} diff --git a/.devcontainer/gentoo/devcontainer.json b/.devcontainer/gentoo/devcontainer.json deleted file mode 100644 index 2f0d5bab..00000000 --- a/.devcontainer/gentoo/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Gentoo", - "service": "qemu", - "containerEnv": { - "BOOT": "gentoo" - }, - "forwardPorts": [8006], - "portsAttributes": { - "8006": { - "label": "Web", - "onAutoForward": "notify" - } - }, - "otherPortsAttributes": { - "onAutoForward": "ignore" - }, - "dockerComposeFile": "../codespaces.yml", - "workspaceFolder": "/workspaces/qemu", - "initializeCommand": "docker system prune --all --force" -} diff --git a/.devcontainer/kali/devcontainer.json b/.devcontainer/kali/devcontainer.json deleted file mode 100644 index 8b78c93f..00000000 --- a/.devcontainer/kali/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Kali Linux", - "service": "qemu", - "containerEnv": { - "BOOT": "kali" - }, - "forwardPorts": [8006], - "portsAttributes": { - "8006": { - "label": "Web", - "onAutoForward": "notify" - } - }, - "otherPortsAttributes": { - "onAutoForward": "ignore" - }, - "dockerComposeFile": "../codespaces.yml", - "workspaceFolder": "/workspaces/qemu", - "initializeCommand": "docker system prune --all --force" -} diff --git a/.devcontainer/kubuntu/devcontainer.json b/.devcontainer/kubuntu/devcontainer.json deleted file mode 100644 index e3a24dde..00000000 --- a/.devcontainer/kubuntu/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Kubuntu", - "service": "qemu", - "containerEnv": { - "BOOT": "kubuntu" - }, - "forwardPorts": [8006], - "portsAttributes": { - "8006": { - "label": "Web", - "onAutoForward": "notify" - } - }, - "otherPortsAttributes": { - "onAutoForward": "ignore" - }, - "dockerComposeFile": "../codespaces.yml", - "workspaceFolder": "/workspaces/qemu", - "initializeCommand": "docker system prune --all --force" -} diff --git a/.devcontainer/manjaro/devcontainer.json b/.devcontainer/manjaro/devcontainer.json deleted file mode 100644 index 05f7ad26..00000000 --- a/.devcontainer/manjaro/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Manjaro", - "service": "qemu", - "containerEnv": { - "BOOT": "manjaro" - }, - "forwardPorts": [8006], - "portsAttributes": { - "8006": { - "label": "Web", - "onAutoForward": "notify" - } - }, - "otherPortsAttributes": { - "onAutoForward": "ignore" - }, - "dockerComposeFile": "../codespaces.yml", - "workspaceFolder": "/workspaces/qemu", - "initializeCommand": "docker system prune --all --force" -} diff --git a/.devcontainer/mx/devcontainer.json b/.devcontainer/mx/devcontainer.json deleted file mode 100644 index f3a9f616..00000000 --- a/.devcontainer/mx/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "MX Linux", - "service": "qemu", - "containerEnv": { - "BOOT": "mx" - }, - "forwardPorts": [8006], - "portsAttributes": { - "8006": { - "label": "Web", - "onAutoForward": "notify" - } - }, - "otherPortsAttributes": { - "onAutoForward": "ignore" - }, - "dockerComposeFile": "../codespaces.yml", - "workspaceFolder": "/workspaces/qemu", - "initializeCommand": "docker system prune --all --force" -} diff --git a/.devcontainer/nixos/devcontainer.json b/.devcontainer/nixos/devcontainer.json deleted file mode 100644 index d4182c80..00000000 --- a/.devcontainer/nixos/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "NixOS", - "service": "qemu", - "containerEnv": { - "BOOT": "nixos" - }, - "forwardPorts": [8006], - "portsAttributes": { - "8006": { - "label": "Web", - "onAutoForward": "notify" - } - }, - "otherPortsAttributes": { - "onAutoForward": "ignore" - }, - "dockerComposeFile": "../codespaces.yml", - "workspaceFolder": "/workspaces/qemu", - "initializeCommand": "docker system prune --all --force" -} diff --git a/.devcontainer/rocky/devcontainer.json b/.devcontainer/rocky/devcontainer.json deleted file mode 100644 index 127c93df..00000000 --- a/.devcontainer/rocky/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Rocky Linux", - "service": "qemu", - "containerEnv": { - "BOOT": "rocky" - }, - "forwardPorts": [8006], - "portsAttributes": { - "8006": { - "label": "Web", - "onAutoForward": "notify" - } - }, - "otherPortsAttributes": { - "onAutoForward": "ignore" - }, - "dockerComposeFile": "../codespaces.yml", - "workspaceFolder": "/workspaces/qemu", - "initializeCommand": "docker system prune --all --force" -} diff --git a/.devcontainer/slack/devcontainer.json b/.devcontainer/slack/devcontainer.json deleted file mode 100644 index 154f8fdd..00000000 --- a/.devcontainer/slack/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Slackware", - "service": "qemu", - "containerEnv": { - "BOOT": "slack" - }, - "forwardPorts": [8006], - "portsAttributes": { - "8006": { - "label": "Web", - "onAutoForward": "notify" - } - }, - "otherPortsAttributes": { - "onAutoForward": "ignore" - }, - "dockerComposeFile": "../codespaces.yml", - "workspaceFolder": "/workspaces/qemu", - "initializeCommand": "docker system prune --all --force" -} diff --git a/.devcontainer/suse/devcontainer.json b/.devcontainer/suse/devcontainer.json deleted file mode 100644 index c1c84623..00000000 --- a/.devcontainer/suse/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "OpenSUSE", - "service": "qemu", - "containerEnv": { - "BOOT": "suse" - }, - "forwardPorts": [8006], - "portsAttributes": { - "8006": { - "label": "Web", - "onAutoForward": "notify" - } - }, - "otherPortsAttributes": { - "onAutoForward": "ignore" - }, - "dockerComposeFile": "../codespaces.yml", - "workspaceFolder": "/workspaces/qemu", - "initializeCommand": "docker system prune --all --force" -} diff --git a/.devcontainer/tails/devcontainer.json b/.devcontainer/tails/devcontainer.json deleted file mode 100644 index e789135f..00000000 --- a/.devcontainer/tails/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Tails", - "service": "qemu", - "containerEnv": { - "BOOT": "tails" - }, - "forwardPorts": [8006], - "portsAttributes": { - "8006": { - "label": "Web", - "onAutoForward": "notify" - } - }, - "otherPortsAttributes": { - "onAutoForward": "ignore" - }, - "dockerComposeFile": "../codespaces.yml", - "workspaceFolder": "/workspaces/qemu", - "initializeCommand": "docker system prune --all --force" -} diff --git a/.devcontainer/ubuntu/devcontainer.json b/.devcontainer/ubuntu/devcontainer.json deleted file mode 100644 index f6bedf42..00000000 --- a/.devcontainer/ubuntu/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Ubuntu Desktop", - "service": "qemu", - "containerEnv": { - "BOOT": "ubuntu" - }, - "forwardPorts": [8006], - "portsAttributes": { - "8006": { - "label": "Web", - "onAutoForward": "notify" - } - }, - "otherPortsAttributes": { - "onAutoForward": "ignore" - }, - "dockerComposeFile": "../codespaces.yml", - "workspaceFolder": "/workspaces/qemu", - "initializeCommand": "docker system prune --all --force" -} diff --git a/.devcontainer/ubuntus/devcontainer.json b/.devcontainer/ubuntus/devcontainer.json deleted file mode 100644 index cea19d40..00000000 --- a/.devcontainer/ubuntus/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Ubuntu Server", - "service": "qemu", - "containerEnv": { - "BOOT": "ubuntus" - }, - "forwardPorts": [8006], - "portsAttributes": { - "8006": { - "label": "Web", - "onAutoForward": "notify" - } - }, - "otherPortsAttributes": { - "onAutoForward": "ignore" - }, - "dockerComposeFile": "../codespaces.yml", - "workspaceFolder": "/workspaces/qemu", - "initializeCommand": "docker system prune --all --force" -} diff --git a/.devcontainer/xubuntu/devcontainer.json b/.devcontainer/xubuntu/devcontainer.json deleted file mode 100644 index 756ff1e8..00000000 --- a/.devcontainer/xubuntu/devcontainer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Xubuntu", - "service": "qemu", - "containerEnv": { - "BOOT": "xubuntu" - }, - "forwardPorts": [8006], - "portsAttributes": { - "8006": { - "label": "Web", - "onAutoForward": "notify" - } - }, - "otherPortsAttributes": { - "onAutoForward": "ignore" - }, - "dockerComposeFile": "../codespaces.yml", - "workspaceFolder": "/workspaces/qemu", - "initializeCommand": "docker system prune --all --force" -} diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index f08cd935..00000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,10 +0,0 @@ -version: 2 -updates: - - package-ecosystem: docker - directory: / - schedule: - interval: weekly - - package-ecosystem: github-actions - directory: / - schedule: - interval: weekly diff --git a/.github/renovate.json b/.github/renovate.json deleted file mode 100644 index cc31624f..00000000 --- a/.github/renovate.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["config:recommended", ":disableDependencyDashboard"] -} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 2e3703d6..00000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,103 +0,0 @@ -name: Build - -on: - workflow_dispatch: - -concurrency: - group: build - cancel-in-progress: false - -jobs: - shellcheck: - name: Test - uses: ./.github/workflows/check.yml - build: - name: Build - needs: shellcheck - runs-on: ubuntu-latest - permissions: - actions: write - packages: write - contents: read - steps: - - - name: Checkout - uses: actions/checkout@v5 - with: - fetch-depth: 0 - - - name: Docker metadata - id: meta - uses: docker/metadata-action@v5 - with: - context: git - images: | - ghcr.io/${{ github.repository }} - ${{ secrets.DOCKERHUB_REPO }} - tags: | - type=raw,value=latest,priority=100 - type=raw,value=${{ vars.MAJOR }}.${{ vars.MINOR }} - labels: | - org.opencontainers.image.title=${{ vars.NAME }} - env: - DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login into Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Login to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build Docker image - uses: docker/build-push-action@v6 - with: - context: . - push: true - provenance: false - platforms: linux/amd64,linux/arm64 - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - annotations: ${{ steps.meta.outputs.annotations }} - build-args: | - VERSION_ARG=${{ steps.meta.outputs.version }} - - - name: Create a release - uses: action-pack/github-release@v2 - with: - tag: "v${{ steps.meta.outputs.version }}" - title: "v${{ steps.meta.outputs.version }}" - token: ${{ secrets.REPO_ACCESS_TOKEN }} - - - name: Increment version variable - uses: action-pack/bump@v2 - with: - token: ${{ secrets.REPO_ACCESS_TOKEN }} - - - name: Push to Gitlab mirror - uses: action-pack/gitlab-sync@v3 - with: - url: ${{ secrets.GITLAB_URL }} - token: ${{ secrets.GITLAB_TOKEN }} - username: ${{ secrets.GITLAB_USERNAME }} - - - name: Send mail - uses: action-pack/send-mail@v1 - with: - to: ${{secrets.MAILTO}} - from: Github Actions <${{secrets.MAILTO}}> - connection_url: ${{secrets.MAIL_CONNECTION}} - subject: Build of ${{ github.event.repository.name }} v${{ steps.meta.outputs.version }} completed - body: | - The build job of ${{ github.event.repository.name }} v${{ steps.meta.outputs.version }} was completed successfully! - - See https://github.com/${{ github.repository }}/actions for more information. diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 0897a158..820cbe28 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -4,7 +4,6 @@ permissions: {} jobs: shellcheck: - name: shellcheck runs-on: ubuntu-latest steps: - @@ -14,16 +13,20 @@ jobs: name: Run ShellCheck uses: ludeeus/action-shellcheck@master env: - SHELLCHECK_OPTS: -x --source-path=src -e SC2001 -e SC2034 -e SC2064 -e SC2317 -e SC2153 + SHELLCHECK_OPTS: -x --source-path=src -e SC1091 -e SC2001 -e SC2002 -e SC2034 -e SC2064 -e SC2153 -e SC2317 -e SC2028 - name: Lint Dockerfile uses: hadolint/hadolint-action@v3.3.0 with: dockerfile: Dockerfile - ignore: DL3008,DL3003,DL3006 + ignore: DL3006,DL3008 failure-threshold: warning + - + name: Validate XML + uses: action-pack/valid-xml@v1 + with: + path: "assets" + file-endings: ".xml" - name: Validate JSON and YML files uses: GrantBirki/json-yaml-validate@v4 - with: - yaml_exclude_regex: ".*\\kubernetes\\.yml$" diff --git a/.github/workflows/hub.yml b/.github/workflows/hub.yml deleted file mode 100644 index 3629c930..00000000 --- a/.github/workflows/hub.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Update -on: - push: - branches: - - master - paths: - - readme.md - - README.md - - .github/workflows/hub.yml - -jobs: - dockerHubDescription: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - - name: Docker Hub Description - uses: peter-evans/dockerhub-description@v5 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - repository: ${{ secrets.DOCKERHUB_REPO }} - short-description: ${{ github.event.repository.description }} - readme-filepath: ./readme.md diff --git a/.github/workflows/publish-docker.yml b/.github/workflows/publish-docker.yml new file mode 100644 index 00000000..9db5be43 --- /dev/null +++ b/.github/workflows/publish-docker.yml @@ -0,0 +1,53 @@ +name: Docker Build and Push + +on: + release: + types: [created] + +jobs: + shellcheck: + uses: ./.github/workflows/check.yml + build-and-push: + needs: shellcheck + runs-on: ubuntu-latest + permissions: + actions: write + packages: write + contents: read + steps: + - + name: Checkout + uses: actions/checkout@v5 + with: + fetch-depth: 0 + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - + name: Login into Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - + name: Extract version from release + id: version_tag + run: | + # Extract the version from the release tag, removing any 'v' prefix + VERSION_TAG="${GITHUB_REF#refs/tags/}" + CLEAN_VERSION=$(echo "$VERSION_TAG" | sed 's/^v//') + echo "##[set-output name=version;]${CLEAN_VERSION}" + - + name: Build and push version tag + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ secrets.DOCKER_USERNAME }}/qemu-local:${{ steps.version_tag.outputs.version }} + - + name: Build and push latest tag + uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ secrets.DOCKER_USERNAME }}/qemu-local:latest diff --git a/.github/workflows/review.yml b/.github/workflows/review.yml deleted file mode 100644 index 225ce1b7..00000000 --- a/.github/workflows/review.yml +++ /dev/null @@ -1,66 +0,0 @@ -on: - pull_request: - -name: "Review" - -permissions: - contents: read - pull-requests: write - checks: write - -jobs: - review: - name: review - runs-on: ubuntu-latest - steps: - - - name: Checkout code - uses: actions/checkout@v5 - - - name: Spelling - uses: reviewdog/action-misspell@v1 - with: - locale: "US" - level: warning - pattern: | - *.md - *.sh - reporter: github-pr-review - github_token: ${{ secrets.GITHUB_TOKEN }} - - - name: Hadolint - uses: reviewdog/action-hadolint@v1 - with: - level: warning - reporter: github-pr-review - hadolint_ignore: DL3003 DL3006 DL3008 - github_token: ${{ secrets.GITHUB_TOKEN }} - - - name: YamlLint - uses: reviewdog/action-yamllint@v1 - with: - level: warning - reporter: github-pr-review - github_token: ${{ secrets.GITHUB_TOKEN }} - - - name: ActionLint - uses: reviewdog/action-actionlint@v1 - with: - level: warning - reporter: github-pr-review - github_token: ${{ secrets.GITHUB_TOKEN }} - - - name: Shellformat - uses: reviewdog/action-shfmt@v1 - with: - level: warning - shfmt_flags: "-i 2 -ci -bn" - github_token: ${{ secrets.GITHUB_TOKEN }} - - - name: Shellcheck - uses: reviewdog/action-shellcheck@v1 - with: - level: warning - reporter: github-pr-review - shellcheck_flags: -x -e SC2001 -e SC2034 -e SC2064 -e SC2317 -e SC2153 - github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index c275f1ad..00000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,11 +0,0 @@ -on: - workflow_dispatch: - pull_request: - -name: "Test" -permissions: {} - -jobs: - shellcheck: - name: Test - uses: ./.github/workflows/check.yml diff --git a/kubernetes.yml b/kubernetes.yml deleted file mode 100644 index 186bd304..00000000 --- a/kubernetes.yml +++ /dev/null @@ -1,86 +0,0 @@ ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: qemu-pvc -spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 64Gi ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: qemu - labels: - name: qemu -spec: - replicas: 1 - selector: - matchLabels: - app: qemu - template: - metadata: - labels: - app: qemu - spec: - containers: - - name: qemu - image: qemux/qemu - env: - - name: BOOT - value: "mint" - - name: DISK_SIZE - value: "64G" - ports: - - containerPort: 8006 - name: http - protocol: TCP - - containerPort: 5900 - name: vnc - protocol: TCP - securityContext: - capabilities: - add: - - NET_ADMIN - privileged: true - volumeMounts: - - mountPath: /storage - name: storage - - mountPath: /dev/kvm - name: dev-kvm - - mountPath: /dev/net/tun - name: dev-tun - terminationGracePeriodSeconds: 120 - volumes: - - name: storage - persistentVolumeClaim: - claimName: qemu-pvc - - hostPath: - path: /dev/kvm - name: dev-kvm - - hostPath: - path: /dev/net/tun - type: CharDevice - name: dev-tun ---- -apiVersion: v1 -kind: Service -metadata: - name: qemu -spec: - internalTrafficPolicy: Cluster - ports: - - name: http - port: 8006 - protocol: TCP - targetPort: 8006 - - name: vnc - port: 5900 - protocol: TCP - targetPort: 5900 - selector: - app: qemu - type: ClusterIP