From c470dc7e5ee59f8db3c6feda52b797c30281a773 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Thu, 13 Nov 2025 14:17:42 +0100 Subject: [PATCH] Add OS and Cloud-Init selection to UniFi OS VM script This update introduces interactive selection of operating system (Debian 13 or Ubuntu 24.04) and Cloud-Init support for VM creation. The script now fetches the latest UniFi OS Server installer dynamically, configures VM settings based on user choices, and injects the installer into the appropriate cloud image. Cloud-Init configuration is handled automatically for Ubuntu and optionally for Debian, improving flexibility and automation. --- vm/unifi-os-vm.sh | 222 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 185 insertions(+), 37 deletions(-) diff --git a/vm/unifi-os-vm.sh b/vm/unifi-os-vm.sh index 576cb2376..23bf8992c 100644 --- a/vm/unifi-os-vm.sh +++ b/vm/unifi-os-vm.sh @@ -5,6 +5,8 @@ # License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE source /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/api.func) +# Load Cloud-Init library for VM configuration +source /dev/stdin <<<$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVED/main/misc/cloud-init.sh) 2>/dev/null || true function header_info() { clear @@ -23,11 +25,13 @@ GEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\(..\)/\1: RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)" METHOD="" NSAPP="UniFi OS Server" -var_os="linux" -var_version="x64" -UOS_VERSION="4.3.5" -UOS_URL="https://fw-download.ubnt.com/data/unifi-os-server/da70-linux-x64-4.3.5-5306ffbb-fc6d-4414-912b-29cbfb0ce85a.5-x64" -UOS_INSTALLER="unifi-os-server-${UOS_VERSION}.bin" +var_os="debian" +var_version="13" +USE_CLOUD_INIT="no" +OS_TYPE="" +OS_VERSION="" +OS_CODENAME="" +OS_DISPLAY="" YW=$(echo "\033[33m") BL=$(echo "\033[36m") @@ -61,6 +65,7 @@ MACADDRESS="${TAB}šŸ”—${TAB}${CL}" VLANTAG="${TAB}šŸ·ļø${TAB}${CL}" CREATING="${TAB}šŸš€${TAB}${CL}" ADVANCED="${TAB}🧩${TAB}${CL}" +CLOUD="${TAB}ā˜ļø${TAB}${CL}" THIN="discard=on,ssd=1," set -Eeuo pipefail @@ -204,13 +209,86 @@ function exit-script() { exit } +function select_os() { + if OS_CHOICE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "SELECT OS" --radiolist \ + "Choose Operating System for UniFi OS VM" 12 68 2 \ + "debian13" "Debian 13 (Trixie) - Latest" ON \ + "ubuntu2404" "Ubuntu 24.04 LTS (Noble)" OFF \ + 3>&1 1>&2 2>&3); then + case $OS_CHOICE in + debian13) + OS_TYPE="debian" + OS_VERSION="13" + OS_CODENAME="trixie" + OS_DISPLAY="Debian 13 (Trixie)" + ;; + ubuntu2404) + OS_TYPE="ubuntu" + OS_VERSION="24.04" + OS_CODENAME="noble" + OS_DISPLAY="Ubuntu 24.04 LTS" + ;; + esac + echo -e "${OS}${BOLD}${DGN}Operating System: ${BGN}${OS_DISPLAY}${CL}" + else + exit-script + fi +} + +function select_cloud_init() { + # Ubuntu only has cloudimg variant (always Cloud-Init), so no choice needed + if [ "$OS_TYPE" = "ubuntu" ]; then + USE_CLOUD_INIT="yes" + echo -e "${CLOUD}${BOLD}${DGN}Cloud-Init: ${BGN}yes (Ubuntu requires Cloud-Init)${CL}" + return + fi + + # Debian has two image variants, so user can choose + if (whiptail --backtitle "Proxmox VE Helper Scripts" --title "CLOUD-INIT" \ + --yesno "Enable Cloud-Init for VM configuration?\n\nCloud-Init allows automatic configuration of:\n• User accounts and passwords\n• SSH keys\n• Network settings (DHCP/Static)\n• DNS configuration\n\nYou can also configure these settings later in Proxmox UI.\n\nNote: Debian without Cloud-Init will use nocloud image with console auto-login." 18 68); then + USE_CLOUD_INIT="yes" + echo -e "${CLOUD}${BOLD}${DGN}Cloud-Init: ${BGN}yes${CL}" + else + USE_CLOUD_INIT="no" + echo -e "${CLOUD}${BOLD}${DGN}Cloud-Init: ${BGN}no${CL}" + fi +} + +function get_image_url() { + local arch=$(dpkg --print-architecture) + case $OS_TYPE in + debian) + # Debian has two variants: + # - generic: For Cloud-Init enabled VMs + # - nocloud: For VMs without Cloud-Init (has console auto-login) + if [ "$USE_CLOUD_INIT" = "yes" ]; then + echo "https://cloud.debian.org/images/cloud/${OS_CODENAME}/latest/debian-${OS_VERSION}-generic-${arch}.qcow2" + else + echo "https://cloud.debian.org/images/cloud/${OS_CODENAME}/latest/debian-${OS_VERSION}-nocloud-${arch}.qcow2" + fi + ;; + ubuntu) + # Ubuntu only has cloudimg variant (always with Cloud-Init support) + echo "https://cloud-images.ubuntu.com/${OS_CODENAME}/current/${OS_CODENAME}-server-cloudimg-${arch}.img" + ;; + esac +} + function default_settings() { + # OS Selection - ALWAYS ask + select_os + + # Cloud-Init Selection - ALWAYS ask + select_cloud_init + + # Set defaults for other settings VMID=$(get_valid_nextid) FORMAT="" - MACHINE="q35" + MACHINE=" -machine q35" + DISK_CACHE="" DISK_SIZE="30G" HN="unifi-server-os" - CPU_TYPE="" + CPU_TYPE=" -cpu host" CORE_COUNT="2" RAM_SIZE="4096" BRG="vmbr0" @@ -220,10 +298,11 @@ function default_settings() { START_VM="yes" METHOD="default" echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}" - echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}q35${CL}" + echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}Q35 (Modern)${CL}" echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}" + echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}None${CL}" echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}" - echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}KVM64${CL}" + echo -e "${OS}${BOLD}${DGN}CPU Model: ${BGN}Host${CL}" echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}" echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}" echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${CL}" @@ -231,11 +310,18 @@ function default_settings() { echo -e "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}" echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${BGN}Default${CL}" echo -e "${GATEWAY}${BOLD}${DGN}Start VM when completed: ${BGN}yes${CL}" - echo -e "${CREATING}${BOLD}${DGN}Creating a Unifi OS VM using the above default settings${CL}" + echo -e "${CREATING}${BOLD}${DGN}Creating a UniFi OS VM using the above default settings${CL}" } function advanced_settings() { METHOD="advanced" + + # OS Selection - ALWAYS ask + select_os + + # Cloud-Init Selection - ALWAYS ask + select_cloud_init + [ -z "${VMID:-}" ] && VMID=$(get_valid_nextid) while true; do if VMID=$(whiptail --backtitle "Proxmox VE Helper Scripts" --inputbox "Set Virtual Machine ID" 8 58 $VMID --title "VIRTUAL MACHINE ID" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then @@ -255,15 +341,15 @@ function advanced_settings() { done if MACH=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "MACHINE TYPE" --radiolist --cancel-button Exit-Script "Choose Machine Type" 10 58 2 \ - "q35" "Modern (PCIe, UEFI, default)" ON \ - "i440fx" "Legacy (older compatibility)" OFF \ + "q35" "Q35 (Modern, PCIe, UEFI)" ON \ + "i440fx" "i440fx (Legacy)" OFF \ 3>&1 1>&2 2>&3); then if [ "$MACH" = "q35" ]; then - echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}q35${CL}" + echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}Q35 (Modern)${CL}" FORMAT="" MACHINE=" -machine q35" else - echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx${CL}" + echo -e "${CONTAINERTYPE}${BOLD}${DGN}Machine Type: ${BGN}i440fx (Legacy)${CL}" FORMAT=",efitype=4m" MACHINE="" fi @@ -287,8 +373,8 @@ function advanced_settings() { fi if DISK_CACHE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "DISK CACHE" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \ - "0" "None" OFF \ - "1" "Write Through (Default)" ON \ + "0" "None (Default)" ON \ + "1" "Write Through" OFF \ 3>&1 1>&2 2>&3); then if [ $DISK_CACHE = "1" ]; then echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}" @@ -314,8 +400,8 @@ function advanced_settings() { fi if CPU_TYPE1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "CPU MODEL" --radiolist "Choose CPU Model" --cancel-button Exit-Script 10 58 2 \ - "KVM64" "Default – safe for migration/compatibility" ON \ - "Host" "Use host CPU features (faster, no migration)" OFF \ + "Host" "Host (Faster, recommended)" ON \ + "KVM64" "KVM64 (Compatibility)" OFF \ 3>&1 1>&2 2>&3); then case "$CPU_TYPE1" in Host) @@ -468,20 +554,63 @@ fi msg_ok "Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location." msg_ok "Virtual Machine ID is ${CL}${BL}$VMID${CL}." -# --- Download Debian Cloud Image --- -msg_info "Downloading Debian 13 Cloud Image" -URL="https://cloud.debian.org/images/cloud/trixie/latest/debian-13-nocloud-amd64.qcow2" +# Fetch latest UniFi OS Server version and download URL +msg_info "Fetching latest UniFi OS Server version" + +# Install jq if not available +if ! command -v jq &>/dev/null; then + msg_info "Installing jq for JSON parsing" + apt-get update -qq >/dev/null 2>&1 + apt-get install -y jq -qq >/dev/null 2>&1 +fi + +# Download firmware list from Ubiquiti API +API_URL="https://fw-update.ui.com/api/firmware-latest" +TEMP_JSON=$(mktemp) + +if ! curl -fsSL "$API_URL" -o "$TEMP_JSON"; then + rm -f "$TEMP_JSON" + msg_error "Failed to fetch data from Ubiquiti API" + exit 1 +fi + +# Parse JSON to find latest unifi-os-server linux-x64 version +LATEST=$(jq -r ' + ._embedded.firmware + | map(select(.product == "unifi-os-server")) + | map(select(.platform == "linux-x64")) + | sort_by(.version_major, .version_minor, .version_patch) + | last +' "$TEMP_JSON") + +UOS_VERSION=$(echo "$LATEST" | jq -r '.version' | sed 's/^v//') +UOS_URL=$(echo "$LATEST" | jq -r '._links.data.href') + +# Cleanup temp file +rm -f "$TEMP_JSON" + +if [ -z "$UOS_URL" ] || [ -z "$UOS_VERSION" ]; then + msg_error "Failed to parse UniFi OS Server version or download URL" + exit 1 +fi + +UOS_INSTALLER="unifi-os-server-${UOS_VERSION}.bin" +msg_ok "Found UniFi OS Server ${UOS_VERSION}" + +# --- Download Cloud Image --- +msg_info "Downloading ${OS_DISPLAY} Cloud Image" +URL=$(get_image_url) CACHE_DIR="/var/lib/vz/template/cache" CACHE_FILE="$CACHE_DIR/$(basename "$URL")" -FILE_IMG="/var/lib/vz/template/tmp/${CACHE_FILE##*/%.xz}" # .qcow2 +FILE_IMG="/var/lib/vz/template/tmp/${CACHE_FILE##*/%.xz}" if [[ ! -s "$CACHE_FILE" ]]; then curl -f#SL -o "$CACHE_FILE" "$URL" msg_ok "Downloaded ${CL}${BL}$(basename "$CACHE_FILE")${CL}" else msg_ok "Using cached image ${CL}${BL}$(basename "$CACHE_FILE")${CL}" fi -FILE="debian-13-nocloud-amd64.qcow2" -msg_ok "Downloaded Debian Cloud Image" +FILE="$(basename "$CACHE_FILE")" +msg_ok "Downloaded ${OS_DISPLAY} Cloud Image" # --- Inject UniFi Installer --- if ! command -v virt-customize &>/dev/null; then @@ -492,18 +621,28 @@ if ! command -v virt-customize &>/dev/null; then fi msg_info "Injecting UniFi OS Installer into Cloud Image" -virt-customize -q -a "$FILE" \ - --run-command "echo 'nameserver 1.1.1.1' > /etc/resolv.conf" \ - --install qemu-guest-agent,ca-certificates,curl,lsb-release,podman \ - --run-command "curl -fsSL '${UOS_URL}' -o /root/${UOS_INSTALLER} && chmod +x /root/${UOS_INSTALLER}" \ - --run-command 'mkdir -p /etc/systemd/system/getty@tty1.service.d' \ - --run-command "bash -c 'echo -e \"[Service]\nExecStart=\nExecStart=-/sbin/agetty --autologin root --noclear %I \$TERM\" > /etc/systemd/system/getty@tty1.service.d/override.conf'" \ - >/dev/null +if [ "$USE_CLOUD_INIT" = "yes" ]; then + # Cloud-Init enabled: No auto-login, standard setup + virt-customize -q -a "$FILE" \ + --run-command "echo 'nameserver 1.1.1.1' > /etc/resolv.conf" \ + --install qemu-guest-agent,ca-certificates,curl,lsb-release,podman \ + --run-command "curl -fsSL '${UOS_URL}' -o /root/${UOS_INSTALLER} && chmod +x /root/${UOS_INSTALLER}" \ + >/dev/null +else + # No Cloud-Init: Keep auto-login for console access + virt-customize -q -a "$FILE" \ + --run-command "echo 'nameserver 1.1.1.1' > /etc/resolv.conf" \ + --install qemu-guest-agent,ca-certificates,curl,lsb-release,podman \ + --run-command "curl -fsSL '${UOS_URL}' -o /root/${UOS_INSTALLER} && chmod +x /root/${UOS_INSTALLER}" \ + --run-command 'mkdir -p /etc/systemd/system/getty@tty1.service.d' \ + --run-command "bash -c 'echo -e \"[Service]\nExecStart=\nExecStart=-/sbin/agetty --autologin root --noclear %I \$TERM\" > /etc/systemd/system/getty@tty1.service.d/override.conf'" \ + >/dev/null +fi msg_ok "UniFi OS Installer integrated" msg_info "Creating UniFi OS VM" -qm create "$VMID" -agent 1 $MACHINE -tablet 0 -localtime 1 -bios ovmf \ - $CPU_TYPE -cores "$CORE_COUNT" -memory "$RAM_SIZE" \ +qm create "$VMID" -agent 1${MACHINE} -tablet 0 -localtime 1 -bios ovmf \ + ${CPU_TYPE} -cores "$CORE_COUNT" -memory "$RAM_SIZE" \ -name "$HN" -tags community-script \ -net0 virtio,bridge="$BRG",macaddr="$MAC""$VLAN""$MTU" \ -onboot 1 -ostype l26 -scsihw virtio-scsi-pci @@ -518,11 +657,18 @@ fi qm set "$VMID" \ -efidisk0 "${STORAGE}:0${FORMAT},size=4M" \ - -scsi0 "${DISK_REF},size=${DISK_SIZE}" \ + -scsi0 "${DISK_REF},${DISK_CACHE}size=${DISK_SIZE}" \ -boot order=scsi0 -serial0 socket >/dev/null qm resize "$VMID" scsi0 "$DISK_SIZE" >/dev/null qm set "$VMID" --agent enabled=1 >/dev/null +# Add Cloud-Init drive if enabled +if [ "$USE_CLOUD_INIT" = "yes" ]; then + msg_info "Configuring Cloud-Init" + qm set "$VMID" --ide2 "${STORAGE}:cloudinit" >/dev/null + msg_ok "Cloud-Init configured" +fi + DESCRIPTION=$( cat < @@ -555,11 +701,13 @@ EOF ) qm set "$VMID" -description "$DESCRIPTION" >/dev/null -msg_ok "Created a Unifi OS VM ${CL}${BL}(${HN})" +msg_ok "Created a UniFi OS VM ${CL}${BL}(${HN})" +msg_info "Operating System: ${OS_DISPLAY}" +msg_info "Cloud-Init: ${USE_CLOUD_INIT}" if [ "$START_VM" == "yes" ]; then - msg_info "Starting Unifi OS VM" + msg_info "Starting UniFi OS VM" qm start $VMID - msg_ok "Started Unifi OS VM" + msg_ok "Started UniFi OS VM" fi post_update_to_api "done" "none" msg_ok "Completed Successfully!\n"