Files
ProxmoxVE/vm/opnsense-vm.sh
T
2026-10-08 15:09:45 +02:00

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."