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.
This commit is contained in:
CanbiZ 2025-11-13 14:17:42 +01:00
parent 055aa760c6
commit c470dc7e5e

View File

@ -5,6 +5,8 @@
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE # 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) 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() { function header_info() {
clear 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)" RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)"
METHOD="" METHOD=""
NSAPP="UniFi OS Server" NSAPP="UniFi OS Server"
var_os="linux" var_os="debian"
var_version="x64" var_version="13"
UOS_VERSION="4.3.5" USE_CLOUD_INIT="no"
UOS_URL="https://fw-download.ubnt.com/data/unifi-os-server/da70-linux-x64-4.3.5-5306ffbb-fc6d-4414-912b-29cbfb0ce85a.5-x64" OS_TYPE=""
UOS_INSTALLER="unifi-os-server-${UOS_VERSION}.bin" OS_VERSION=""
OS_CODENAME=""
OS_DISPLAY=""
YW=$(echo "\033[33m") YW=$(echo "\033[33m")
BL=$(echo "\033[36m") BL=$(echo "\033[36m")
@ -61,6 +65,7 @@ MACADDRESS="${TAB}🔗${TAB}${CL}"
VLANTAG="${TAB}🏷️${TAB}${CL}" VLANTAG="${TAB}🏷️${TAB}${CL}"
CREATING="${TAB}🚀${TAB}${CL}" CREATING="${TAB}🚀${TAB}${CL}"
ADVANCED="${TAB}🧩${TAB}${CL}" ADVANCED="${TAB}🧩${TAB}${CL}"
CLOUD="${TAB}☁️${TAB}${CL}"
THIN="discard=on,ssd=1," THIN="discard=on,ssd=1,"
set -Eeuo pipefail set -Eeuo pipefail
@ -204,13 +209,86 @@ function exit-script() {
exit 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() { 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) VMID=$(get_valid_nextid)
FORMAT="" FORMAT=""
MACHINE="q35" MACHINE=" -machine q35"
DISK_CACHE=""
DISK_SIZE="30G" DISK_SIZE="30G"
HN="unifi-server-os" HN="unifi-server-os"
CPU_TYPE="" CPU_TYPE=" -cpu host"
CORE_COUNT="2" CORE_COUNT="2"
RAM_SIZE="4096" RAM_SIZE="4096"
BRG="vmbr0" BRG="vmbr0"
@ -220,10 +298,11 @@ function default_settings() {
START_VM="yes" START_VM="yes"
METHOD="default" METHOD="default"
echo -e "${CONTAINERID}${BOLD}${DGN}Virtual Machine ID: ${BGN}${VMID}${CL}" 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 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 "${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 "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}"
echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}" echo -e "${RAMSIZE}${BOLD}${DGN}RAM Size: ${BGN}${RAM_SIZE}${CL}"
echo -e "${BRIDGE}${BOLD}${DGN}Bridge: ${BGN}${BRG}${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 "${VLANTAG}${BOLD}${DGN}VLAN: ${BGN}Default${CL}"
echo -e "${DEFAULT}${BOLD}${DGN}Interface MTU Size: ${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 "${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() { function advanced_settings() {
METHOD="advanced" METHOD="advanced"
# OS Selection - ALWAYS ask
select_os
# Cloud-Init Selection - ALWAYS ask
select_cloud_init
[ -z "${VMID:-}" ] && VMID=$(get_valid_nextid) [ -z "${VMID:-}" ] && VMID=$(get_valid_nextid)
while true; do 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 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 done
if MACH=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "MACHINE TYPE" --radiolist --cancel-button Exit-Script "Choose Machine Type" 10 58 2 \ 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 \ "q35" "Q35 (Modern, PCIe, UEFI)" ON \
"i440fx" "Legacy (older compatibility)" OFF \ "i440fx" "i440fx (Legacy)" OFF \
3>&1 1>&2 2>&3); then 3>&1 1>&2 2>&3); then
if [ "$MACH" = "q35" ]; 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="" FORMAT=""
MACHINE=" -machine q35" MACHINE=" -machine q35"
else 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" FORMAT=",efitype=4m"
MACHINE="" MACHINE=""
fi fi
@ -287,8 +373,8 @@ function advanced_settings() {
fi fi
if DISK_CACHE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "DISK CACHE" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \ if DISK_CACHE=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "DISK CACHE" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \
"0" "None" OFF \ "0" "None (Default)" ON \
"1" "Write Through (Default)" ON \ "1" "Write Through" OFF \
3>&1 1>&2 2>&3); then 3>&1 1>&2 2>&3); then
if [ $DISK_CACHE = "1" ]; then if [ $DISK_CACHE = "1" ]; then
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}" echo -e "${DISKSIZE}${BOLD}${DGN}Disk Cache: ${BGN}Write Through${CL}"
@ -314,8 +400,8 @@ function advanced_settings() {
fi fi
if CPU_TYPE1=$(whiptail --backtitle "Proxmox VE Helper Scripts" --title "CPU MODEL" --radiolist "Choose CPU Model" --cancel-button Exit-Script 10 58 2 \ 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" "Host (Faster, recommended)" ON \
"Host" "Use host CPU features (faster, no migration)" OFF \ "KVM64" "KVM64 (Compatibility)" OFF \
3>&1 1>&2 2>&3); then 3>&1 1>&2 2>&3); then
case "$CPU_TYPE1" in case "$CPU_TYPE1" in
Host) Host)
@ -468,20 +554,63 @@ fi
msg_ok "Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location." msg_ok "Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location."
msg_ok "Virtual Machine ID is ${CL}${BL}$VMID${CL}." msg_ok "Virtual Machine ID is ${CL}${BL}$VMID${CL}."
# --- Download Debian Cloud Image --- # Fetch latest UniFi OS Server version and download URL
msg_info "Downloading Debian 13 Cloud Image" msg_info "Fetching latest UniFi OS Server version"
URL="https://cloud.debian.org/images/cloud/trixie/latest/debian-13-nocloud-amd64.qcow2"
# 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_DIR="/var/lib/vz/template/cache"
CACHE_FILE="$CACHE_DIR/$(basename "$URL")" 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 if [[ ! -s "$CACHE_FILE" ]]; then
curl -f#SL -o "$CACHE_FILE" "$URL" curl -f#SL -o "$CACHE_FILE" "$URL"
msg_ok "Downloaded ${CL}${BL}$(basename "$CACHE_FILE")${CL}" msg_ok "Downloaded ${CL}${BL}$(basename "$CACHE_FILE")${CL}"
else else
msg_ok "Using cached image ${CL}${BL}$(basename "$CACHE_FILE")${CL}" msg_ok "Using cached image ${CL}${BL}$(basename "$CACHE_FILE")${CL}"
fi fi
FILE="debian-13-nocloud-amd64.qcow2" FILE="$(basename "$CACHE_FILE")"
msg_ok "Downloaded Debian Cloud Image" msg_ok "Downloaded ${OS_DISPLAY} Cloud Image"
# --- Inject UniFi Installer --- # --- Inject UniFi Installer ---
if ! command -v virt-customize &>/dev/null; then if ! command -v virt-customize &>/dev/null; then
@ -492,6 +621,15 @@ if ! command -v virt-customize &>/dev/null; then
fi fi
msg_info "Injecting UniFi OS Installer into Cloud Image" msg_info "Injecting UniFi OS Installer into Cloud Image"
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" \ virt-customize -q -a "$FILE" \
--run-command "echo 'nameserver 1.1.1.1' > /etc/resolv.conf" \ --run-command "echo 'nameserver 1.1.1.1' > /etc/resolv.conf" \
--install qemu-guest-agent,ca-certificates,curl,lsb-release,podman \ --install qemu-guest-agent,ca-certificates,curl,lsb-release,podman \
@ -499,11 +637,12 @@ virt-customize -q -a "$FILE" \
--run-command 'mkdir -p /etc/systemd/system/getty@tty1.service.d' \ --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'" \ --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 >/dev/null
fi
msg_ok "UniFi OS Installer integrated" msg_ok "UniFi OS Installer integrated"
msg_info "Creating UniFi OS VM" msg_info "Creating UniFi OS VM"
qm create "$VMID" -agent 1 $MACHINE -tablet 0 -localtime 1 -bios ovmf \ qm create "$VMID" -agent 1${MACHINE} -tablet 0 -localtime 1 -bios ovmf \
$CPU_TYPE -cores "$CORE_COUNT" -memory "$RAM_SIZE" \ ${CPU_TYPE} -cores "$CORE_COUNT" -memory "$RAM_SIZE" \
-name "$HN" -tags community-script \ -name "$HN" -tags community-script \
-net0 virtio,bridge="$BRG",macaddr="$MAC""$VLAN""$MTU" \ -net0 virtio,bridge="$BRG",macaddr="$MAC""$VLAN""$MTU" \
-onboot 1 -ostype l26 -scsihw virtio-scsi-pci -onboot 1 -ostype l26 -scsihw virtio-scsi-pci
@ -518,11 +657,18 @@ fi
qm set "$VMID" \ qm set "$VMID" \
-efidisk0 "${STORAGE}:0${FORMAT},size=4M" \ -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 -boot order=scsi0 -serial0 socket >/dev/null
qm resize "$VMID" scsi0 "$DISK_SIZE" >/dev/null qm resize "$VMID" scsi0 "$DISK_SIZE" >/dev/null
qm set "$VMID" --agent enabled=1 >/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=$( DESCRIPTION=$(
cat <<EOF cat <<EOF
<div align='center'> <div align='center'>
@ -555,11 +701,13 @@ EOF
) )
qm set "$VMID" -description "$DESCRIPTION" >/dev/null 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 if [ "$START_VM" == "yes" ]; then
msg_info "Starting Unifi OS VM" msg_info "Starting UniFi OS VM"
qm start $VMID qm start $VMID
msg_ok "Started Unifi OS VM" msg_ok "Started UniFi OS VM"
fi fi
post_update_to_api "done" "none" post_update_to_api "done" "none"
msg_ok "Completed Successfully!\n" msg_ok "Completed Successfully!\n"