369 lines
12 KiB
Bash
369 lines
12 KiB
Bash
#!/bin/bash
|
||
### ВКЛЮЧИТЬ qm start !!!!!!!!!!!
|
||
### МЫСЛИ ПО ФИЧАМ
|
||
# Добавить переезд на ноду, выбор ноды. Статистика загрузки нод - в хелп.
|
||
# ИЗУЧИТЬ!!!! /usr/share/pve-docs/examples/guest-example-hookscript.pl
|
||
|
||
storage="syno-tigra"
|
||
path="/mnt/pve/${storage}/snippets/"
|
||
node=$(hostname | rev | cut -c4)
|
||
if [[ $node -eq "l" ]]; then node="lab"; fi
|
||
|
||
###DEBUG
|
||
echo "DEBUG NODE=$node"
|
||
exit 110
|
||
### END DEBUG
|
||
|
||
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")
|
||
qm stop $vmid
|
||
echo "VM $vmid is stopping."
|
||
done
|
||
for line in (cat hosts.tmp)
|
||
do
|
||
ip=$(echo -n "$line" | cut -d ';')
|
||
vmid=$(get_vmid "$ip")
|
||
snippet="${path}${vmid}_${template}"
|
||
status=$(qm status $vmid | cut -d ' ' -f 2)
|
||
until [[ $status -eq "stopped" ]]
|
||
do
|
||
echo "Waiting for VM $vmid become stopped..."
|
||
sleep 1
|
||
done
|
||
ha-manager remove vm:${vmid} &>/dev/null &&
|
||
echo "VM $vmid removed from HA"
|
||
qm destroy $vmid --destroy-unreferenced-disks --purge &&
|
||
echo "VM $vmid successfully removed"
|
||
rm "$snippet" &&
|
||
echo "Snippet $snippet removed"
|
||
done
|
||
}
|
||
|
||
# Обрабатываем опции
|
||
while getopts "a:f:hk:u:p:d: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
|