pve/scripts/buildvm_extra_ip.sh

327 lines
13 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
# from
# https://github.com/oneclickvirt/pve
# 2025.06.09
# 自动选择要绑定的IPV4地址 额外的IPV4地址需要与本机的IPV4地址在同一个子网内即前缀一致
# 此时开设出的虚拟机的网关为宿主机的IPV4的网关不需要强制约定MAC地址。
# 此时附加的IPV4地址是宿主机目前的IPV4地址顺位后面的地址
# 比如目前是 1.1.1.32 然后 1.1.1.33 已经有虚拟机了那么本脚本附加IP地址为 1.1.1.34
# ./buildvm_extra_ip.sh VMID 用户名 密码 CPU核数 内存 硬盘 系统 存储盘 是否附加IPV6(默认为N)
# ./buildvm_extra_ip.sh 152 test1 1234567 1 512 5 debian11 local N
cd /root >/dev/null 2>&1
init_params() {
vm_num="${1:-152}"
user="${2:-test}"
password="${3:-123456}"
core="${4:-1}"
memory="${5:-512}"
disk="${6:-5}"
system="${7:-ubuntu22}"
storage="${8:-local}"
independent_ipv6="${9:-N}"
independent_ipv6=$(echo "$independent_ipv6" | tr '[:upper:]' '[:lower:]')
rm -rf "vm$vm_num"
user_ip=""
user_ip_range=""
gateway=""
if [ ! -d "qcow" ]; then
mkdir qcow
fi
rm -rf "vm$vm_num"
}
check_cdn() {
local o_url=$1
local shuffled_cdn_urls=($(shuf -e "${cdn_urls[@]}")) # 打乱数组顺序
for cdn_url in "${shuffled_cdn_urls[@]}"; do
if curl -4 -sL -k "$cdn_url$o_url" --max-time 6 | grep -q "success" >/dev/null 2>&1; then
export cdn_success_url="$cdn_url"
return
fi
sleep 0.5
done
export cdn_success_url=""
}
check_cdn_file() {
check_cdn "https://raw.githubusercontent.com/spiritLHLS/ecs/main/back/test"
if [ -n "$cdn_success_url" ]; then
echo "CDN available, using CDN"
else
echo "No CDN available, no use CDN"
fi
}
download_with_retry() {
local url="$1"
local output="$2"
local max_attempts=5
local attempt=1
local delay=1
while [ $attempt -le $max_attempts ]; do
wget -q "$url" -O "$output" && return 0
echo "Download failed: $url, try $attempt, wait $delay seconds and retry..."
echo "下载失败:$url,尝试第 $attempt 次,等待 $delay 秒后重试..."
sleep $delay
attempt=$((attempt + 1))
delay=$((delay * 2))
[ $delay -gt 30 ] && delay=30
done
echo -e "\e[31mDownload failed: $url, maximum number of attempts exceeded ($max_attempts)\e[0m"
echo -e "\e[31m下载失败$url,超过最大尝试次数 ($max_attempts)\e[0m"
return 1
}
load_default_config() {
local config_url="${cdn_success_url}https://raw.githubusercontent.com/oneclickvirt/pve/main/scripts/default_vm_config.sh"
local config_file="default_vm_config.sh"
if download_with_retry "$config_url" "$config_file"; then
. "./$config_file"
else
echo -e "\e[31mUnable to load default configuration, script terminated.\e[0m"
echo -e "\e[31m无法加载默认配置脚本终止。\e[0m"
exit 1
fi
}
get_host_network_info() {
if ! command -v lshw >/dev/null 2>&1; then
apt-get install -y lshw
fi
if ! command -v ping >/dev/null 2>&1; then
apt-get install -y iputils-ping
apt-get install -y ping
fi
interface=$(lshw -C network | awk '/logical name:/{print $3}' | head -1)
user_main_ip_range=$(grep -A 1 "iface ${interface}" /etc/network/interfaces | grep "address" | awk '{print $2}' | head -n 1)
if [ -z "$user_main_ip_range" ]; then
user_main_ip_range=$(grep -A 1 "iface vmbr0" /etc/network/interfaces | grep "address" | awk '{print $2}' | head -n 1)
if [ -z "$user_main_ip_range" ]; then
_red "宿主机可用IP区间查询失败"
exit 1
fi
fi
user_main_ip=$(echo "$user_main_ip_range" | cut -d'/' -f1)
user_ip_range=$(echo "$user_main_ip_range" | cut -d'/' -f2)
ip_range=$((32 - user_ip_range))
range=$((2 ** ip_range - 3))
IFS='.' read -r -a octets <<<"$user_main_ip"
ip_list=()
for ((i = 0; i < $range; i++)); do
octet=$((i % 256))
if [ $octet -gt 254 ]; then
break
fi
ip="${octets[0]}.${octets[1]}.${octets[2]}.$((octets[3] + octet))"
ip_list+=("$ip")
done
_green "当前宿主机可用的外网IP列表长度为${range}"
for ip in "${ip_list[@]}"; do
if ! ping -c 1 "$ip" >/dev/null; then
user_ip="$ip"
break
fi
done
gateway=$(grep -E "iface $interface" -A 3 "/etc/network/interfaces" | grep "gateway" | awk '{print $2}' | head -n 1)
if [ -z "$gateway" ]; then
gateway=$(grep -E "iface vmbr0" -A 3 "/etc/network/interfaces" | grep "gateway" | awk '{print $2}' | head -n 1)
if [ -z "$gateway" ]; then
_red "宿主机网关查询失败"
exit 1
fi
fi
if [ -z "$user_ip" ]; then
_red "可使用的IP列表查询失败"
exit 1
fi
if [ -z "$user_ip_range" ]; then
_red "本虚拟机将要绑定的IP选择失败"
exit 1
fi
_green "当前虚拟机将绑定的IP为${user_ip}"
user_ip_prefix=$(echo "$user_ip" | awk -F '.' '{print $1"."$2"."$3}')
user_main_ip_prefix=$(echo "$user_main_ip" | awk -F '.' '{print $1"."$2"."$3}')
if [ "$user_ip_prefix" = "$user_main_ip_prefix" ]; then
_yellow "宿主机的IPV4前缀与将要开设的虚拟机的IPV4前缀相同。"
else
_blue "宿主机的IPV4前缀与将要开设的虚拟机的IPV4前缀不同请使用 需要手动指定IPV4地址的版本 的脚本"
exit 1
fi
}
create_vm() {
appended_file="/usr/local/bin/pve_appended_content.txt"
if [ "$independent_ipv6" == "n" ]; then
qm create $vm_num \
--agent 1 \
--scsihw virtio-scsi-single \
--serial0 socket \
--cores $core \
--sockets 1 \
--cpu $cpu_type \
--net0 virtio,bridge=vmbr0,firewall=0 \
--ostype l26 \
${kvm_flag}
else
if [ -s "$appended_file" ]; then
net1_bridge="vmbr1"
else
net1_bridge="vmbr2"
fi
qm create $vm_num \
--agent 1 \
--scsihw virtio-scsi-single \
--serial0 socket \
--cores $core \
--sockets 1 \
--cpu $cpu_type \
--net0 virtio,bridge=vmbr0,firewall=0 \
--net1 virtio,bridge="$net1_bridge",firewall=0 \
--ostype l26 \
${kvm_flag}
fi
if [ "$system_arch" = "x86" ] || [ "$system_arch" = "x86_64" ]; then
qm importdisk $vm_num /root/qcow/${system}.qcow2 ${storage}
else
qm set $vm_num --bios ovmf
qm importdisk $vm_num /root/qcow/${system}.${ext} ${storage}
fi
sleep 3
volid=$(pvesm list ${storage} | awk -v vmid="${vm_num}" '$5 == vmid && $1 ~ /\.raw$/ {print $1}' | tail -n 1)
if [ -z "$volid" ]; then
echo "No .raw file found for VM ID '${vm_num}' in storage '${storage}'. Searching for other formats..."
volid=$(pvesm list ${storage} | awk -v vmid="${vm_num}" '$5 == vmid {print $1}' | tail -n 1)
fi
if [ -z "$volid" ]; then
echo "Error: No file found for VM ID '${vm_num}' in storage '${storage}'"
exit 1
fi
file_path=$(pvesm path ${volid})
if [ $? -ne 0 ] || [ -z "$file_path" ]; then
echo "Error: Failed to resolve path for volume '${volid}'"
exit 1
fi
file_name=$(basename "$file_path")
echo "Found file: $file_name"
echo "Attempting to set SCSI hardware with virtio-scsi-pci for VM $vm_num..."
qm set $vm_num --scsihw virtio-scsi-pci --scsi0 ${storage}:${vm_num}/vm-${vm_num}-disk-0.raw
if [ $? -ne 0 ]; then
echo "Failed to set SCSI hardware with vm-${vm_num}-disk-0.raw. Trying alternative disk file..."
qm set $vm_num --scsihw virtio-scsi-pci --scsi0 ${storage}:${vm_num}/$file_name
if [ $? -ne 0 ]; then
echo "Failed to set SCSI hardware with $file_name for VM $vm_num. Trying fallback file..."
qm set $vm_num --scsihw virtio-scsi-pci --scsi0 ${storage}:$file_name
if [ $? -ne 0 ]; then
echo "All attempts failed. Exiting..."
exit 1
fi
fi
fi
}
configure_vm() {
qm set $vm_num --bootdisk scsi0
qm set $vm_num --boot order=scsi0
qm set $vm_num --memory $memory
if [[ "$system_arch" == "arm" ]]; then
qm set $vm_num --scsi1 ${storage}:cloudinit
else
qm set $vm_num --ide1 ${storage}:cloudinit
fi
if [ "$independent_ipv6" == "y" ]; then
if [ ! -z "$host_ipv6_address" ] && [ ! -z "$ipv6_prefixlen" ] && [ ! -z "$ipv6_gateway" ] && [ ! -z "$ipv6_address_without_last_segment" ]; then
qm set $vm_num --ipconfig0 ip=${user_ip}/${user_ip_range},gw=${gateway}
appended_file="/usr/local/bin/pve_appended_content.txt"
if [ -s "$appended_file" ]; then
# 使用 vmbr1 网桥和 NAT 映射
vm_internal_ipv6="2001:db8:1::${vm_num}"
qm set $vm_num --ipconfig1 ip6="${vm_internal_ipv6}/64",gw6="2001:db8:1::1"
host_external_ipv6=$(get_available_vmbr1_ipv6)
if [ -z "$host_external_ipv6" ]; then
echo -e "\e[31mNo available IPv6 address found for NAT mapping\e[0m"
echo -e "\e[31m没有可用的IPv6地址用于NAT映射\e[0m"
independent_ipv6_status="N"
else
setup_nat_mapping "$vm_internal_ipv6" "$host_external_ipv6"
vm_external_ipv6="$host_external_ipv6"
echo "VM configured with NAT mapping: $vm_internal_ipv6 -> $host_external_ipv6"
echo "虚拟机已配置NAT映射$vm_internal_ipv6 -> $host_external_ipv6"
independent_ipv6_status="Y"
fi
elif grep -q "vmbr2" /etc/network/interfaces; then
# 使用 vmbr2 网桥直接分配IPv6地址
qm set $vm_num --ipconfig1 ip6="${ipv6_address_without_last_segment}${vm_num}/128",gw6="${host_ipv6_address}"
vm_external_ipv6="${ipv6_address_without_last_segment}${vm_num}"
independent_ipv6_status="Y"
else
independent_ipv6_status="N"
fi
qm set $vm_num --nameserver "1.1.1.1 2606:4700:4700::1111" || qm set $vm_num --nameserver 1.1.1.1
qm set $vm_num --searchdomain local
else
independent_ipv6_status="N"
fi
else
independent_ipv6_status="N"
fi
if [ "$independent_ipv6_status" == "N" ]; then
qm set $vm_num --ipconfig0 ip=${user_ip}/${user_ip_range},gw=${gateway}
qm set $vm_num --nameserver 8.8.8.8
qm set $vm_num --searchdomain local
fi
qm set $vm_num --cipassword $password --ciuser $user
sleep 5
qm resize $vm_num scsi0 ${disk}G
if [ $? -ne 0 ]; then
if [[ $disk =~ ^[0-9]+G$ ]]; then
dnum=${disk::-1}
disk_m=$((dnum * 1024))
qm resize $vm_num scsi0 ${disk_m}M
fi
fi
qm start $vm_num
}
save_vm_info() {
if [ "$independent_ipv6_status" == "Y" ]; then
echo "$vm_num $user $password $core $memory $disk $system $storage $user_ip $vm_external_ipv6" >>"vm${vm_num}"
data=$(echo " VMID 用户名-username 密码-password CPU核数-CPU 内存-memory 硬盘-disk 系统-system 存储盘-storage 外网IPV4-ipv4 外网IPV6-ipv6")
else
echo "$vm_num $user $password $core $memory $disk $system $storage $user_ip" >>"vm${vm_num}"
data=$(echo " VMID 用户名-username 密码-password CPU核数-CPU 内存-memory 硬盘-disk 系统-system 存储盘-storage 外网IP地址-ipv4")
fi
values=$(cat "vm${vm_num}")
IFS=' ' read -ra data_array <<<"$data"
IFS=' ' read -ra values_array <<<"$values"
length=${#data_array[@]}
for ((i = 0; i < $length; i++)); do
echo "${data_array[$i]} ${values_array[$i]}"
echo ""
done >"/tmp/temp${vm_num}.txt"
sed -i 's/^/# /' "/tmp/temp${vm_num}.txt"
cat "/etc/pve/qemu-server/${vm_num}.conf" >>"/tmp/temp${vm_num}.txt"
cp "/tmp/temp${vm_num}.txt" "/etc/pve/qemu-server/${vm_num}.conf"
rm -rf "/tmp/temp${vm_num}.txt"
cat "vm${vm_num}"
}
main() {
cdn_urls=("https://cdn0.spiritlhl.top/" "http://cdn1.spiritlhl.net/" "http://cdn2.spiritlhl.net/" "http://cdn3.spiritlhl.net/" "http://cdn4.spiritlhl.net/")
check_cdn_file
load_default_config || exit 1
setup_locale
get_system_arch || exit 1
check_kvm_support
init_params "$@"
validate_vm_num || exit 1
get_host_network_info
create_vm
configure_vm
save_vm_info
}
main "$@"
rm -rf default_vm_config.sh