From cd4468cae8c6b49a8806b518788c0b0020cfb6f9 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Sun, 23 Mar 2025 17:50:33 +0100 Subject: [PATCH] Add new script to create Synology VM --- json/cache.json | 36 + scripts/menus/create_vm_menu.sh | 50 ++ scripts/menus/main_menu.sh | 14 +- scripts/vm/synology.sh | 1104 +++++++++++++++++++++++++++++++ 4 files changed, 1198 insertions(+), 6 deletions(-) create mode 100644 scripts/menus/create_vm_menu.sh create mode 100644 scripts/vm/synology.sh diff --git a/json/cache.json b/json/cache.json index b2308f0..bb8a484 100644 --- a/json/cache.json +++ b/json/cache.json @@ -845,5 +845,41 @@ "de": "Netzwerk überprüfen", "it": "Verificare rete", "pt": "Verificar rede" + }, + "Next Steps:": { + "es": "Próximos pasos:", + "fr": "Étapes suivantes :", + "de": "Nächste Schritte:", + "it": "Prossimi passaggi:" + }, + "Start the VM": { + "es": "Iniciar la VM", + "fr": "Démarrer la machine virtuelle", + "de": "Die VM starten", + "it": "Avvia la VM" + }, + "Open the VM console and wait for the loader to boot": { + "es": "Abre la consola de la VM y espera a que cargue el cargador", + "fr": "Ouvrez la console de la VM et attendez que le chargeur démarre", + "de": "Öffnen Sie die VM-Konsole und warten Sie, bis der Loader startet", + "it": "Apri la console della VM e attendi l'avvio del caricatore" + }, + "In the loader interface, follow the instructions to select your Synology model": { + "es": "En la interfaz del cargador, sigue las instrucciones para seleccionar tu modelo de Synology DSM", + "fr": "Dans l'interface du chargeur, suivez les instructions pour sélectionner votre modèle Synology DSM", + "de": "In der Ladeoberfläche folgen Sie den Anweisungen, um Ihr Synology DSM-Modell auszuwählen", + "it": "Nell'interfaccia del caricatore, segui le istruzioni per selezionare il modello di Synology DSM" + }, + "Complete the DSM installation wizard": { + "es": "Completa el asistente de instalación de DSM", + "fr": "Complétez l'assistant d'installation de DSM", + "de": "Vervollständigen Sie den DSM-Installationsassistenten", + "it": "Completa la procedura guidata di installazione DSM" + }, + "Find your device using https://finds.synology.com": { + "es": "Encuentra tu dispositivo en https://finds.synology.com", + "fr": "Trouvez votre appareil en utilisant https://finds.synology.com", + "de": "Finden Sie Ihr Gerät unter https://finds.synology.com", + "it": "Trova il tuo dispositivo usando https://finds.synology.com" } } diff --git a/scripts/menus/create_vm_menu.sh b/scripts/menus/create_vm_menu.sh new file mode 100644 index 0000000..de07ea4 --- /dev/null +++ b/scripts/menus/create_vm_menu.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +# ========================================================== +# ProxMenu - VM Creation Menu +# ========================================================== +# Author : MacRimi +# Copyright : (c) 2024 MacRimi +# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE) +# Version : 1.0 +# Last Updated: 28/01/2025 +# ========================================================== + + +# Configuration ============================================ +REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main" +BASE_DIR="/usr/local/share/proxmenux" +UTILS_FILE="$BASE_DIR/utils.sh" +VENV_PATH="/opt/googletrans-env" + +if [[ -f "$UTILS_FILE" ]]; then + source "$UTILS_FILE" +fi +load_language +initialize_cache +# ========================================================== + +show_proxmenux_logo + +while true; do + OPTION=$(whiptail --title "$(translate "Create VM Menu")" --menu "$(translate "Select a VM template to create:")" 18 70 5 \ + "1" "$(translate "Synology DSM Virtual Machine")" \ + "2" "$(translate "Return to Main Menu")" 3>&1 1>&2 2>&3) + + case $OPTION in + 1) + msg_info2 "$(translate "Running script:") $(translate "Synology DSM VM Creator")..." + bash <(curl -s "$REPO_URL/scripts/vm/synology.sh") + if [ $? -ne 0 ]; then + msg_warn "$(translate "Operation cancelled or failed.")" + sleep 2 + fi + ;; + 2) + exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh") + ;; + *) + exec bash <(curl -s "$REPO_URL/scripts/menus/main_menu.sh") + ;; + esac +done \ No newline at end of file diff --git a/scripts/menus/main_menu.sh b/scripts/menus/main_menu.sh index f13784a..34174a6 100644 --- a/scripts/menus/main_menu.sh +++ b/scripts/menus/main_menu.sh @@ -28,15 +28,16 @@ initialize_cache show_menu() { - while true; do - OPTION=$(whiptail --title "$(translate "Main ProxMenux")" --menu "$(translate "Select an option:")" 20 70 8 \ +while true; do + OPTION=$(whiptail --title "$(translate "Main ProxMenux")" --menu "$(translate "Select an option:")" 22 70 9 \ "1" "$(translate "Settings post-install Proxmox")" \ "2" "$(translate "Hardware: GPUs and Coral-TPU")" \ "3" "$(translate "Hard Drives, Disk Images, and Storage")" \ "4" "$(translate "Essential Proxmox VE Helper-Scripts")" \ "5" "$(translate "Network")" \ - "6" "$(translate "Settings")" \ - "7" "$(translate "Exit")" 3>&1 1>&2 2>&3) + "6" "$(translate "Create VM from template or script")" \ + "7" "$(translate "Settings")" \ + "8" "$(translate "Exit")" 3>&1 1>&2 2>&3) @@ -46,8 +47,9 @@ show_menu() { 3) exec bash <(curl -s "$REPO_URL/scripts/menus/storage_menu.sh") ;; 4) exec bash <(curl -s "$REPO_URL/scripts/menus/menu_Helper_Scripts.sh") ;; 5) exec bash <(curl -s "$REPO_URL/scripts/repair_network.sh") ;; - 6) exec bash <(curl -s "$REPO_URL/scripts/menus/config_menu.sh") ;; - 7) clear; msg_ok "$(translate "Thank you for using ProxMenu. Goodbye!")"; exit 0 ;; + 6) exec bash <(curl -s "$REPO_URL/scripts/menus/create_vm_menu.sh") ;; + 7) exec bash <(curl -s "$REPO_URL/scripts/menus/config_menu.sh") ;; + 8) clear; msg_ok "$(translate "Thank you for using ProxMenu. Goodbye!")"; exit 0 ;; *) msg_warn "$(translate "Invalid option")"; sleep 2 ;; esac diff --git a/scripts/vm/synology.sh b/scripts/vm/synology.sh new file mode 100644 index 0000000..695ba3e --- /dev/null +++ b/scripts/vm/synology.sh @@ -0,0 +1,1104 @@ +#!/usr/bin/env bash + +# ========================================================== +# ProxMenuX - Synology DSM VM Creator Script +# ========================================================== +# Author : MacRimi +# Copyright : (c) 2024 MacRimi +# License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE) +# Version : 1.0 +# Last Updated: 13/03/2025 +# ========================================================== +# Description: +# This script automates the creation and configuration of a Synology DSM +# (DiskStation Manager) virtual machine (VM) in Proxmox VE. It simplifies the +# setup process by allowing both default and advanced configuration options. +# +# The script automates the complete VM creation process, including loader +# download, disk configuration, and VM boot setup. +# +# **Credits** +# This script is an original idea but incorporates ideas and elements from +# a similar script by user **tim104979** from the ProxmoxVE branch: +# (https://raw.githubusercontent.com/tim104979/ProxmoxVE/refs/heads/main/vm/synology-vm.sh) +# +# Copyright (c) Proxmox VE Helper-Scripts Community +# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE +# +# ========================================================== + + +# Configuration ============================================ +REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main" +BASE_DIR="/usr/local/share/proxmenux" +UTILS_FILE="$BASE_DIR/utils.sh" +VENV_PATH="/opt/googletrans-env" + +if [[ -f "$UTILS_FILE" ]]; then + source "$UTILS_FILE" +fi +load_language +initialize_cache +# ========================================================== + +GEN_MAC="02" +for i in {1..5}; do + BYTE=$(printf "%02X" $((RANDOM % 256))) + GEN_MAC="${GEN_MAC}:${BYTE}" +done + +NEXTID=$(pvesh get /cluster/nextid 2>/dev/null || echo "100") +NAME="Synology VM" +IMAGES_DIR="/var/lib/vz/template/iso" +ERROR_FLAG=false + + + + + +function exit_script() { + clear + if whiptail --backtitle "ProxMenuX" --title "$NAME" --yesno "$(translate "This will create a New $NAME. Proceed?")" 10 58; then + start_script + else + clear + exit + fi +} + + +# Define the header_info function at the beginning of the script + +function header_info() { + clear + show_proxmenux_logo + echo -e "${BL}╔═══════════════════════════════════════════════╗${CL}" + echo -e "${BL}║ ║${CL}" + echo -e "${BL}║${YWB} Synology VM Creator ${BL}║${CL}" + echo -e "${BL}║ ║${CL}" + echo -e "${BL}╚═══════════════════════════════════════════════╝${CL}" + echo -e +} +# ========================================================== + + + + + + +# ========================================================== +# start Script +# ========================================================== +function start_script() { + if (whiptail --backtitle "ProxMenuX" --title "SETTINGS" --yesno "$(translate "Use Default Settings?")" --no-button Advanced 10 58); then + header_info + echo -e "${DEF}Using Default Settings${CL}" + default_settings + else + header_info + echo -e "${CUS}Using Advanced Settings${CL}" + advanced_settings + fi +} +# ========================================================== + + + + +# ========================================================== +# Default Settings +# ========================================================== +function default_settings() { + VMID="$NEXTID" + FORMAT="" + MACHINE=" -machine q35" + BIOS_TYPE=" -bios ovmf" + DISK_CACHE="" + HN="Synology-DSM" + CPU_TYPE=" -cpu host" + CORE_COUNT="2" + RAM_SIZE="4096" + BRG="vmbr0" + MAC="$GEN_MAC" + VLAN="" + MTU="" + SERIAL_PORT="socket" + START_VM="no" + + echo -e "${DGN}Using Virtual Machine ID: ${BGN}${VMID}${CL}" + echo -e "${DGN}Using Machine Type: ${BGN}q35${CL}" + echo -e "${DGN}Using BIOS Type: ${BGN}OVMF (UEFI)${CL}" + echo -e "${DGN}Using Hostname: ${BGN}${HN}${CL}" + echo -e "${DGN}Using CPU Model: ${BGN}Host${CL}" + echo -e "${DGN}Allocated Cores: ${BGN}${CORE_COUNT}${CL}" + echo -e "${DGN}Allocated RAM: ${BGN}${RAM_SIZE}${CL}" + echo -e "${DGN}Using Bridge: ${BGN}${BRG}${CL}" + echo -e "${DGN}Using MAC Address: ${BGN}${MAC}${CL}" + echo -e "${DGN}Using VLAN: ${BGN}Default${CL}" + echo -e "${DGN}Using Interface MTU Size: ${BGN}Default${CL}" + echo -e "${DGN}Configuring Serial Port: ${BGN}${SERIAL_PORT}${CL}" + echo -e "${DGN}Start VM when completed: ${BGN}${START_VM}${CL}" + echo -e + echo -e "${DEF}Creating a $NAME using the above default settings${CL}" + + sleep 1 + select_disk_type +} +# ========================================================== + + + + + +# ========================================================== +# advanced Settings +# ========================================================== +function advanced_settings() { + # VM ID Selection + while true; do + if VMID=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set Virtual Machine ID")" 8 58 $NEXTID --title "VIRTUAL MACHINE ID" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z "$VMID" ]; then + VMID="$NEXTID" + fi + if pct status "$VMID" &>/dev/null || qm status "$VMID" &>/dev/null; then + echo -e "${CROSS}${RD} ID $VMID is already in use${CL}" + sleep 1 + continue + fi + echo -e "${DGN}Virtual Machine ID: ${BGN}$VMID${CL}" + break + else + exit_script + fi + done + + # Machine Type Selection + if MACH=$(whiptail --backtitle "ProxMenuX" --title "$(translate "MACHINE TYPE")" --radiolist --cancel-button Exit-Script "Choose Type" 10 58 2 \ + "q35" "Machine q35" ON \ + "i440fx" "Machine i440fx" OFF \ + 3>&1 1>&2 2>&3); then + if [ $MACH = q35 ]; then + echo -e "${DGN}Using Machine Type: ${BGN}$MACH${CL}" + FORMAT="" + MACHINE=" -machine q35" + else + echo -e "${DGN}Using Machine Type: ${BGN}$MACH${CL}" + FORMAT=",efitype=4m" + MACHINE="" + fi + else + exit_script + fi + + # BIOS Type Selection + if BIOS=$(whiptail --backtitle "ProxMenuX" --title "$(translate "BIOS TYPE")" --radiolist --cancel-button Exit-Script "Choose BIOS Type" 10 58 2 \ + "ovmf" "UEFI (OVMF)" ON \ + "seabios" "SeaBIOS (Legacy)" OFF \ + 3>&1 1>&2 2>&3); then + if [ "$BIOS" = "seabios" ]; then + echo -e "${DGN}Using BIOS Type: ${BGN}SeaBIOS${CL}" + BIOS_TYPE=" -bios seabios" + else + echo -e "${DGN}Using BIOS Type: ${BGN}OVMF (UEFI)${CL}" + BIOS_TYPE=" -bios ovmf" + fi + else + exit_script + fi + + # Hostname Selection + if VM_NAME=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set Hostname")" 8 58 Synology-DSM --title "HOSTNAME" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z $VM_NAME ]; then + HN="Synology-DSM" + echo -e "${DGN}Using Hostname: ${BGN}$HN${CL}" + else + HN=$(echo ${VM_NAME,,} | tr -d ' ') + echo -e "${DGN}Using Hostname: ${BGN}$HN${CL}" + fi + else + exit_script + fi + + # CPU Type Selection + if CPU_TYPE1=$(whiptail --backtitle "ProxMenuX" --title "$(translate "CPU MODEL")" --radiolist "Choose" --cancel-button Exit-Script 10 58 2 \ + "1" "Host" ON \ + "0" "KVM64" OFF \ + 3>&1 1>&2 2>&3); then + if [ $CPU_TYPE1 = "1" ]; then + echo -e "${DGN}Using CPU Model: ${BGN}Host${CL}" + CPU_TYPE=" -cpu host" + else + echo -e "${DGN}Using CPU Model: ${BGN}KVM64${CL}" + CPU_TYPE="" + fi + else + exit_script + fi + + # Core Count Selection + if CORE_COUNT=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Allocate CPU Cores")" 8 58 2 --title "CORE COUNT" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z $CORE_COUNT ]; then + CORE_COUNT="2" + echo -e "${DGN}Allocated Cores: ${BGN}$CORE_COUNT${CL}" + else + echo -e "${DGN}Allocated Cores: ${BGN}$CORE_COUNT${CL}" + fi + else + exit_script + fi + + # RAM Size Selection + if RAM_SIZE=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Allocate RAM in MiB")" 8 58 4096 --title "RAM" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z $RAM_SIZE ]; then + RAM_SIZE="4096" + echo -e "${DGN}Allocated RAM: ${BGN}$RAM_SIZE${CL}" + else + echo -e "${DGN}Allocated RAM: ${BGN}$RAM_SIZE${CL}" + fi + else + exit_script + fi + + # Bridge Selection + if BRG=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set a Bridge")" 8 58 vmbr0 --title "BRIDGE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z $BRG ]; then + BRG="vmbr0" + echo -e "${DGN}Using Bridge: ${BGN}$BRG${CL}" + else + echo -e "${DGN}Using Bridge: ${BGN}$BRG${CL}" + fi + else + exit_script + fi + + # MAC Address Selection + if MAC1=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set a MAC Address")" 8 58 $GEN_MAC --title "MAC ADDRESS" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z $MAC1 ]; then + MAC="$GEN_MAC" + echo -e "${DGN}Using MAC Address: ${BGN}$MAC${CL}" + else + MAC="$MAC1" + echo -e "${DGN}Using MAC Address: ${BGN}$MAC1${CL}" + fi + else + exit_script + fi + + # VLAN Selection + if VLAN1=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set a Vlan(leave blank for default)")" 8 58 --title "VLAN" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z $VLAN1 ]; then + VLAN1="Default" + VLAN="" + echo -e "${DGN}Using Vlan: ${BGN}$VLAN1${CL}" + else + VLAN=",tag=$VLAN1" + echo -e "${DGN}Using Vlan: ${BGN}$VLAN1${CL}" + fi + else + exit_script + fi + + # MTU Selection + if MTU1=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "Set Interface MTU Size (leave blank for default)")" 8 58 --title "MTU SIZE" --cancel-button Exit-Script 3>&1 1>&2 2>&3); then + if [ -z $MTU1 ]; then + MTU1="Default" + MTU="" + echo -e "${DGN}Using Interface MTU Size: ${BGN}$MTU1${CL}" + else + MTU=",mtu=$MTU1" + echo -e "${DGN}Using Interface MTU Size: ${BGN}$MTU1${CL}" + fi + else + exit_script + fi + + + + # Confirmation + if (whiptail --backtitle "ProxMenuX" --title "$(translate "ADVANCED SETTINGS COMPLETE")" --yesno "Ready to create a $NAME?" --no-button Do-Over 10 58); then + echo -e + echo -e "$(translate "${CUS}Creating a $NAME using the above advanced settings${CL}")" + sleep 1 + select_disk_type + else + header_info + sleep 1 + echo -e "$(translate "${CUS}Using Advanced Settings${CL}")" + advanced_settings + fi +} +# ========================================================== + + + + + +# ========================================================== +# Select Disk +# ========================================================== +function select_disk_type() { + + DISK_TYPE=$(whiptail --backtitle "ProxMenuX" --title "DISK TYPE" --menu "$(translate "Choose disk type:")" 12 58 2 \ + "virtual" "$(translate "Create virtual disk")" \ + "passthrough" "$(translate "Use physical disk passthrough")" \ + --ok-button "Select" --cancel-button "Cancel" 3>&1 1>&2 2>&3) + + EXIT_STATUS=$? + + if [[ $EXIT_STATUS -ne 0 ]]; then + clear + header_info + msg_error "Operation cancelled by user. Returning to start scrip..." + sleep 2 + if whiptail --backtitle "ProxMenuX" --title "$NAME" --yesno "$(translate "This will create a New $NAME. Proceed?")" 10 58; then + start_script + else + clear + exit + fi + fi + + if [[ "$DISK_TYPE" == "virtual" ]]; then + select_virtual_disk + else + select_passthrough_disk + fi +} + +# ========================================================== + + + + + +# ========================================================== +# Select Virtual Disks +# ========================================================== +function select_virtual_disk() { + + VIRTUAL_DISKS=() + + # Loop to add multiple disks + local add_more_disks=true + while $add_more_disks; do + + msg_info "Detecting available storage volumes..." + + # Get list of available storage + STORAGE_MENU=() + while read -r line; do + TAG=$(echo $line | awk '{print $1}') + TYPE=$(echo $line | awk '{print $2}') + FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format "%.2f" | awk '{printf( "%9sB", $6)}') + ITEM=$(printf "%-15s %-10s %-15s" "$TAG" "$TYPE" "$FREE") + STORAGE_MENU+=("$TAG" "$ITEM" "OFF") + done < <(pvesm status -content images | awk 'NR>1') + + # Check that storage is available + VALID=$(pvesm status -content images | awk 'NR>1') + if [ -z "$VALID" ]; then + msg_error "Unable to detect a valid storage location." + sleep 2 + select_disk_type + fi + + + # Select storage + if [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then + STORAGE=${STORAGE_MENU[0]} + msg_ok "Using ${CL}${BL}$STORAGE${CL} ${GN}for Storage Location." + else + + kill $SPINNER_PID > /dev/null + STORAGE=$(whiptail --backtitle "ProxMenuX" --title "$(translate "Select Storage Volume")" --radiolist \ + "$(translate "Choose the storage volume for the virtual disk:\n")" 20 78 10 \ + "${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3) + + if [ $? -ne 0 ] || [ -z "$STORAGE" ]; then + if [ ${#VIRTUAL_DISKS[@]} -eq 0 ]; then + msg_error "No storage selected. At least one disk is required." + select_disk_type + else + add_more_disks=false + continue + fi + fi + + + fi + + # Request disk size + DISK_SIZE=$(whiptail --backtitle "ProxMenuX" --inputbox "$(translate "System Disk Size (GB)")" 8 58 32 --title "VIRTUAL DISK" --cancel-button Cancel 3>&1 1>&2 2>&3) + + if [ $? -ne 0 ]; then + if [ ${#VIRTUAL_DISKS[@]} -eq 0 ]; then + msg_error "Disk size not specified. At least one disk is required." + sleep 2 + select_disk_type + + else + add_more_disks=false + continue + fi + fi + + if [ -z "$DISK_SIZE" ]; then + DISK_SIZE="32" + fi + + # Store the configuration in the disk list + VIRTUAL_DISKS+=("${STORAGE}:${DISK_SIZE}") + + + # Ask if you want to create another disk + if ! whiptail --backtitle "ProxMenuX" --title "$(translate "Add Another Disk")" \ + --yesno "$(translate "Do you want to add another virtual disk?")" 8 58; then + add_more_disks=false + fi + done + + # Show summary of the created disks + if [ ${#VIRTUAL_DISKS[@]} -gt 0 ]; then + + msg_ok "Virtual Disks Created:" + for i in "${!VIRTUAL_DISKS[@]}"; do + echo -e "${TAB}${BL}- Disk $((i+1)): ${VIRTUAL_DISKS[$i]}GB${CL}" + done + fi + + + export VIRTUAL_DISKS + + + select_loader +} + +# ========================================================== + + + + + + +# ========================================================== +# Select Physical Disks +# ========================================================== +function select_passthrough_disk() { + + + FREE_DISKS=() + + # Get the list of mounted disks + USED_DISKS=$(lsblk -n -o PKNAME,TYPE | grep 'lvm' | awk '{print "/dev/" $1}') + MOUNTED_DISKS=$(mount | grep -o '/dev/[a-z]*' | sort | uniq) + + while read -r DISK; do + + if ! echo "$USED_DISKS" | grep -q "$DISK" && ! echo "$MOUNTED_DISKS" | grep -q "$DISK"; then + + MODEL=$(lsblk -dn -o MODEL "$DISK" | xargs) + SIZE=$(lsblk -dn -o SIZE "$DISK" | xargs) + DESCRIPTION=$(printf "%-40s %10s" "$MODEL" "$SIZE") + FREE_DISKS+=("$DISK" "$DESCRIPTION" "OFF") + fi + done < <(lsblk -dn -e 7,11 -o PATH) + + + # Handle no available disks + if [ "${#FREE_DISKS[@]}" -eq 0 ]; then + whiptail --title "Error" --msgbox "$(translate "No disks available for this VM.")" 8 40 + select_disk_type + fi + + MAX_WIDTH=$(printf "%s\n" "${FREE_DISKS[@]}" | awk '{print length}' | sort -nr | head -n1) + TOTAL_WIDTH=$((MAX_WIDTH + 20)) + + # Set a reasonable minimum width + if [ $TOTAL_WIDTH -lt 70 ]; then + TOTAL_WIDTH=70 + fi + + # Display menu to select multiple free disks with dynamically calculated width + SELECTED_DISKS=$(whiptail --title "Select Disks" --checklist \ + "$(translate "Select the disks you want to use (use spacebar to select):")" 20 $TOTAL_WIDTH 10 \ + "${FREE_DISKS[@]}" 3>&1 1>&2 2>&3) + + # Check if at least one disk was selected + if [ -z "$SELECTED_DISKS" ]; then + msg_error "Disk not specified. At least one disk is required." + sleep 2 + select_disk_type + fi + + # Process selected disks + msg_ok "Disk passthrough selected:" + PASSTHROUGH_DISKS=() + for DISK in $(echo "$SELECTED_DISKS" | tr -d '"'); do + DISK_INFO=$(lsblk -ndo MODEL,SIZE "$DISK" | xargs) + echo -e "${TAB}${CL}${BL}- $DISK $DISK_INFO${GN}${CL}" + PASSTHROUGH_DISKS+=("$DISK") + done + + + select_loader +} +# ========================================================== + + + + + + +# ========================================================== +# Select Loader +# ========================================================== +function select_loader() { + # Ensure the images directory exists + if [ ! -d "$IMAGES_DIR" ]; then + msg_info "Creating images directory" + mkdir -p "$IMAGES_DIR" + chmod 755 "$IMAGES_DIR" + msg_ok "Images directory created: $IMAGES_DIR" + fi + + # Create the loader selection menu + LOADER_OPTION=$(whiptail --backtitle "ProxMenuX" --title "SELECT LOADER" --menu "$(translate "Choose a loader for Synology DSM:")" 15 70 4 \ + "1" "AuxXxilium Arc Loader" \ + "2" "RedPill Loader (RROrg - RR)" \ + "3" "TinyCore RedPill Loader (PeterSuh-Q3 M-shell)" \ + "4" "Custom Loader Image (from $IMAGES_DIR)" \ + 3>&1 1>&2 2>&3) + + if [ -z "$LOADER_OPTION" ]; then + exit_script + fi + + case $LOADER_OPTION in + 1) + LOADER_TYPE="arc" + LOADER_NAME="AuxXxilium Arc" + LOADER_URL="https://github.com/AuxXxilium/arc/" + echo -e "${DGN}${TAB}Selected Loader: ${BGN}$LOADER_NAME${CL}" + download_loader + ;; + 2) + LOADER_TYPE="redpill" + LOADER_NAME="RedPill RR" + LOADER_URL="https://github.com/RROrg/rr/" + echo -e "${DGN}${TAB}Selected Loader: ${BGN}$LOADER_NAME${CL}" + download_loader + ;; + 3) + LOADER_TYPE="tinycore" + LOADER_NAME="TinyCore RedPill M-shell" + LOADER_URL="https://github.com/PeterSuh-Q3/tinycore-redpill/" + echo -e "${DGN}${TAB}Selected Loader: ${BGN}$LOADER_NAME${CL}" + download_loader + ;; + 4) + LOADER_TYPE="custom" + LOADER_NAME="Custom Image" + LOADER_URL="https://xpenology.com/forum/" + echo -e "${DGN}${TAB}Selected Loader: ${BGN}$LOADER_NAME${CL}" + select_custom_image + ;; + esac +} + +function select_custom_image() { + # Check if there are any images in the directory + IMAGES=$(find "$IMAGES_DIR" -type f -name "*.img" -o -name "*.iso" -o -name "*.qcow2" -o -name "*.vmdk" | sort) + + if [ -z "$IMAGES" ]; then + whiptail --title "$(translate "No Images Found")" --msgbox "No compatible images found in $IMAGES_DIR\n\nSupported formats: .img, .iso, .qcow2, .vmdk\n\nPlease add some images and try again." 15 70 + select_loader + fi + + # Create an array of image options for whiptail + IMAGE_OPTIONS=() + + while read -r img; do + filename=$(basename "$img") + filesize=$(du -h "$img" | cut -f1) + IMAGE_OPTIONS+=("$img" "$filesize") + done <<< "$IMAGES" + + # Let the user select an image + LOADER_FILE=$(whiptail --backtitle "ProxMenuX" --title "SELECT CUSTOM IMAGE" --menu "$(translate "Choose a custom image:")" 20 70 10 "${IMAGE_OPTIONS[@]}" 3>&1 1>&2 2>&3) + + if [ -z "$LOADER_FILE" ]; then + msg_error "No custom image selected" + exit_script + fi + + echo -e "${DGN}${TAB}Using Custom Image: ${BGN}$(basename "$LOADER_FILE")${CL}" + FILE=$(basename "$LOADER_FILE") +} +# ========================================================== + + + + + + + +# ========================================================== +# Download Loader +# ========================================================== +function download_loader() { + + echo -e "${DGN}${TAB}Retrieving the URL for the ${BGN}$LOADER_NAME loader${CL}" + + case $LOADER_TYPE in + arc) + curl -s https://api.github.com/repos/AuxXxilium/arc/releases/latest \ + | grep "browser_download_url.*evo.img.zip" \ + | cut -d '"' -f 4 \ + | xargs wget -q --show-progress -O "$IMAGES_DIR/evo.img.zip" + + if [ -f "$IMAGES_DIR/evo.img.zip" ]; then + cd "$IMAGES_DIR" + unzip -q evo.img.zip + rm evo.img.zip + FILE="arc.img" + LOADER_FILE="$IMAGES_DIR/$FILE" + cd - > /dev/null + else + msg_error "Failed to download $LOADER_NAME loader" + sleep 1 + select_loader + fi + ;; + + redpill) + curl -s https://api.github.com/repos/RROrg/rr/releases/latest \ + | grep "browser_download_url.*\.img\.zip" \ + | cut -d '"' -f 4 \ + | xargs wget -q --show-progress -O "$IMAGES_DIR/rr.img.zip" + + if [ -f "$IMAGES_DIR/rr.img.zip" ]; then + cd "$IMAGES_DIR" + msg_info "Unzipping $LOADER_NAME loader. Please wait..." + unzip -qo rr.img.zip + msg_ok "Unzipped $LOADER_NAME loader successfully." + rm -f rr.img.zip + FILE="rr.img" + LOADER_FILE="$IMAGES_DIR/$FILE" + cd - > /dev/null + fi + + ;; + + tinycore) + curl -s https://api.github.com/repos/PeterSuh-Q3/tinycore-redpill/releases/latest \ + | grep "browser_download_url.*tinycore-redpill.v.*img.gz" \ + | cut -d '"' -f 4 \ + | xargs wget -q --show-progress -O "$IMAGES_DIR/tinycore.img.gz" + + if [ -f "$IMAGES_DIR/tinycore.img.gz" ]; then + cd "$IMAGES_DIR" + + msg_info "Unzipping $LOADER_NAME loader. Please wait..." + gunzip -f tinycore.img.gz 2> /dev/null + msg_ok "Unzipped $LOADER_NAME loader successfully." + FILE="tinycore.img" + LOADER_FILE="$IMAGES_DIR/$FILE" + cd - > /dev/null + + else + msg_error "Failed to download $LOADER_NAME loader" + sleep 1 + select_loader + + fi + ;; + esac + + msg_ok "Downloaded ${CL}${BL}${FILE}${CL} to ${IMAGES_DIR}" +} +# ======================================================= + + + + + +# ========================================================== +# Select UEFI Storage +# ========================================================== +function select_efi_storage() { + local vmid=$1 + local STORAGE="" + + STORAGE_MENU=() + + while read -r line; do + TAG=$(echo $line | awk '{print $1}') + TYPE=$(echo $line | awk '{printf "%-10s", $2}') + FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format "%.2f" | awk '{printf( "%9sB", $6)}') + + ITEM=" Type: $TYPE Free: $FREE" + OFFSET=2 + if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then + MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET)) + fi + + STORAGE_MENU+=("$TAG" "$ITEM" "OFF") + done < <(pvesm status -content images | awk 'NR>1') + + VALID=$(pvesm status -content images | awk 'NR>1') + if [ -z "$VALID" ]; then + msg_error "Unable to detect a valid storage location for EFI disk." + + elif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then + STORAGE=${STORAGE_MENU[0]} + + else + kill $SPINNER_PID > /dev/null + while [ -z "${STORAGE:+x}" ]; do + STORAGE=$(whiptail --backtitle "ProxMenuX" --title "EFI Disk Storage" --radiolist \ + "$(translate "Choose the storage volume for the EFI disk (4MB):\n\nUse Spacebar to select.")" \ + 16 $(($MSG_MAX_LENGTH + 23)) 6 \ + "${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3) || exit + + done + + fi + + echo "$STORAGE" +} +# ========================================================== + + + + + +# ========================================================== +# Select Storage Loader +# ========================================================== +function select_storage_volume() { + local vmid=$1 + local purpose=$2 + local STORAGE="" + + STORAGE_MENU=() + + while read -r line; do + TAG=$(echo $line | awk '{print $1}') + TYPE=$(echo $line | awk '{printf "%-10s", $2}') + FREE=$(echo $line | numfmt --field 4-6 --from-unit=K --to=iec --format "%.2f" | awk '{printf( "%9sB", $6)}') + + ITEM=" Type: $TYPE Free: $FREE" + OFFSET=2 + if [[ $((${#ITEM} + $OFFSET)) -gt ${MSG_MAX_LENGTH:-} ]]; then + MSG_MAX_LENGTH=$((${#ITEM} + $OFFSET)) + fi + + STORAGE_MENU+=("$TAG" "$ITEM" "OFF") + done < <(pvesm status -content images | awk 'NR>1') + + VALID=$(pvesm status -content images | awk 'NR>1') + if [ -z "$VALID" ]; then + msg_error "Unable to detect a valid storage location." + exit 1 + elif [ $((${#STORAGE_MENU[@]} / 3)) -eq 1 ]; then + STORAGE=${STORAGE_MENU[0]} + else + while [ -z "${STORAGE:+x}" ]; do + STORAGE=$(whiptail --backtitle "ProxMenuX" --title "Storage Pools" --radiolist \ + "$(translate "Choose the storage volume for $purpose:\n\nUse Spacebar to select.")" \ + 16 $(($MSG_MAX_LENGTH + 23)) 6 \ + "${STORAGE_MENU[@]}" 3>&1 1>&2 2>&3) || exit + done + fi + + echo "$STORAGE" +} + + + + + + +# ========================================================== +# Create VM +# ========================================================== +function create_vm() { + + # Create the VM + qm create $VMID -agent 1${MACHINE} -tablet 0 -localtime 1${BIOS_TYPE}${CPU_TYPE} -cores $CORE_COUNT -memory $RAM_SIZE \ + -name $HN -tags proxmenux -net0 virtio,bridge=$BRG,macaddr=$MAC$VLAN$MTU -onboot 1 -ostype l26 -scsihw virtio-scsi-pci \ + -serial0 socket + msg_ok "Create a $NAME" + + + +# Check if UEFI (OVMF) is being used =================== + if [[ "$BIOS_TYPE" == *"ovmf"* ]]; then + + msg_info "Configuring EFI disk" + EFI_STORAGE=$(select_efi_storage $VMID) + EFI_DISK_NAME="vm-${VMID}-disk-efivars" + + # Determine storage type and extension + STORAGE_TYPE=$(pvesm status -storage $EFI_STORAGE | awk 'NR>1 {print $2}') + case $STORAGE_TYPE in + nfs | dir) + EFI_DISK_EXT=".raw" + EFI_DISK_REF="$VMID/" + ;; + *) + EFI_DISK_EXT="" + EFI_DISK_REF="" + ;; + esac + + if pvesm alloc "$EFI_STORAGE" "$VMID" "$EFI_DISK_NAME$EFI_DISK_EXT" 4M >/dev/null 2>&1; then + if qm set "$VMID" -efidisk0 "$EFI_STORAGE:${EFI_DISK_REF}$EFI_DISK_NAME$EFI_DISK_EXT,pre-enrolled-keys=0" >/dev/null 2>&1; then + msg_ok "EFI disk created and configured on ${CL}${BL}$EFI_STORAGE${GN}${CL}" + else + msg_error "Failed to configure EFI disk" + ERROR_FLAG=true + fi + else + msg_error "Failed to create EFI disk" + ERROR_FLAG=true + fi + + fi +# ========================================================== + + +# Select storage volume for loader ======================= + + LOADER_STORAGE=$(select_storage_volume $VMID "loader disk") + + + #Run the command in the background and capture its PID + qm importdisk $VMID ${LOADER_FILE} $LOADER_STORAGE > /tmp/import_log_$VMID.txt 2>&1 & + import_pid=$! + + # Show a simple progress indicator + echo -n "Importing loader disk: " + while kill -0 $import_pid 2>/dev/null; do + echo -n "." + sleep 2.5 + done + + wait $import_pid + rm -f /tmp/import_log_$VMID.txt + + IMPORTED_DISK=$(qm config $VMID | grep -E 'unused[0-9]+' | tail -1 | cut -d: -f1) + + # If the disk was not imported correctly, show an error message but continue + if [ -z "$IMPORTED_DISK" ]; then + msg_error "Loader import failed. No disk detected." + ERROR_FLAG=true + else + msg_ok "Loader imported successfully to ${CL}${BL}$LOADER_STORAGE${GN}${CL}" + fi + + # Configure the loader disk as scsi0 + DISK_NAME="vm-${VMID}-disk-0" + + result=$(qm set "$VMID" -ide0 "${LOADER_STORAGE}:${DISK_NAME}" 2>&1 > /dev/null) + if [[ $? -eq 0 ]]; then + msg_ok "Configured loader disk as ide0" + else + ERROR_FLAG=true + fi + result=$(qm set "$VMID" -boot order=ide0 2>&1) + if [[ $? -eq 0 ]]; then + msg_ok "Loader configured as boot device." + else + ERROR_FLAG=true + fi + +# ========================================================== + +if [ "$DISK_TYPE" = "virtual" ]; then + if [ ${#VIRTUAL_DISKS[@]} -eq 0 ]; then + msg_error "No virtual disks configured." + exit_script + fi + + DISK_INFO="" + CONSOLE_DISK_INFO="" + + for i in "${!VIRTUAL_DISKS[@]}"; do + IFS=':' read -r STORAGE SIZE <<< "${VIRTUAL_DISKS[$i]}" + + STORAGE_TYPE=$(pvesm status -storage $STORAGE | awk 'NR>1 {print $2}') + case $STORAGE_TYPE in + nfs | dir) + DISK_EXT=".raw" + DISK_REF="$VMID/" + ;; + *) + DISK_EXT="" + DISK_REF="" + ;; + esac + + + DISK_NUM=$((i+1)) + DISK_NAME="vm-${VMID}-disk-${DISK_NUM}${DISK_EXT}" + + + # Create virtual disk + msg_info "Creating virtual disk..." + if ! pvesm alloc "$STORAGE" "$VMID" "$DISK_NAME" "$SIZE"G >/dev/null 2>&1; then + msg_error "Failed to allocate virtual disk $DISK_NUM" + + fi + + # Configure disk in the VM (sata0, sata1, etc.) + SATA_ID="sata$i" + if ! qm set "$VMID" -$SATA_ID "$STORAGE:${DISK_REF}$DISK_NAME" >/dev/null 2>&1; then + msg_error "Failed to configure virtual disk as $SATA_ID" + + fi + msg_ok "Configured virtual disk as $SATA_ID, ${SIZE}GB on ${CL}${BL}$STORAGE${CL} ${GN}" + + # Add information to the description + DISK_INFO="${DISK_INFO}

