327 lines
11 KiB
Bash
327 lines
11 KiB
Bash
#!/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
|
||
|
||
# Далее объявляю переменные, в цикле getopts это не получается
|
||
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
|
||
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
|
||
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 [ $username ]; then
|
||
sed -i "s|user: root|user: ${username}\nsudo: ALL=(ALL) NOPASSWD:ALL|" "$snippet"
|
||
fi
|
||
if [ $password ]; then
|
||
phash=$(cat $password | mkpasswd -m sha-256 -s)
|
||
sed -i "s|.*password.*|password: ${phash}|" "$snippet"
|
||
fi
|
||
if [ $pubkey ]; then
|
||
sed -i "s|.*ssh-rsa.*| - $(cat ${pubkey})|" "$snippet"
|
||
fi
|
||
echo "Snippet $snippet created"
|
||
}
|
||
|
||
# Обрабатываем опции
|
||
while getopts "a:f:hk:u:p:d:n: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};;
|
||
n) node=${OPTARG};;
|
||
t) tag=${OPTARG};;
|
||
\?) echo "Invalid option."; show_help; exit 1;;
|
||
esac
|
||
done
|
||
|
||
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 && $use_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
|
||
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 [ $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: " 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" &>/dev/null; then
|
||
echo "VM $vmid exists. Aborting"
|
||
exit 7
|
||
elif [[ -f "$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
|
||
###
|
||
# # 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
|
||
# qm start $vmid ВРЕМЕННО ОТКЛЮЧЕНО
|
||
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"
|
||
# ОБЯЗАТЕЛЬНО ПРОВЕРИТЬ КОМАНДУ!!!
|
||
fi
|
||
done
|
||
|
||
# Финальная часть
|
||
rm hosts.tmp
|
||
if [ $privkey ]; then
|
||
echo
|
||
echo "Generated private key: ./$privkey"
|
||
echo "SAVE IT IMMEDIATELY!!!"
|
||
fi
|