#!/bin/bash ### ВКЛЮЧИТЬ qm start !!!!!!!!!!! ### МЫСЛИ ПО ФИЧАМ # Добавить переезд на ноду, выбор ноды. Статистика загрузки нод - в хелп. # ИЗУЧИТЬ!!!! /usr/share/pve-docs/examples/guest-example-hookscript.pl storage="syno-tigra" path="/mnt/pve/${storage}/snippets/" node=3 size=50 remove=false template="user.yaml" harule="" pubkey="" username="" password="" tag="" file="" show_help () { echo echo 'Usage:' echo 'vmcreate [-h][-a rule_name][-k pub_keyfile][-u][-p][-d 50][-t][-f filename]' echo 'Arguments:' echo '-h - show this help.' echo '-a - add hosts to HA affinity rules with name 'rule_name'.' echo "-k - embed custom public key or create new if 'pub_keyfile' does not exist." echo "-u - specify user instead of default 'root'." echo "-p - specify password instead of default." echo "-d - add custom disk space (in gibibytes, integer). Default is 50." echo "-n - specify target node NUMBER to migrate VM to after creating. Default is '3'" echo "-t - add additional proxmox tag. Default is only pve node number." echo "-f - get IP addresses and Hostnames from 'flilename'." echo "-s - specify custom snippet template. Default is 'user.yaml'" echo "-R - REMOVE specified VMs, snippets and HA rule (if specified). Be careful! It's a production cluster!" echo echo "If file not specified, script will use arguments as a list of IP addresses." echo "In this case Hostname will be inherited from 2 last IP octets. Example for 10.10.35.20: 'vm035020'." echo "IP address should be 10.10.XXX.YYY" echo } #Функция создания VMID, аргумент - IPv4 адрес. get_vmid () { local vlan=$(echo -n $1 | cut -d '.' -f 3) local oct4=$(echo -n $1 | cut -d '.' -f 4) local vmid="" if [[ $vlan -eq 0 ]]; then vmid=100 elif [[ $vlan -eq 1 ]]; then vmid=101 else vmid=$(printf "%02d\n" "$vlan") fi vmid+=$(printf "%03d\n" "$oct4") echo -n $vmid } # Функция для проверки IP адреса по шаблону 10.10.*.*, написана гуглом ipcheck () { local ip=$1 local stat=1 #oldIFS=$IFS # Check if the IP matches the general IPv4 pattern if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then # Split the IP into octets IFS='.' read -r -a octets <<< "$ip" # Check if each octet is within the valid range (0-255) if [[ ${octets[0]} -eq 10 && ${octets[1]} -eq 10 && ${octets[2]} -le 252 && ${octets[3]} -le 252 && ${octets[3]} -ne 0 ]]; then stat=0 fi fi return $stat #IFS=$oldIFS } # Функция для проверки hostname на валидные символы (буквы, цифры, дефис) hostcheck () { local hostname=$1 local pattern="^[a-zA-Z0-9-]+$" if [[ "$hostname" =~ $pattern ]]; then return 0 else return 1 fi } keypair_generate () { ssh-keygen -t rsa -N "" -f $privkey pubkey="$privkey".pub echo "Keypair generated." } mksnippet () { snippet="${path}${vmid}_${template}" cp ./user.yaml "$snippet" sed -i "s/HOSTNAME/$hostname/g" "$snippet" if [ $username ]; then sed -i "s|user: root|user: ${username}\nsudo: ALL=(ALL) NOPASSWD:ALL|" "$snippet" || echo "sedDEBUG 1" fi if [ $password ]; then phash=$(echo $password | mkpasswd -m sha-512 -s) echo "PHASH: $phash" sed -i "s|.*password.*|password: ${phash}|" "$snippet" || echo "sedDEBUG 2" fi if [ $pubkey ]; then sshkey=$(cat "$pubkey") sed -i "s|.*ssh-rsa.*| - ${sshkey}|" "$snippet" || echo "sedDEBUG 3" fi echo "Snippet $snippet created" } REMOVE () { if [ $harule ]; then ha-manager rules remove $harule; fi for line in (cat hosts.tmp) do ip=$(echo -n "$line" | cut -d ';') vmid=$(get_vmid "$ip") snippet="${path}${vmid}_${template}" qm stop $vmid if [ $harule ]; then ha-manager remove vm:${vmid}; fi qm destroy $vmid --destroy-unreferenced-disks --purge done } # Обрабатываем опции while getopts "a:f:hk:u:p:d:n:t:s:R" opt; do case $opt in a) harule=${OPTARG};; f) file=${OPTARG};; h) show_help; exit 0;; k) pubkey=${OPTARG};; u) username=${OPTARG};; p) password=${OPTARG};; d) size=${OPTARG};; n) node=${OPTARG};; t) tag=${OPTARG};; s) template=${OPTARG};; R) remove=true;; \?) echo "Invalid option."; show_help; exit 1;; esac done if [ $tag ]; then tag=",${tag}"; fi if [ ! -f $template ]; then echo "Specified snippet template does not exist. Aborting."; exit 10; fi if [ $harule ]; then hostcheck "$harule" if [ $? -ne 0 ]; then echo "HArule should contain only letters, numbers and dashes. Aborting."; exit 1; fi fi if [[ $node -lt 1 || $node -gt 4 ]]; then echo "Node number is not in [1..4]. Please specify correct node number. Aborting" fi # DEBUG Print specified options if [ $harule ]; then echo "harule: $harule"; fi if [ $file ]; then echo "file: $file"; fi if [ $pubkey ]; then echo "pubkey: $pubkey"; fi if [ $username ]; then echo "username: $username"; fi if [ $password ]; then echo "password: $password"; fi if [ $size ]; then echo "size: $size"; fi if [ $tag ]; then echo "tag: $tag"; fi # END DEBUG # Удаляем обработанные опции, оставляя только аргументы скрипта shift "$((OPTIND - 1))" echo "DEBUG arguments amount: $#" ### Проверка допустимости опций if ! [[ $size -ge 10 && $size -le 500 ]]; then echo "Disk size increment shoud be in range of 10..500. Aborting."; exit 2; fi # Создаём hosts.tmp из аргументов/файла, попутно проверяя данные # Если заданы И аргументы, И файл - сразу нахуй if [[ $# -ne 0 && $file ]]; then echo "Please use EITHER file OR arguments. Use '-h' flag for help" exit 3 else # Если файл задан if [ $file ]; then # Но не существует, то нахуй if [ ! -f "$file" ];then echo "File $file does not exist. Aborting." exit 4 fi fi # Если заданы аргументы if [ $# -ne 0 ]; then touch hosts.tmp echo -n "" > hosts.tmp for arg in "$@"; do ipcheck "$arg" if [ $? -eq 0 ]; then echo -n "$arg;" >> ./hosts.tmp echo -n "vm" >> ./hosts.tmp get_vmid "$arg" >> hosts.tmp echo >> hosts.tmp else echo "Argument $arg is not a valid IPv4 address (10.10.*.*). Aborting." rm hosts.tmp exit 5 fi done # А если нет, то файл. else touch hosts.tmp echo -n "" > hosts.tmp for line in $(cat "$file"); do ip=$(echo -n $line | cut -d ';' -f 1) hostname=$(echo -n $line | cut -d ';' -f 2) ipcheck "$ip" if [[ $? -eq 0 ]]; then hostcheck "$hostname" if [[ $? -eq 0 ]]; then echo $line >> hosts.tmp else echo "Hostname $hostname is not valid. Should contain only letters, numbers and dash. Aborting." rm hosts.tmp exit 5 fi else echo "$ip is not a valid IPv4 address (10.10.XXX.YYY). Aborting." rm hosts.tmp exit 5 fi done fi fi # Закончили с вводными данными #Самое время УДАЛИТЬ, если задан флаг if $remove; then input=n read -p "Script is ready to REMOVE $(cat hosts.tmp | wc -l) VMs. ARE YOU SURE???" input read -p "REALLY??? Enter to continue or Ctrl+C to abort" case $input in y) REMOVE; echo "Specified VMs REMOVED."; exit 0;; Y) REMOVE; echo "Specified VMs REMOVED."; exit 0;; *) echo "Aborting..."; exit 1;; esac fi # Проверяем ключ if [ $pubkey ]; then if [[ ! -f "$pubkey" ]]; then input=y echo "You specified public key but it does not exist. Generate new pair? Y/n" read input case $input in y) read -p "Enter name for your PRIVATE key. Public will be *.pub: " privkey && keypair_generate "$privkey";; Y) read -p "Enter name for your PRIVATE key. Public will be *.pub: " privkey && keypair_generate "$privkey";; n) echo "Public key is necessary to continue. Please use default, specify or generate new pair."; exit 6;; N) echo "Public key is necessary to continue. Please use default, specify or generate new pair."; exit 6;; *) echo "Use '-h' flag for help"; exit 6;; esac fi else read -p "Public key not specified. Use default? Enter to continue or Ctrl+C to abort" fi if [[ (head -c 7 "$pubkey") -ne "ssh-rsa" ]]; then echo "$pubkey is not a valid public key. Make sure you specified PUBLIC key." exit 6 fi # Проверяем, что нет ВМ в списке и нет сниппетов for line in $(cat hosts.tmp) do ip=$(echo -n "$line" | cut -d ';' -f 1) vmid=$(get_vmid "$ip") snippet="${path}${vmid}_${template}" if qm status "$vmid" &>/dev/null; then echo "VM $vmid exists. Aborting" exit 7 elif [[ -f "$snippet" ]]; then echo "Snippet $snippet exists. Aborting" exit 7 fi done # Подготовительные операции закончены, приступаем к выполнению. read -p "Script is ready to create $(cat hosts.tmp | wc -l) VMs. Press Enter" for line in $(cat hosts.tmp) do ip=$(echo -n $line | cut -d ';' -f 1) vlan=$(echo -n $ip | cut -d '.' -f 3) if [[ $vlan -eq 0 || $vlan -eq 1 ]]; then mask=23 gw="10.10.0.1" vlan=100 else mask=24 gw="10.10.${vlan}.1" fi hostname=$(echo -n $line | cut -d ';' -f 2) vmid=$(get_vmid "$ip") mksnippet echo -n "Now cloning VM $vmid from a template....." qm clone 5000 $vmid --name $hostname --full > /dev/null if [ $? -eq 0 ]; then echo "OK" else echo "ERROR" exit 8 fi qm set $vmid --tags "${node}${tag}" qm resize $vmid scsi0 +"$size"G qm set $vmid --cicustom "user=${storage}:snippets/${vmid}_${template}" qm set $vmid --ipconfig0 ip="$ip"/"$mask",gw="$gw" sed -i "s|tag=35|tag=$vlan|" /etc/pve/qemu-server/${vmid}.conf || echo "sedDEBUG 4" # sed -i "s|vmbr0|vmbr1|" /etc/pve/qemu-server/${vmid}.conf qm cloudinit update $vmid echo "Snippet:" cat $snippet # # Migrate VM to target node # echo -n "Migrating VM $vmid to pve${node}....." # qm migrate $vmid pve${node} && # if [ $? -eq 0 ]; then # echo "OK" # else # echo "ERROR" # exit 9 # fi read -p "Press enter to start VM" qm start $vmid && if [ $? -eq 0 ]; then echo "VM ${vmid} started successfully" fi if [ $harule ]; then ha-manager add vm:$vmid --state started --max_relocate 2 && ha-manager rules add node-affinity $harule --resources vm:$vmid --nodes pve1,pve2,pve3 --strict 1 && echo "HA rule added" if [ $? -ne0 ]; then echo "Error creating HA rule"; fi fi done # Финальная часть rm hosts.tmp if [ $privkey ]; then echo echo "Generated private key: ./$privkey" echo "SAVE IT IMMEDIATELY!!!" fi