Virtual Disk $DISK_NUM: ${SIZE}GB on ${STORAGE}

" + CONSOLE_DISK_INFO="${CONSOLE_DISK_INFO}- Virtual Disk $DISK_NUM: ${SIZE}GB on ${STORAGE} ($SATA_ID)\n" + done + + + + # HTML description +HTML_DESC="
+ + + + + +
+ProxMenux Logo + +

Synology DSM VM

+

Created with ProxMenuX

+

Loader: $LOADER_NAME

+
+ +

+Docs +Code +Loader +Ko-fi +

+ +
+${DISK_INFO} +
+
" + + msg_info "Setting VM description" + if ! qm set "$VMID" -description "$HTML_DESC" >/dev/null 2>&1; then + msg_error "Failed to set VM description" + exit_script + fi + msg_ok "Configured VM description" + + +else + + + # Configure multiple passthrough disks + DISK_INFO="" + CONSOLE_DISK_INFO="" + + for i in "${!PASSTHROUGH_DISKS[@]}"; do + DISK="${PASSTHROUGH_DISKS[$i]}" + DISK_MODEL=$(lsblk -ndo MODEL "$DISK" | xargs) + DISK_SIZE=$(lsblk -ndo SIZE "$DISK" | xargs) + DISK_ID="sata$i" + + + result=$(qm set $VMID -${DISK_ID} ${DISK} 2>&1) + if [[ $? -eq 0 ]]; then + msg_ok "Configured disk ${CL}${BL}($DISK_MODEL $DISK_SIZE)${CL}${GN} as $DISK_ID" + fi + # Add information to the description + DISK_INFO="${DISK_INFO}

