mirror of
https://github.com/community-scripts/ProxmoxVE.git
synced 2026-10-10 21:02:09 +02:00
598 lines
20 KiB
Bash
598 lines
20 KiB
Bash
#!/usr/bin/env bash
|
|
# Copyright (c) 2021-2026 community-scripts ORG
|
|
# Author: michelroegl-brunner | MickLesk (CanbiZ)
|
|
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
|
|
|
COMMUNITY_SCRIPTS_URL="${COMMUNITY_SCRIPTS_URL:-https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main}"
|
|
source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/pve/vm-core.func")
|
|
load_functions
|
|
|
|
#API VARIABLES
|
|
RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)"
|
|
METHOD=""
|
|
APP="OPNsense"
|
|
APP_TYPE="vm"
|
|
NSAPP="opnsense-vm"
|
|
var_os="opnsense"
|
|
var_version="26.7"
|
|
FREEBSD_MAJOR="15"
|
|
#
|
|
GEN_MAC=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\(..\)/\1:/g; s/.$//')
|
|
GEN_MAC_LAN=02:$(openssl rand -hex 5 | awk '{print toupper($0)}' | sed 's/\(..\)/\1:/g; s/.$//')
|
|
|
|
HA=$(echo "\033[1;34m")
|
|
THIN="discard=on,ssd=1,"
|
|
|
|
header_info
|
|
echo -e "Loading..."
|
|
set -Eeo pipefail
|
|
shopt -s inherit_errexit
|
|
trap 'error_handler $LINENO "$BASH_COMMAND"' ERR
|
|
trap cleanup EXIT
|
|
trap 'post_update_to_api "failed" "130"' SIGINT
|
|
trap 'post_update_to_api "failed" "143"' SIGTERM
|
|
trap 'post_update_to_api "failed" "129"; exit 129' SIGHUP
|
|
|
|
vm_require_arch amd64
|
|
|
|
function check_disk_space() {
|
|
local path="$1"
|
|
local required_gb="$2"
|
|
local available_kb=$(df -k "$path" | awk 'NR==2 {print $4}')
|
|
local available_gb=$((available_kb / 1024 / 1024))
|
|
if [ $available_gb -lt $required_gb ]; then
|
|
return 1
|
|
fi
|
|
return 0
|
|
}
|
|
|
|
TEMP_DIR=$(mktemp -d)
|
|
if ! check_disk_space "$TEMP_DIR" 20 && [ -d "/var/tmp" ] && check_disk_space "/var/tmp" 20; then
|
|
rm -rf "$TEMP_DIR"
|
|
TEMP_DIR=$(mktemp -d /var/tmp/opnsense-vm.XXXXXX)
|
|
fi
|
|
pushd "$TEMP_DIR" >/dev/null
|
|
|
|
vm_preflight
|
|
vm_require_tools xz
|
|
|
|
function send_line_to_vm() {
|
|
echo -e "${DGN}Sending line: ${YW}$1${CL}"
|
|
for ((i = 0; i < ${#1}; i++)); do
|
|
character=${1:i:1}
|
|
case $character in
|
|
" ") character="spc" ;;
|
|
"-") character="minus" ;;
|
|
"=") character="equal" ;;
|
|
",") character="comma" ;;
|
|
".") character="dot" ;;
|
|
"/") character="slash" ;;
|
|
"'") character="apostrophe" ;;
|
|
";") character="semicolon" ;;
|
|
'\') character="backslash" ;;
|
|
'`') character="grave_accent" ;;
|
|
"[") character="bracket_left" ;;
|
|
"]") character="bracket_right" ;;
|
|
"_") character="shift-minus" ;;
|
|
"+") character="shift-equal" ;;
|
|
"?") character="shift-slash" ;;
|
|
"<") character="shift-comma" ;;
|
|
">") character="shift-dot" ;;
|
|
'"') character="shift-apostrophe" ;;
|
|
":") character="shift-semicolon" ;;
|
|
"|") character="shift-backslash" ;;
|
|
"~") character="shift-grave_accent" ;;
|
|
"{") character="shift-bracket_left" ;;
|
|
"}") character="shift-bracket_right" ;;
|
|
"A") character="shift-a" ;;
|
|
"B") character="shift-b" ;;
|
|
"C") character="shift-c" ;;
|
|
"D") character="shift-d" ;;
|
|
"E") character="shift-e" ;;
|
|
"F") character="shift-f" ;;
|
|
"G") character="shift-g" ;;
|
|
"H") character="shift-h" ;;
|
|
"I") character="shift-i" ;;
|
|
"J") character="shift-j" ;;
|
|
"K") character="shift-k" ;;
|
|
"L") character="shift-l" ;;
|
|
"M") character="shift-m" ;;
|
|
"N") character="shift-n" ;;
|
|
"O") character="shift-o" ;;
|
|
"P") character="shift-p" ;;
|
|
"Q") character="shift-q" ;;
|
|
"R") character="shift-r" ;;
|
|
"S") character="shift-s" ;;
|
|
"T") character="shift-t" ;;
|
|
"U") character="shift-u" ;;
|
|
"V") character="shift-v" ;;
|
|
"W") character="shift-w" ;;
|
|
"X") character="shift-x" ;;
|
|
"Y") character="shift-y" ;;
|
|
"Z") character="shift-z" ;;
|
|
"!") character="shift-1" ;;
|
|
"@") character="shift-2" ;;
|
|
"#") character="shift-3" ;;
|
|
'$') character="shift-4" ;;
|
|
"%") character="shift-5" ;;
|
|
"^") character="shift-6" ;;
|
|
"&") character="shift-7" ;;
|
|
"*") character="shift-8" ;;
|
|
"(") character="shift-9" ;;
|
|
")") character="shift-0" ;;
|
|
esac
|
|
qm sendkey $VMID "$character"
|
|
done
|
|
qm sendkey $VMID ret
|
|
}
|
|
|
|
function get_available_bridges() {
|
|
ip -o link show type bridge 2>/dev/null | awk -F': ' '{print $2}' | sort
|
|
}
|
|
|
|
function validate_ip_octets() {
|
|
local octet='(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])'
|
|
[[ "$1" =~ ^${octet}\.${octet}\.${octet}\.${octet}$ ]]
|
|
}
|
|
|
|
function prompt_router_input() {
|
|
local var_name="$1" title="$2" prompt="$3" default_value="$4" value
|
|
if vm_dialog inputbox "$title" "$prompt" 8 58 "$default_value" --cancel-button Exit-Script; then
|
|
value="$VM_DIALOG_RESULT"
|
|
printf -v "$var_name" '%s' "$value"
|
|
else
|
|
exit_script
|
|
fi
|
|
}
|
|
|
|
function prompt_optional_static_ip() {
|
|
local ip_var="$1" gw_var="$2" mask_var="$3" prefix="$4" ip_value gw_value mask_value
|
|
prompt_router_input "$ip_var" "${prefix} IP ADDRESS" "Set a ${prefix} IP" "${!ip_var:-}"
|
|
ip_value="${!ip_var}"
|
|
if [ -z "$ip_value" ]; then
|
|
echo -e "${DGN}Using DHCP AS ${prefix} IP ADDRESS${CL}"
|
|
return 0
|
|
fi
|
|
if ! validate_ip_octets "$ip_value"; then
|
|
msg_error "Invalid IP Address format for ${prefix} IP. Needs to be 0.0.0.0, was $ip_value"
|
|
exit 1
|
|
fi
|
|
echo -e "${DGN}Using ${prefix} IP ADDRESS: ${BGN}$ip_value${CL}"
|
|
|
|
prompt_router_input "$gw_var" "${prefix} GATEWAY IP ADDRESS" "Set a ${prefix} GATEWAY IP" "${!gw_var:-}"
|
|
gw_value="${!gw_var}"
|
|
if [ -z "$gw_value" ]; then
|
|
msg_error "${prefix} gateway is required for a static IP."
|
|
exit 1
|
|
fi
|
|
if ! validate_ip_octets "$gw_value"; then
|
|
msg_error "Invalid IP Address format for ${prefix} Gateway. Needs to be 0.0.0.0, was $gw_value"
|
|
exit 1
|
|
fi
|
|
echo -e "${DGN}Using ${prefix} GATEWAY ADDRESS: ${BGN}$gw_value${CL}"
|
|
|
|
prompt_router_input "$mask_var" "${prefix} NETMASK" "Set a ${prefix} netmask (24 for example)" "${!mask_var:-}"
|
|
mask_value="${!mask_var}"
|
|
if [ -z "$mask_value" ]; then
|
|
msg_error "${prefix} netmask is required for a static IP."
|
|
exit 1
|
|
fi
|
|
if [[ ! "$mask_value" =~ ^[0-9]+$ || "$mask_value" -lt 1 || "$mask_value" -gt 32 ]]; then
|
|
msg_error "Invalid ${prefix} NETMASK format. Needs to be 1-32, was $mask_value"
|
|
exit 1
|
|
fi
|
|
echo -e "${DGN}Using ${prefix} NETMASK: ${BGN}$mask_value${CL}"
|
|
}
|
|
|
|
function prompt_router_mac() {
|
|
local var_name="$1" title="$2" prompt="$3" default_value="$4" label="$5" value
|
|
prompt_router_input "$var_name" "$title" "$prompt" "$default_value"
|
|
value="${!var_name}"
|
|
[[ -n "$value" ]] || value="$default_value"
|
|
printf -v "$var_name" '%s' "$value"
|
|
if ! validate_mac_address "$value"; then
|
|
msg_error "Invalid ${label}: $value"
|
|
exit 1
|
|
fi
|
|
echo -e "${DGN}Using ${label}: ${BGN}$value${CL}"
|
|
}
|
|
|
|
function select_network_mode() {
|
|
local available_bridges bridge_count default_wan_brg
|
|
available_bridges=$(get_available_bridges)
|
|
bridge_count=$(echo "$available_bridges" | wc -l)
|
|
default_wan_brg=$(echo "$available_bridges" | grep -v "^${BRG}$" | head -n1 || true)
|
|
|
|
if [[ "${VM_UNATTENDED:-0}" == "1" ]]; then
|
|
WAN_BRG="${VM_WAN_BRIDGE:-}"
|
|
elif [ "$bridge_count" -ge 2 ]; then
|
|
if vm_dialog radiolist "NETWORK CONFIGURATION" --cancel-button Exit-Script \
|
|
"Choose network setup mode for OPNsense:\n" 14 70 2 \
|
|
"dual" "Dual Interface (Firewall/Router) - uses ${default_wan_brg}" ON \
|
|
"single" "Single Interface (Proxy/VPN/IDS Server)" OFF; then
|
|
if [ "$VM_DIALOG_RESULT" = "dual" ]; then
|
|
WAN_BRG="$default_wan_brg"
|
|
echo -e "${DGN}Network Mode: ${BGN}Dual Interface (Firewall)${CL}"
|
|
echo -e "${DGN}Using WAN Bridge: ${BGN}${WAN_BRG}${CL}"
|
|
echo -e "${DGN}Using WAN MAC Address: ${BGN}${WAN_MAC}${CL}"
|
|
else
|
|
echo -e "${DGN}Network Mode: ${BGN}Single Interface (Proxy/VPN/IDS)${CL}"
|
|
WAN_BRG=""
|
|
fi
|
|
else
|
|
exit_script
|
|
fi
|
|
else
|
|
echo -e "${DGN}Network Mode: ${BGN}Single Interface (Proxy/VPN/IDS)${CL}"
|
|
echo -e "${YW} (Only one bridge detected, dual interface requires a second bridge)${CL}"
|
|
WAN_BRG=""
|
|
fi
|
|
}
|
|
|
|
function prompt_wan_bridge() {
|
|
local wan_bridges wan_menu=() first=true brg
|
|
wan_bridges=$(get_available_bridges | grep -v "^${BRG}$" || true)
|
|
if [ -z "$wan_bridges" ]; then
|
|
WAN_BRG=""
|
|
msg_warn "Only one bridge is available; using single-interface mode."
|
|
return 0
|
|
fi
|
|
while IFS= read -r brg; do
|
|
if $first; then
|
|
wan_menu+=("$brg" "" "ON")
|
|
first=false
|
|
else
|
|
wan_menu+=("$brg" "" "OFF")
|
|
fi
|
|
done <<<"$wan_bridges"
|
|
|
|
if vm_dialog radiolist "WAN BRIDGE" "Select WAN Bridge" 14 58 6 "${wan_menu[@]}"; then
|
|
WAN_BRG="$VM_DIALOG_RESULT"
|
|
[[ -n "$WAN_BRG" ]] || WAN_BRG=$(echo "$wan_bridges" | head -n1)
|
|
echo -e "${DGN}Using WAN Bridge: ${BGN}$WAN_BRG${CL}"
|
|
else
|
|
exit_script
|
|
fi
|
|
}
|
|
|
|
function default_settings() {
|
|
vm_apply_machine_type "i440fx"
|
|
VMID=$(get_valid_nextid)
|
|
DISK_SIZE="20G"
|
|
FORMAT=",efitype=4m"
|
|
MACHINE=""
|
|
DISK_CACHE=""
|
|
HN="opnsense"
|
|
CPU_TYPE=""
|
|
CORE_COUNT="4"
|
|
RAM_SIZE="8192"
|
|
BRG="vmbr0"
|
|
IP_ADDR=""
|
|
WAN_IP_ADDR=""
|
|
LAN_GW=""
|
|
WAN_GW=""
|
|
NETMASK=""
|
|
WAN_NETMASK=""
|
|
VLAN=""
|
|
MAC=$GEN_MAC
|
|
WAN_MAC=$GEN_MAC_LAN
|
|
WAN_BRG=""
|
|
MTU=""
|
|
START_VM="yes"
|
|
METHOD="default"
|
|
|
|
echo -e "${DGN}Using Virtual Machine ID: ${BGN}${VMID}${CL}"
|
|
echo -e "${DGN}Using Hostname: ${BGN}${HN}${CL}"
|
|
echo -e "${DGN}Allocated Cores: ${BGN}${CORE_COUNT}${CL}"
|
|
echo -e "${DGN}Allocated RAM: ${BGN}${RAM_SIZE}${CL}"
|
|
if ! ip link show "${BRG}" &>/dev/null; then
|
|
msg_error "Bridge '${BRG}' does not exist"
|
|
exit
|
|
else
|
|
echo -e "${DGN}Using LAN Bridge: ${BGN}${BRG}${CL}"
|
|
fi
|
|
echo -e "${DGN}Using LAN VLAN: ${BGN}Default${CL}"
|
|
echo -e "${DGN}Using LAN MAC Address: ${BGN}${MAC}${CL}"
|
|
select_network_mode
|
|
echo -e "${DGN}Using Interface MTU Size: ${BGN}Default${CL}"
|
|
echo -e "${DGN}Start VM when completed: ${BGN}yes${CL}"
|
|
echo -e "${BL}Creating a OPNsense VM using the above default settings${CL}"
|
|
}
|
|
|
|
function advanced_settings() {
|
|
METHOD="advanced"
|
|
IP_ADDR=""
|
|
WAN_IP_ADDR=""
|
|
LAN_GW=""
|
|
WAN_GW=""
|
|
NETMASK=""
|
|
WAN_NETMASK=""
|
|
VLAN=""
|
|
MTU=""
|
|
vm_prompt_disk_size "20G"
|
|
vm_prompt_keyboard
|
|
vm_prompt_verbose "no"
|
|
vm_prompt_start_vm "yes"
|
|
vm_prompt_vmid "${VMID:-$(get_valid_nextid)}"
|
|
vm_prompt_machine_type "i440fx"
|
|
vm_prompt_cpu_model "kvm64"
|
|
vm_prompt_disk_cache "none"
|
|
vm_prompt_hostname "opnsense"
|
|
vm_prompt_cpu_cores "4"
|
|
vm_prompt_ram "8192"
|
|
|
|
prompt_router_input "BRG" "LAN BRIDGE" "Set a LAN Bridge" "vmbr0"
|
|
[[ -n "$BRG" ]] || BRG="vmbr0"
|
|
if ! ip link show "${BRG}" &>/dev/null; then
|
|
msg_error "Bridge '${BRG}' does not exist"
|
|
exit 1
|
|
fi
|
|
echo -e "${DGN}Using LAN Bridge: ${BGN}$BRG${CL}"
|
|
|
|
prompt_optional_static_ip "IP_ADDR" "LAN_GW" "NETMASK" "LAN"
|
|
prompt_wan_bridge
|
|
if [[ -n "$WAN_BRG" ]]; then
|
|
prompt_optional_static_ip "WAN_IP_ADDR" "WAN_GW" "WAN_NETMASK" "WAN"
|
|
fi
|
|
prompt_router_mac "MAC" "LAN MAC ADDRESS" "Set a LAN MAC Address" "$GEN_MAC" "LAN MAC address"
|
|
if [[ -n "$WAN_BRG" ]]; then
|
|
prompt_router_mac "WAN_MAC" "WAN MAC ADDRESS" "Set a WAN MAC Address" "$GEN_MAC_LAN" "WAN MAC address"
|
|
else
|
|
WAN_MAC="$GEN_MAC_LAN"
|
|
fi
|
|
|
|
if vm_confirm_advanced_settings "Ready to create OPNsense VM?"; then
|
|
echo -e "${RD}Creating a OPNsense VM using the above advanced settings${CL}"
|
|
else
|
|
header_info
|
|
echo -e "${RD}Using Advanced Settings${CL}"
|
|
advanced_settings
|
|
fi
|
|
}
|
|
|
|
vm_start_script "Use Default Settings?\n\nDefaults:\n• 4 CPU Cores\n• 8 GB RAM\n• 20 GB Disk" 13 58
|
|
post_to_api_vm
|
|
|
|
vm_select_storage "$HN"
|
|
msg_ok "Virtual Machine ID is ${CL}${BL}$VMID${CL}."
|
|
msg_info "Retrieving the URL for the OPNsense Qcow2 Disk Image"
|
|
vm_latest_from_index "https://download.freebsd.org/releases/VM-IMAGES/" "${FREEBSD_MAJOR}\.[0-9]+-RELEASE" \
|
|
--probe "https://download.freebsd.org/releases/VM-IMAGES/{}/amd64/Latest/FreeBSD-{}-amd64.qcow2.xz" \
|
|
--probe "https://download.freebsd.org/releases/VM-IMAGES/{}/amd64/Latest/FreeBSD-{}-amd64-ufs.qcow2.xz" \
|
|
--probe "https://download.freebsd.org/releases/VM-IMAGES/{}/amd64/Latest/FreeBSD-{}-amd64-zfs.qcow2.xz" || exit 115
|
|
FREEBSD_VER="$VM_INDEX_LATEST"
|
|
URL="$VM_INDEX_URL"
|
|
msg_ok "Download URL: ${CL}${BL}${URL}${CL}"
|
|
|
|
# Check available disk space (require at least 20GB for safety)
|
|
if ! check_disk_space "$TEMP_DIR" 20; then
|
|
AVAILABLE_GB=$(df -h "$TEMP_DIR" | awk 'NR==2 {print $4}')
|
|
msg_error "Insufficient disk space in temporary directory ($TEMP_DIR)."
|
|
msg_error "Available: ${AVAILABLE_GB}, Required: ~20GB for FreeBSD image decompression."
|
|
msg_error "Please free up space or ensure /tmp has sufficient storage."
|
|
exit 214
|
|
fi
|
|
|
|
msg_info "Downloading FreeBSD Image"
|
|
# A mirror serving an error page returns 200, so size decides whether this
|
|
# is an image. Anything real here is far above 5 MB.
|
|
CACHE_FILE="$(vm_image_cache_path "$URL")"
|
|
vm_fetch_image "$URL" "$CACHE_FILE" --cache --verify-xz --min-bytes $((5 * 1024 * 1024)) || exit 1
|
|
echo -en "\e[1A\e[0K"
|
|
msg_ok "Downloaded ${CL}${BL}$(basename "$CACHE_FILE")${CL}"
|
|
|
|
# Check disk space again before decompression
|
|
if ! check_disk_space "$TEMP_DIR" 15; then
|
|
AVAILABLE_GB=$(df -h "$TEMP_DIR" | awk 'NR==2 {print $4}')
|
|
msg_error "Insufficient disk space for decompression."
|
|
msg_error "Available: ${AVAILABLE_GB}, Required: ~15GB for decompressed image."
|
|
exit 214
|
|
fi
|
|
|
|
FILE="$TEMP_DIR/FreeBSD.qcow2"
|
|
vm_extract_image "$CACHE_FILE" "$FILE" || exit 115
|
|
FILE="$VM_IMAGE_FILE"
|
|
|
|
vm_define_disk_references 1
|
|
|
|
vm_claim_vmid
|
|
msg_info "Creating a OPNsense VM"
|
|
# A firewall VM filters itself: Proxmox's per-NIC firewall off, and virtio
|
|
# multiqueue matched to the cores so routing scales past one vCPU.
|
|
qm create $VMID -agent 1${MACHINE} -tablet 0 -bios ovmf${CPU_TYPE} -cores $CORE_COUNT -memory $RAM_SIZE -balloon 0 \
|
|
-name $HN -tags community-script -net0 virtio,bridge=$BRG,macaddr=$MAC,firewall=0,queues=$CORE_COUNT$VLAN$MTU -onboot 1 -ostype l26 -scsihw virtio-scsi-pci
|
|
vm_mark_created
|
|
|
|
# Retry pvesm alloc on transient zfs_request "got timeout" errors (#14127)
|
|
alloc_attempt=1
|
|
alloc_max=4
|
|
alloc_delay=5
|
|
while :; do
|
|
alloc_err=$(pvesm alloc $STORAGE $VMID $DISK0 4M 2>&1 >/dev/null) && break
|
|
if [[ "$alloc_err" == *"got timeout"* && $alloc_attempt -lt $alloc_max ]]; then
|
|
echo -e "${YW}[WARN]${CL} pvesm alloc hit zfs timeout (attempt $alloc_attempt/$alloc_max), retrying in ${alloc_delay}s..."
|
|
pvesm free "${DISK0_REF}" &>/dev/null || true
|
|
sleep "$alloc_delay"
|
|
alloc_attempt=$((alloc_attempt + 1))
|
|
alloc_delay=$((alloc_delay * 2))
|
|
continue
|
|
fi
|
|
echo -e "$alloc_err" >&2
|
|
exit 220
|
|
done
|
|
vm_import_disk "$VMID" "$FILE" "$STORAGE"
|
|
qm set $VMID \
|
|
-efidisk0 ${DISK0_REF}${FORMAT} \
|
|
-scsi0 "${VM_IMPORTED_DISK}",${DISK_CACHE}${THIN}size=2G \
|
|
-boot order=scsi0 \
|
|
-serial0 socket \
|
|
-tags community-script >/dev/null
|
|
vm_resize_disk
|
|
set_description
|
|
|
|
msg_info "Bridge interfaces are being added."
|
|
qm set $VMID \
|
|
-net0 virtio,bridge=${BRG},macaddr=${MAC},firewall=0,queues=${CORE_COUNT}${VLAN}${MTU} 2>/dev/null
|
|
if [ -n "$WAN_BRG" ]; then
|
|
qm set $VMID \
|
|
-net1 virtio,bridge=${WAN_BRG},macaddr=${WAN_MAC},firewall=0,queues=${CORE_COUNT} 2>/dev/null
|
|
fi
|
|
msg_ok "Bridge interfaces have been successfully added."
|
|
|
|
msg_ok "Created a OPNsense VM ${CL}${BL}(${HN})"
|
|
msg_ok "Starting OPNsense VM (the bootstrap takes 10-30 minutes)"
|
|
$STD qm start $VMID
|
|
sleep 90
|
|
send_line_to_vm "root"
|
|
sleep 2
|
|
send_line_to_vm ""
|
|
send_line_to_vm "for i in \$(seq 1 60); do fetch -q https://raw.githubusercontent.com/opnsense/update/master/src/bootstrap/opnsense-bootstrap.sh.in && break; sleep 5; done"
|
|
sleep 5
|
|
# FreeBSD 15+ VM images ship the base system as pkgbase packages; the bootstrap's
|
|
# "delete all packages" step would remove the running base system (/bin/rm etc.)
|
|
# and brick the VM. Deregister them from the pkg db first - the files stay in
|
|
# place and OPNsense replaces base and kernel with its own sets afterwards.
|
|
send_line_to_vm "echo \"PRAGMA foreign_keys=ON; DELETE FROM packages WHERE name LIKE 'FreeBSD-%';\" | pkg shell"
|
|
sleep 5
|
|
send_line_to_vm "sh ./opnsense-bootstrap.sh.in -y -f -r ${var_version}"
|
|
msg_ok "OPNsense VM is being installed, do not close the terminal, or the installation will fail."
|
|
# The bootstrap ends with an automatic reboot into OPNsense. While it runs the
|
|
# console keeps changing (download progress, package installs); once the VM has
|
|
# settled at the login prompt the screen stays static. Poll a screendump hash
|
|
# and continue after 3 minutes without change, bounded by a floor (the build
|
|
# never finishes faster) and a ceiling for slow machines. If no screendump can
|
|
# be captured at all, fall back to a fixed wait.
|
|
SCREEN_PPM="${TEMP_DIR}/screen-${VMID}.ppm"
|
|
|
|
function screen_hash() {
|
|
# Remove the previous dump first: a stale file from an earlier successful
|
|
# dump must not simulate a static screen when later dumps start failing.
|
|
# Note: "qm monitor" is unusable here - its readline attaches to /dev/tty
|
|
# even with piped stdin and captures the terminal, so use the API instead.
|
|
rm -f "$SCREEN_PPM"
|
|
timeout 10 pvesh create /nodes/$(hostname -s)/qemu/$VMID/monitor --command "screendump ${SCREEN_PPM}" >/dev/null 2>&1 || true
|
|
md5sum "$SCREEN_PPM" 2>/dev/null | cut -d' ' -f1 || true
|
|
}
|
|
|
|
build_elapsed=300
|
|
build_stable=0
|
|
screen_ok=0
|
|
hash_a=""
|
|
hash_b=""
|
|
sleep 300
|
|
while [ $build_stable -lt 6 ] && [ $build_elapsed -lt 2400 ]; do
|
|
sleep 30
|
|
build_elapsed=$((build_elapsed + 30))
|
|
new_hash=$(screen_hash)
|
|
if [ -n "$new_hash" ]; then
|
|
screen_ok=1
|
|
# The login prompt cursor may blink: a screen alternating between the same
|
|
# two frames (A/B/A/B) counts as stable, anything new resets the counter
|
|
if [ "$new_hash" = "$hash_a" ] || [ "$new_hash" = "$hash_b" ]; then
|
|
build_stable=$((build_stable + 1))
|
|
else
|
|
build_stable=0
|
|
fi
|
|
else
|
|
build_stable=0
|
|
fi
|
|
hash_b="$hash_a"
|
|
hash_a="$new_hash"
|
|
if [ -n "$new_hash" ]; then
|
|
echo -e "${DGN}Waiting for OPNsense build: ${YW}$((build_elapsed / 60))min elapsed, screen ${new_hash:0:8}, stable ${build_stable}/6${CL}"
|
|
else
|
|
echo -e "${DGN}Waiting for OPNsense build: ${YW}$((build_elapsed / 60))min elapsed, screendump failed${CL}"
|
|
fi
|
|
# No working screendump after several attempts: fixed wait instead
|
|
if [ $screen_ok -eq 0 ] && [ $build_elapsed -ge 480 ]; then
|
|
msg_warn "Console screendump not available on this system - falling back to a fixed wait (12 minutes)."
|
|
sleep 720
|
|
build_elapsed=$((build_elapsed + 720))
|
|
break
|
|
fi
|
|
done
|
|
if [ $build_stable -ge 6 ]; then
|
|
msg_ok "OPNsense console settled after $((build_elapsed / 60)) minutes"
|
|
else
|
|
msg_warn "The console did not settle within $((build_elapsed / 60)) minutes; continuing, verify the OPNsense console afterwards"
|
|
fi
|
|
# The answers below are matched to OPNsense's console dialog (setaddr.php).
|
|
# One prompt ("via WAN tracking?") only appears when WAN has DHCP6, so a spare
|
|
# "n" is sent for it; where it is absent, OPNsense re-asks the IPv6 address and
|
|
# the following ENTER lands there. Invalid answers are re-asked, empty ones take
|
|
# the default, which keeps the sequence safe in both dialog variants.
|
|
send_line_to_vm "root"
|
|
send_line_to_vm "opnsense"
|
|
send_line_to_vm "2"
|
|
|
|
if [ "$IP_ADDR" != "" ]; then
|
|
send_line_to_vm "1"
|
|
send_line_to_vm "n"
|
|
send_line_to_vm "${IP_ADDR}"
|
|
send_line_to_vm "${NETMASK}"
|
|
send_line_to_vm "${LAN_GW}"
|
|
send_line_to_vm "n"
|
|
send_line_to_vm " "
|
|
send_line_to_vm "n"
|
|
send_line_to_vm "n"
|
|
send_line_to_vm " "
|
|
send_line_to_vm "n"
|
|
send_line_to_vm "n"
|
|
send_line_to_vm "n"
|
|
send_line_to_vm "n"
|
|
send_line_to_vm "n"
|
|
else
|
|
send_line_to_vm "1"
|
|
send_line_to_vm "y"
|
|
send_line_to_vm "n"
|
|
send_line_to_vm "n"
|
|
send_line_to_vm " "
|
|
send_line_to_vm "n"
|
|
send_line_to_vm "n"
|
|
send_line_to_vm "n"
|
|
fi
|
|
#Wait for config changes to be saved
|
|
sleep 20
|
|
if [ -n "$WAN_BRG" ] && [ "$WAN_IP_ADDR" != "" ]; then
|
|
send_line_to_vm "2"
|
|
send_line_to_vm "2"
|
|
send_line_to_vm "n"
|
|
send_line_to_vm "${WAN_IP_ADDR}"
|
|
send_line_to_vm "${WAN_NETMASK}"
|
|
send_line_to_vm "${WAN_GW}"
|
|
send_line_to_vm "n"
|
|
send_line_to_vm " "
|
|
send_line_to_vm "n"
|
|
send_line_to_vm " "
|
|
send_line_to_vm "n"
|
|
send_line_to_vm "n"
|
|
send_line_to_vm "n"
|
|
fi
|
|
sleep 10
|
|
send_line_to_vm "0"
|
|
if [[ "$START_VM" == "no" ]]; then
|
|
msg_info "Shutting down OPNsense as requested"
|
|
$STD qm shutdown "$VMID" --timeout 120
|
|
msg_ok "OPNsense VM shut down"
|
|
else
|
|
msg_ok "OPNsense VM is running"
|
|
fi
|
|
|
|
if [ "$IP_ADDR" != "" ]; then
|
|
LAN_URL="http://${IP_ADDR}"
|
|
else
|
|
LAN_URL="DHCP - check the OPNsense console or your leases"
|
|
fi
|
|
|
|
vm_print_summary \
|
|
"LAN URL=${LAN_URL}" \
|
|
"LAN Bridge=${BRG}" \
|
|
"WAN Bridge=${WAN_BRG:-single-interface mode}" \
|
|
"OPNsense Version=${var_version}" \
|
|
"FreeBSD Base=${FREEBSD_VER}"
|
|
vm_next_steps \
|
|
"Verify the guest bootstrap and network configuration in the OPNsense console." \
|
|
"Login as root with password opnsense after successful bootstrap, then change the password immediately." \
|
|
"Keep WAN and LAN isolated appropriately; single-interface mode is intended for proxy, VPN, or IDS use cases."
|
|
vm_finish "VM creation completed; verify the guest bootstrap and network configuration in the console."
|