Files
vmcreate/vmcreate.sh
2025-10-29 10:39:51 +00:00

346 lines
12 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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