Passthrough Disk $((i+1)): $DISK ($DISK_MODEL $DISK_SIZE)

" + CONSOLE_DISK_INFO="${CONSOLE_DISK_INFO}- Passthrough Disk $((i+1)): $DISK ($DISK_MODEL $DISK_SIZE) (${DISK_ID})\n" + done + + + # HTML description +HTML_DESC="
+ + + + + +
+ProxMenux Logo + +

Synology DSM VM

+

Created with ProxMenuX

+

Loader: $LOADER_NAME

+
+ +

+Docs +Code +Loader +Ko-fi +

+ +
+${DISK_INFO} +
+
" + + + result=$(qm set $VMID -description "$HTML_DESC" 2>&1) + if [[ $? -eq 0 ]]; then + msg_ok "Configured VM description" + fi + + +fi + + +if [ "$ERROR_FLAG" = true ]; then + msg_error "VM created with errors. Check configuration." +else +msg_success "$(translate "Completed Successfully!")" + +echo -e "${TAB}${GN}$(translate "Next Steps:")${CL}" +echo -e "${TAB}1. $(translate "Start the VM")" +echo -e "${TAB}2. $(translate "Open the VM console and wait for the loader to boot")" +echo -e "${TAB}3. $(translate "In the loader interface, follow the instructions to select your Synology model")" +echo -e "${TAB}4. $(translate "Complete the DSM installation wizard")" +echo -e "${TAB}5. $(translate "Find your device using https://finds.synology.com")" +echo -e + +fi + +} + +# ========================================================== + + + +# ========================================================== +# Main execution +# ========================================================== +header_info +echo -e "\n Loading..." +sleep 1 + +# Start script +if whiptail --backtitle "ProxMenuX" --title "$NAME" --yesno "$(translate "This will create a New $NAME. Proceed?")" 10 58; then + start_script +else + clear + exit +fi + +# Create VM +create_vm + +# ========================================================== \ No newline at end of file