#!/bin/bash ### ВКЛЮЧИТЬ qm start !!!!!!!!!!! ### МЫСЛИ ПО ФИЧАМ # Сделать проверку, какие правила НА существуют, и вывести выбор /etc/pve/ha/rules.cfg # Сделать замену тега влан # Добавить переезд на ноду, выбор ноды. Статистика загрузки нод - в хелп. # Добавить тег прокса # ИЗУЧИТЬ!!!! /usr/share/pve-docs/examples/guest-example-hookscript.pl storage="syno-tigra" #path="/mnt/pve/$storage/snippets" path="./snippets/" #SEE BELOW!!! mkdir $path #REMOVE AFTER CORRECT PATH!!!!!!!! node=3 size=50 show_help(){ 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 "-t - add additional proxmox tag. Default is only pve node number." echo "-f - get IP addresses and Hostnames from 'flilename'." 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" } #Функция создания 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 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}_user.yaml" cp ./user.yaml "$snippet" sed -i "s/HOSTNAME/$hostname/g" "$snippet" if [[ -n $username ]]; then sed -i "s|user: root|user: ${username}\nsudo: ALL=(ALL) NOPASSWD:ALL|" "$snippet" fi if [[ -n $password ]]; then phash=$(cat $password | mkpasswd -m sha-256 -s) sed -i "s|.*password.*| - ${phash}|" "$snippet" fi if [[ -v $pubkey ]]; then sed -i "s|.*ssh-rsa.*| - (cat ${pubkey})|" "$snippet" fi echo "Snippet $snippet created" } # Обрабатываем опции while getopts "a:f:hk:u:p:d:t:" 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";; t) tag=,"$OPTARG";; *) echo "Invalid option. Use '-h' for help."; exit 1;; esac done # Удаляем обработанные опции, оставляя только аргументы скрипта shift "$((OPTIND - 1))" echo "DEBUG options amount: $#" ### Проверка допустимости опций if ! [[ $size -ge 10 && $size -le 500 ]]; then echo "Disk size increment shoud be in range of 10..500. Aborting."; exit 2; fi # Вывод переменных для дебага: #if [[ -v file ]]; then echo "File: $file"; fi #if [[ -v pubkey ]]; then echo "Public key: $pubkey"; fi #if [[ -v username ]]; then echo "User: $file"; fi #echo "END DEBUG 1"; exit 101 # Если нет аргументов, то пробуем файл # if [[ $# -eq 0 && ! -e "$file" ]]; then # echo "File $file does not exist. Aborting." # exit 1 # fi # Создаём hosts.tmp из аргументов/файла, попутно проверяя данные # Если заданы И аргументы, И файл - сразу нахуй if [[ $# -ne 0 && -v $file ]]; then echo "Please use EITHER file OR arguments. Use '-h' flag for help" exit 3 else # Если файл задан if [[ -v $file ]]; then # Но не существует, то нахуй if [[ ! -e "$file" ]];then echo "File $file does not exist. Aborting." exit 4 fi else # А если не задан, то дефолтный. Если при этом есть аргументы, то в след. цикле будут использоваться они. file="hosts" fi # Если заданы аргументы if [[ $# -ne 0 ]]; then echo "DEBUG using arguments" 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 echo "DEBUG using file: $file" touch hosts.tmp echo -n "" > hosts.tmp echo "DEBUG cat file" cat "$file" for line in $(cat "$file"); do echo "DEBUG line:$line" 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 [[ -v $pubkey ]]; then if ! [[ -e "$pubkey" ]]; then input=y echo "You sbecified 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: " privkey && keypair_generate "$privkey";; Y) read -p "Enter name for your PRIVATE key: " 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 # Проверяем, что нет ВМ в списке и нет сниппетов for line in $(cat hosts.tmp) do ip=$(echo -n "$line" | cut -d ',' -f 1) vmid=$(get_vmid "$ip") snippet= if qm status "$vmid"; then echo "VM $vmid exists. Aborting" exit 7 elif [[ -e "$snippet" ]]; then echo "Snippet $snippet exists. Aborting" exit 7 fi done ### DEBUG 1 echo "DEBUG cat hosts.tmp:" cat hosts.tmp; echo ### 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 ]]; 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 #### DEBUG 2 echo "IP: $ip" echo "hostname: $hostname" echo "VMID: $vmid" echo "VLAN: $vlan" echo "TAG: $tag" echo "$snippet:" cat "$snippet" exit 100 #### echo -n "Now cloning VM $vmnum 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/${vmnum}_user.yaml" qm set $vmid --ipconfig0 ip="$ip"/"$mask",gw="$gw" sed -i "s|tag=35|tag=$vlan|" /etc/pve/qemu-server/${vmid}.conf qm cloudinit update $vmid ### DEBUG echo "DEBUG: ${vmid}.conf" cat /etc/pve/qemu-server/${vmid}.conf exit 102 ### # qm start $vmid ВРЕМЕННО ОТКЛЮЧЕНО if [ -v 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" # ОБЯЗАТЕЛЬНО ПРОВЕРИТЬ КОМАНДУ!!! fi done # Финальная часть rm hosts.tmp if [ -v $privkey ]; then echo echo "Generated private key: ./$privkey" echo "SAVE IT IMMEDIATELY!!!" fi