#!/usr/bin/env bash set -euo pipefail # Exit on error, unset vars, pipe failures # set -x # Uncomment for debugging # Source OpenStack credentials if available if [ -f "/etc/kolla/admin-openrc.sh" ]; then source /etc/kolla/admin-openrc.sh fi # Activate virtualenv - clearer if/elif structure if [ -f "/opt/deploy/bin/activate" ]; then source /opt/deploy/bin/activate elif [ -f "/etc/kolla/venv/bin/activate" ]; then source /etc/kolla/venv/bin/activate elif [ -f "/opt/ansible-kolla-venv/bin/activate" ]; then source /opt/ansible-kolla-venv/bin/activate elif [ -f "$HOME/venv/bin/activate" ]; then source "$HOME/venv/bin/activate" else echo "Error: No valid Kolla/Ansible virtualenv found." >&2 exit 1 fi # Create images directory IMAGES_DIR="images" mkdir -p "${IMAGES_DIR}" # Helper function to create a flavor if it doesn't already exist create_flavor() { local name="$1" local vcpus="$2" local ram="$3" local disk="$4" if openstack flavor show "$name" >/dev/null 2>&1; then echo "Flavor '$name' already exists — skipping" return 0 fi echo "Creating flavor '$name' (${vcpus} vCPU / ${ram} MB RAM / ${disk} GB disk)..." openstack flavor create \ --vcpus "$vcpus" \ --ram "$ram" \ --disk "$disk" \ --public \ "$name" } # Helper function to upload image only if not already in Glance upload_image_if_needed() { local glance_name="$1" local local_file="$2" local url="$3" local disk_format="${4:-qcow2}" local container_format="${5:-bare}" if [ -f "flags/skip_${glance_name}" ]; then echo "Skipping '${glance_name}' (flags/skip_${glance_name} present)" return 0 fi if openstack image show "${glance_name}" >/dev/null 2>&1; then echo "Image '${glance_name}' already exists in Glance — skipping" return 0 fi # Download only if local file missing AND remote exists if [ ! -f "${local_file}" ]; then if ! curl --fail --silent --head --location --max-time 12 "${url}" >/dev/null; then echo "Remote image not available or HEAD failed: ${url} — skipping '${glance_name}'" return 1 fi echo "Downloading '${glance_name}' from ${url}" if ! wget --progress=bar "${url}" -O "${local_file}"; then echo "Download failed — cleaning up partial file" rm -f "${local_file}" return 1 fi else echo "Local file already exists for '${glance_name}'" fi # Upload if file is present if [ -f "${local_file}" ]; then echo "Creating image '${glance_name}' in Glance" openstack image create "${glance_name}" \ --disk-format "${disk_format}" \ --container-format "${container_format}" \ --file "${local_file}" \ --public || echo "Failed to create image '${glance_name}' (check logs)" else echo "No local file for '${glance_name}' — cannot upload" fi } # Ensure flags directory exists mkdir -p flags # Flavors if [ -f "flags/skip_flavours" ]; then echo "Skipping flavours creation" else # name vcpus ram disk create_flavor 1v1g10g 1 1024 10 create_flavor 1v2g10g 1 2048 10 create_flavor 2v1g10g 2 1024 10 create_flavor 2v2g10g 2 2048 10 create_flavor 4v2g10g 4 2048 10 create_flavor 2v4g20g 2 4096 20 create_flavor 1v1g20g 1 1024 20 create_flavor 1v2g20g 1 2048 20 # ... (add further flavors here using the same pattern) # create_flavor t1.micro 1 1024 0 # create_flavor t2.micro 1 1024 0 # ... (add the rest of t2.* flavors as needed) fi # Try local mirror if flag exists USE_LOCAL=false if [ -f "try-local-images" ]; then USE_LOCAL=true fi # Cirros CIRROS_URL="http://download.cirros-cloud.net/0.5.2/cirros-0.5.2-x86_64-disk.img" CIRROS_LOCAL="http://www.ngtech.home/static/cloud-images/cirros-0.5.2-x86_64-disk.img" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${CIRROS_LOCAL}" >/dev/null && CIRROS_URL="${CIRROS_LOCAL}" upload_image_if_needed "cirros" "${IMAGES_DIR}/cirros-0.5.2-x86_64-disk.img" "${CIRROS_URL}" # AlmaLinux 8 ALMA8_URL="https://repo.almalinux.org/almalinux/8/cloud/x86_64/images/AlmaLinux-8-GenericCloud-latest.x86_64.qcow2" ALMA8_LOCAL="http://www.ngtech.home/static/cloud-images/AlmaLinux-8-GenericCloud-latest.x86_64.qcow2" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${ALMA8_LOCAL}" >/dev/null && ALMA8_URL="${ALMA8_LOCAL}" upload_image_if_needed "almalinux-8" "${IMAGES_DIR}/AlmaLinux-8-GenericCloud-latest.x86_64.qcow2" "${ALMA8_URL}" # AlmaLinux 9 ALMA9_URL="https://repo.almalinux.org/almalinux/9/cloud/x86_64/images/AlmaLinux-9-GenericCloud-latest.x86_64.qcow2" ALMA9_LOCAL="http://www.ngtech.home/static/cloud-images/AlmaLinux-9-GenericCloud-latest.x86_64.qcow2" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${ALMA9_LOCAL}" >/dev/null && ALMA9_URL="${ALMA9_LOCAL}" upload_image_if_needed "almalinux-9" "${IMAGES_DIR}/AlmaLinux-9-GenericCloud-latest.x86_64.qcow2" "${ALMA9_URL}" # AlmaLinux 10 ALMA10_URL="https://repo.almalinux.org/almalinux/10/cloud/x86_64/images/AlmaLinux-10-GenericCloud-latest.x86_64.qcow2" ALMA10_LOCAL="http://www.ngtech.home/static/cloud-images/AlmaLinux-10-GenericCloud-latest.x86_64.qcow2" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${ALMA10_LOCAL}" >/dev/null && ALMA10_URL="${ALMA10_LOCAL}" upload_image_if_needed "almalinux-10" "${IMAGES_DIR}/AlmaLinux-10-GenericCloud-latest.x86_64.qcow2" "${ALMA10_URL}" # Rocky Linux 9 ROCKY9_URL="https://dl.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2" ROCKY9_LOCAL="http://www.ngtech.home/static/cloud-images/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${ROCKY9_LOCAL}" >/dev/null && ROCKY9_URL="${ROCKY9_LOCAL}" upload_image_if_needed "rockylinux-9" "${IMAGES_DIR}/Rocky-9-GenericCloud-Base.latest.x86_64.qcow2" "${ROCKY9_URL}" # Rocky Linux 10 ROCKY10_URL="https://dl.rockylinux.org/pub/rocky/10/images/x86_64/Rocky-10-GenericCloud-Base.latest.x86_64.qcow2" ROCKY10_LOCAL="http://www.ngtech.home/static/cloud-images/Rocky-10-GenericCloud-Base.latest.x86_64.qcow2" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${ROCKY10_LOCAL}" >/dev/null && ROCKY10_URL="${ROCKY10_LOCAL}" upload_image_if_needed "rockylinux-10" "${IMAGES_DIR}/Rocky-10-GenericCloud-Base.latest.x86_64.qcow2" "${ROCKY10_URL}" # CentOS 7 CENTOS7_URL="https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud-2111.qcow2" CENTOS7_LOCAL="http://www.ngtech.home/static/cloud-images/CentOS-7-x86_64-GenericCloud-2111.qcow2" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${CENTOS7_LOCAL}" >/dev/null && CENTOS7_URL="${CENTOS7_LOCAL}" upload_image_if_needed "centos-7" "${IMAGES_DIR}/CentOS-7-x86_64-GenericCloud-2111.qcow2" "${CENTOS7_URL}" # CentOS Stream 8 CENTOS8_URL="https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-latest.x86_64.qcow2" CENTOS8_LOCAL="http://www.ngtech.home/static/cloud-images/CentOS-Stream-GenericCloud-8-latest.x86_64.qcow2" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${CENTOS8_LOCAL}" >/dev/null && CENTOS8_URL="${CENTOS8_LOCAL}" upload_image_if_needed "centos-stream-8" "${IMAGES_DIR}/CentOS-Stream-GenericCloud-8-latest.x86_64.qcow2" "${CENTOS8_URL}" ## CentOS 9 #CENTOS9_URL="https://cloud.centos.org/centos/8-stream/x86_64/images/CentOS-Stream-GenericCloud-8-latest.x86_64.qcow2" #CENTOS9_LOCAL="http://www.ngtech.home/static/cloud-images/CentOS-Stream-GenericCloud-8-latest.x86_64.qcow2" #[ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${CENTOS9_LOCAL}" >/dev/null && CENTOS9_URL="${CENTOS9_LOCAL}" #upload_image_if_needed "centos-9-stream" "${IMAGES_DIR}/CentOS-Stream-GenericCloud-8-latest.x86_64.qcow2" "${CENTOS8_URL}" # Oracle Linux 7 OL7_URL="https://yum.oracle.com/templates/OracleLinux/OL7/u9/x86_64/OL7U9_x86_64-kvm-b257.qcow2" OL7_LOCAL="http://www.ngtech.home/static/cloud-images/OL7U9_x86_64-kvm-b257.qcow2" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${OL7_LOCAL}" >/dev/null && OL7_URL="${OL7_LOCAL}" upload_image_if_needed "oracle-linux-7" "${IMAGES_DIR}/OL7U9_x86_64-kvm-b257.qcow2" "${OL7_URL}" # Oracle Linux 8 # FIX: local URL corrected from .qcow to .qcow2 to match the remote filename OL8_URL="https://yum.oracle.com/templates/OracleLinux/OL8/u10/x86_64/OL8U10_x86_64-kvm-b271.qcow2" OL8_LOCAL="http://www.ngtech.home/static/cloud-images/OL8U10_x86_64-kvm-b271.qcow2" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${OL8_LOCAL}" >/dev/null && OL8_URL="${OL8_LOCAL}" upload_image_if_needed "oracle-linux-8" "${IMAGES_DIR}/OL8U10_x86_64-kvm-b271.qcow2" "${OL8_URL}" # Oracle Linux 9 OL9_URL="https://yum.oracle.com/templates/OracleLinux/OL9/u7/x86_64/OL9U7_x86_64-kvm-b269.qcow2" OL9_LOCAL="http://www.ngtech.home/static/cloud-images/OL9U7_x86_64-kvm-b269.qcow2" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${OL9_LOCAL}" >/dev/null && OL9_URL="${OL9_LOCAL}" upload_image_if_needed "oracle-linux-9" "${IMAGES_DIR}/OL9U7_x86_64-kvm-b269.qcow2" "${OL9_URL}" # Oracle Linux 10 OL10_URL="https://yum.oracle.com/templates/OracleLinux/OL10/u1/x86_64/OL10U1_x86_64-kvm-b270.qcow2" OL10_LOCAL="http://www.ngtech.home/static/cloud-images/OL10U1_x86_64-kvm-b270.qcow2" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${OL10_LOCAL}" >/dev/null && OL10_URL="${OL10_LOCAL}" upload_image_if_needed "oracle-linux-10" "${IMAGES_DIR}/OL10U1_x86_64-kvm-b270.qcow2" "${OL10_URL}" # Fedora 43 # FIX: glance name was "oracle-linux-10" (wrong), corrected to "fedora-43" FEDORA43_URL="https://download.fedoraproject.org/pub/fedora/linux/releases/43/Cloud/x86_64/images/Fedora-Cloud-Base-Generic-43-1.6.x86_64.qcow2" FEDORA43_LOCAL="http://www.ngtech.home/static/cloud-images/Fedora-Cloud-Base-Generic-43-1.6.x86_64.qcow2" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${FEDORA43_LOCAL}" >/dev/null && FEDORA43_URL="${FEDORA43_LOCAL}" upload_image_if_needed "fedora-43" "${IMAGES_DIR}/Fedora-Cloud-Base-Generic-43-1.6.x86_64.qcow2" "${FEDORA43_URL}" # Debian 11 # FIX: local variable was misnamed DEBIAN12_LOCAL, corrected to DEBIAN11_LOCAL DEBIAN11_URL="https://cloud.debian.org/images/cloud/bullseye/latest/debian-11-generic-amd64.qcow2" DEBIAN11_LOCAL="http://www.ngtech.home/static/cloud-images/debian-11-generic-amd64.qcow2" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${DEBIAN11_LOCAL}" >/dev/null && DEBIAN11_URL="${DEBIAN11_LOCAL}" upload_image_if_needed "debian-11" "${IMAGES_DIR}/debian-11-generic-amd64.qcow2" "${DEBIAN11_URL}" # Debian 12 DEBIAN12_URL="https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-generic-amd64.qcow2" DEBIAN12_LOCAL="http://www.ngtech.home/static/cloud-images/debian-12-generic-amd64.qcow2" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${DEBIAN12_LOCAL}" >/dev/null && DEBIAN12_URL="${DEBIAN12_LOCAL}" upload_image_if_needed "debian-12" "${IMAGES_DIR}/debian-12-generic-amd64.qcow2" "${DEBIAN12_URL}" # Debian 13 DEBIAN13_URL="https://cloud.debian.org/images/cloud/trixie/latest/debian-13-generic-amd64.qcow2" DEBIAN13_LOCAL="http://www.ngtech.home/static/cloud-images/debian-13-generic-amd64.qcow2" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${DEBIAN13_LOCAL}" >/dev/null && DEBIAN13_URL="${DEBIAN13_LOCAL}" upload_image_if_needed "debian-13" "${IMAGES_DIR}/debian-13-generic-amd64.qcow2" "${DEBIAN13_URL}" # Ubuntu Jammy (22.04) UBUNTU_JAMMY_URL="https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img" UBUNTU_JAMMY_LOCAL="http://www.ngtech.home/static/cloud-images/jammy-server-cloudimg-amd64.img" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${UBUNTU_JAMMY_LOCAL}" >/dev/null && UBUNTU_JAMMY_URL="${UBUNTU_JAMMY_LOCAL}" upload_image_if_needed "ubuntu-jammy" "${IMAGES_DIR}/jammy-server-cloudimg-amd64.img" "${UBUNTU_JAMMY_URL}" # Ubuntu Focal (20.04) # FIX: comment corrected from "24.04" to "20.04" (Focal is 20.04; Noble is 24.04) UBUNTU_FOCAL_URL="https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img" UBUNTU_FOCAL_LOCAL="http://www.ngtech.home/static/cloud-images/focal-server-cloudimg-amd64.img" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${UBUNTU_FOCAL_LOCAL}" >/dev/null && UBUNTU_FOCAL_URL="${UBUNTU_FOCAL_LOCAL}" upload_image_if_needed "ubuntu-focal" "${IMAGES_DIR}/focal-server-cloudimg-amd64.img" "${UBUNTU_FOCAL_URL}" # Ubuntu Noble (24.04) UBUNTU_NOBLE_URL="https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img" UBUNTU_NOBLE_LOCAL="http://www.ngtech.home/static/cloud-images/noble-server-cloudimg-amd64.img" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${UBUNTU_NOBLE_LOCAL}" >/dev/null && UBUNTU_NOBLE_URL="${UBUNTU_NOBLE_LOCAL}" upload_image_if_needed "ubuntu-noble" "${IMAGES_DIR}/noble-server-cloudimg-amd64.img" "${UBUNTU_NOBLE_URL}" # Ubuntu Resolute (26.04) UBUNTU_RESOLUTE_URL="https://cloud-images.ubuntu.com/resolute/current/resolute-server-cloudimg-amd64.img" UBUNTU_RESOLUTE_LOCAL="http://www.ngtech.home/static/cloud-images/resolute-server-cloudimg-amd64.img" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${UBUNTU_RESOLUTE_LOCAL}" >/dev/null && UBUNTU_RESOLUTE_URL="${UBUNTU_RESOLUTE_LOCAL}" upload_image_if_needed "ubuntu-resolute" "${IMAGES_DIR}/resolute-server-cloudimg-amd64.img" "${UBUNTU_RESOLUTE_URL}" # Alpine Linux 3.23.4 Generic ALPINE_GENERIC_URL="https://dl-cdn.alpinelinux.org/alpine/v3.23/releases/cloud/generic_alpine-3.23.4-x86_64-bios-cloudinit-r0.qcow2" ALPINE_GENERIC_LOCAL="http://www.ngtech.home/static/cloud-images/generic_alpine-3.23.4-x86_64-bios-cloudinit-r0.qcow2" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${ALPINE_GENERIC_LOCAL}" >/dev/null && ALPINE_GENERIC_URL="${ALPINE_GENERIC_LOCAL}" upload_image_if_needed "alpine-generic-3.23.4" "${IMAGES_DIR}/generic_alpine-3.23.4-x86_64-bios-cloudinit-r0.qcow2" "${ALPINE_GENERIC_URL}" # openEuler 24.03 LTS SP3 - compressed .xz - special handling OPENEULER_URL="https://mirrors.tuna.tsinghua.edu.cn/openeuler/openEuler-24.03-LTS-SP3/virtual_machine_img/x86_64/openEuler-24.03-LTS-SP3-x86_64.qcow2.xz" OPENEULER_LOCAL="http://www.ngtech.home/static/cloud-images/openEuler-24.03-LTS-SP3-x86_64.qcow2.xz" OPENEULER_ARCHIVE="${IMAGES_DIR}/openEuler-24.03-LTS-SP3-x86_64.qcow2.xz" OPENEULER_FILE="${IMAGES_DIR}/openEuler-24.03-LTS-SP3-x86_64.qcow2" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${OPENEULER_LOCAL}" >/dev/null && OPENEULER_URL="${OPENEULER_LOCAL}" if [ -f "flags/skip_openeuler-2403" ]; then echo "Skipping 'openeuler-2403' (flags/skip_openeuler-2403 present)" elif openstack image show "openeuler-2403" >/dev/null 2>&1; then echo "Image 'openeuler-2403' already exists — skipping" else if [ ! -f "${OPENEULER_FILE}" ]; then if curl --fail --silent --head --location --max-time 12 "${OPENEULER_URL}" >/dev/null; then echo "Downloading openEuler archive from ${OPENEULER_URL}" wget --progress=bar "${OPENEULER_URL}" -O "${OPENEULER_ARCHIVE}" || { rm -f "${OPENEULER_ARCHIVE}"; echo "Download failed"; } if [ -f "${OPENEULER_ARCHIVE}" ]; then echo "Decompressing openEuler image..." xz -d -k "${OPENEULER_ARCHIVE}" || { echo "Decompression failed"; rm -f "${OPENEULER_ARCHIVE}" "${OPENEULER_FILE}"; } mv "${OPENEULER_ARCHIVE%.xz}" "${OPENEULER_FILE}" 2>/dev/null || true fi else echo "openEuler remote not available — skipping" fi fi if [ -f "${OPENEULER_FILE}" ]; then openstack image create "openeuler-2403" \ --disk-format qcow2 \ --container-format bare \ --file "${OPENEULER_FILE}" \ --public || echo "Failed to upload openEuler" fi fi # OPNsense 26.1.6 - compressed .bz2 - special handling OPNSENSE_URL="https://pkg.opnsense.org/releases/26.1.6/OPNsense-26.1.6-nano-amd64.img.bz2" OPNSENSE_LOCAL="http://www.ngtech.home/static/cloud-images/OPNsense-26.1.6-nano-amd64.img.bz2" OPNSENSE_ARCHIVE="${IMAGES_DIR}/OPNsense-26.1.6-nano-amd64.img.bz2" OPNSENSE_FILE="${IMAGES_DIR}/OPNsense-26.1.6-nano-amd64.img" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${OPNSENSE_LOCAL}" >/dev/null && OPNSENSE_URL="${OPNSENSE_LOCAL}" if [ -f "flags/skip_opnsense-26.1.6" ]; then echo "Skipping 'opnsense-26.1.6' (flags/skip_opnsense-26.1.6 present)" elif openstack image show "opnsense-26.1.6" >/dev/null 2>&1; then echo "Image 'opnsense-26.1.6' already exists — skipping" else if [ ! -f "${OPNSENSE_FILE}" ]; then if curl --fail --silent --head --location --max-time 12 "${OPNSENSE_URL}" >/dev/null; then echo "Downloading OPNsense archive from ${OPNSENSE_URL}" wget --progress=bar "${OPNSENSE_URL}" -O "${OPNSENSE_ARCHIVE}" || { rm -f "${OPNSENSE_ARCHIVE}"; echo "Download failed"; } # FIX: was checking ${OPENEULER_ARCHIVE} instead of ${OPNSENSE_ARCHIVE} if [ -f "${OPNSENSE_ARCHIVE}" ]; then echo "Decompressing OPNsense image..." bunzip2 "${OPNSENSE_ARCHIVE}" || { echo "Decompression failed"; rm -f "${OPNSENSE_ARCHIVE}" "${OPNSENSE_FILE}"; } fi else echo "OPNsense remote not available — skipping" fi fi if [ -f "${OPNSENSE_FILE}" ]; then qemu-img convert -O qcow2 "${OPNSENSE_FILE}" "${OPNSENSE_FILE%.img}.qcow2" # FIX: removed trailing space from image name "opnsense-26.1.6 " → "opnsense-26.1.6" openstack image create "opnsense-26.1.6" \ --disk-format qcow2 \ --container-format bare \ --file "${OPNSENSE_FILE%.img}.qcow2" \ --public || echo "Failed to upload OPNsense" fi fi # NethSecurity 8.7.2 # Note: squashfs-combined-efi is an OpenWrt-style image; it will boot but does # not support cloud-init metadata (hostname, SSH key injection, etc.) NETH_URL="https://updates.nethsecurity.nethserver.org/stable/8.7.2/targets/x86/64/nethsecurity-8.7.2-x86-64-generic-squashfs-combined-efi.img.gz" NETH_LOCAL="http://www.ngtech.home/static/cloud-images/nethsecurity-8.7.2-x86-64-generic-squashfs-combined-efi.img.gz" NETH_ARCHIVE="${IMAGES_DIR}/nethsecurity-8.7.2-x86-64-generic-squashfs-combined-efi.img.gz" NETH_FILE="${IMAGES_DIR}/nethsecurity-8.7.2-x86-64-generic-squashfs-combined-efi.img" [ "$USE_LOCAL" = true ] && curl --fail --silent --head --location "${NETH_LOCAL}" >/dev/null && NETH_URL="${NETH_LOCAL}" if [ -f "flags/skip_nethsecurity-8.7.2" ]; then echo "Skipping 'nethsecurity-8.7.2' (flags/skip_nethsecurity-8.7.2 present)" elif openstack image show "nethsecurity-8.7.2" >/dev/null 2>&1; then echo "Image 'nethsecurity-8.7.2' already exists — skipping" else if [ ! -f "${NETH_FILE}" ]; then if curl --fail --silent --head --location --max-time 12 "${NETH_URL}" >/dev/null; then echo "Downloading NethSecurity archive from ${NETH_URL}" wget --progress=bar "${NETH_URL}" -O "${NETH_ARCHIVE}" || { rm -f "${NETH_ARCHIVE}"; echo "Download failed"; } if [ -f "${NETH_ARCHIVE}" ]; then echo "Decompressing NethSecurity image..." set +e zcat "${NETH_ARCHIVE}" > "${NETH_FILE}" set -e fi else echo "NethSecurity remote not available — skipping" fi fi if [ -f "${NETH_FILE}" ]; then echo "Converting NethSecurity image to qcow2..." qemu-img convert -O qcow2 "${NETH_FILE}" "${NETH_FILE%.img}.qcow2" echo "Uploading NethSecurity to OpenStack..." openstack image create "nethsecurity-8.7.2" \ --disk-format qcow2 \ --container-format bare \ --file "${NETH_FILE%.img}.qcow2" \ --public || echo "Failed to upload NethSecurity" fi fi # Keypair (unchanged) openstack keypair show multi || openstack keypair create --public-key multi.pub multi echo "Bootstrap script completed." # set +x # if you had set -x earlier