Files
ProxmoxVE/vm/openwrt-vm.sh
T
CanbiZ (MickLesk) 0973d1ccfb OpenWrt VM: read the stable release from downloads.openwrt.org (#18411)
* OpenWrt VM: read the stable release from downloads.openwrt.org

The version came from a line of text on the openwrt.org wiki front page.
One host got that page without the line three times in a row while
others resolved fine at the same moment, and the run ended with exit 115
before anything was downloaded. .versions.json on the download server
is what the OpenWrt firmware selector reads and names the stable release
explicitly.

* Update vm/openwrt-vm.sh

Co-authored-by: Tobias <96661824+CrazyWolf13@users.noreply.github.com>

---------

Co-authored-by: Tobias <96661824+CrazyWolf13@users.noreply.github.com>
2026-11-03 14:31:32 +01:00

370 lines
12 KiB
Bash

#!/usr/bin/env bash
# Copyright (c) 2021-2026 tteck
# Author: tteck (tteckster) | Jon Spriggs (jontheniceguy) | 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/DevScripts/main}"
source <(curl -fsSL "${COMMUNITY_SCRIPTS_CORE_URL:-https://raw.githubusercontent.com/community-scripts/core/main}/pve/vm-core.func")
load_functions
RANDOM_UUID="$(cat /proc/sys/kernel/random/uuid)"
METHOD=""
APP="OpenWrt"
APP_TYPE="vm"
NSAPP="openwrt-vm"
var_os="openwrt"
var_version=" "
DISK_SIZE="1G"
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")
header_info
echo -e "\n 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
TEMP_DIR=$(mktemp -d)
pushd "$TEMP_DIR" >/dev/null
vm_preflight
vm_require_tools gzip
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 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"
[[ -n "$value" ]] || value="$default_value"
printf -v "$var_name" '%s' "$value"
else
exit_script
fi
}
function prompt_router_ip() {
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}"
if ! validate_ip_octets "$value"; then
msg_error "Invalid ${label} format. Needs to be 0.0.0.0, was $value"
exit 1
fi
echo -e "${DGN}Using ${label}: ${BGN}$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}"
if ! validate_mac_address "$value"; then
msg_error "Invalid ${label}: $value"
exit 1
fi
echo -e "${DGN}Using ${label}: ${BGN}$value${CL}"
}
function prompt_router_vlan() {
local var_name="$1" label_var="$2" title="$3" prompt="$4" default_value="$5" input_value
while true; do
prompt_router_input "$label_var" "$title" "$prompt" "$default_value"
input_value="${!label_var}"
if [ -z "$input_value" ] || [ "$input_value" = "Default" ]; then
printf -v "$var_name" '%s' ""
printf -v "$label_var" '%s' "Default"
echo -e "${DGN}Using ${title}: ${BGN}Default${CL}"
break
fi
if validate_vlan_tag "$input_value"; then
printf -v "$var_name" '%s' ",tag=$input_value"
echo -e "${DGN}Using ${title}: ${BGN}$input_value${CL}"
break
fi
vm_dialog msgbox "INVALID INPUT" "VLAN must be a number between 1 and 4094, or leave blank for default." 8 58
done
}
function prompt_router_mtu() {
local input_value
while true; do
prompt_router_input "MTU_VALUE" "MTU SIZE" "Set Interface MTU Size (leave blank for default)" ""
input_value="$MTU_VALUE"
if [ -z "$input_value" ]; then
MTU=""
MTU_VALUE="Default"
echo -e "${DGN}Using Interface MTU Size: ${BGN}Default${CL}"
break
fi
if validate_mtu "$input_value"; then
MTU=",mtu=$input_value"
echo -e "${DGN}Using Interface MTU Size: ${BGN}$input_value${CL}"
break
fi
vm_dialog msgbox "INVALID INPUT" "MTU Size must be a number between 576 and 65520, or leave blank for default." 8 58
done
}
function default_settings() {
VMID=$(get_valid_nextid)
vm_apply_machine_type "i440fx"
HN="openwrt"
CORE_COUNT="1"
RAM_SIZE="256"
CPU_TYPE=""
DISK_CACHE=""
BRG="vmbr0"
LAN_BRG="vmbr0"
MAC=$GEN_MAC
LAN_MAC=$GEN_MAC_LAN
VLAN=""
LAN_VLAN=""
LAN_IP_ADDR="192.168.1.1"
LAN_NETMASK="255.255.255.0"
MTU=""
START_VM="yes"
METHOD="default"
DISK_SIZE="1G"
echo -e "${CONTAINERID}${BOLD}${DGN}VMID: ${BGN}${VMID}${CL}"
echo -e "${HOSTNAME}${BOLD}${DGN}Hostname: ${BGN}${HN}${CL}"
echo -e "${CPUCORE}${BOLD}${DGN}CPU Cores: ${BGN}${CORE_COUNT}${CL}"
echo -e "${RAMSIZE}${BOLD}${DGN}RAM: ${BGN}${RAM_SIZE}${CL}"
echo -e "${DISKSIZE}${BOLD}${DGN}Disk Size: ${BGN}${DISK_SIZE}${CL}"
echo -e "${BRIDGE}${BOLD}${DGN}WAN Bridge: ${BGN}${BRG}${CL}"
echo -e "${BRIDGE}${BOLD}${DGN}LAN Bridge: ${BGN}${LAN_BRG}${CL}"
echo -e "${MACADDRESS}${BOLD}${DGN}WAN MAC: ${BGN}${MAC}${CL}"
echo -e "${MACADDRESS}${BOLD}${DGN}LAN MAC: ${BGN}${LAN_MAC}${CL}"
}
function advanced_settings() {
METHOD="advanced"
CPU_TYPE=""
DISK_CACHE=""
vm_apply_machine_type "i440fx"
vm_prompt_vmid "${VMID:-$(get_valid_nextid)}"
vm_prompt_hostname "openwrt"
vm_prompt_cpu_cores "1"
vm_prompt_ram "256"
vm_prompt_disk_size "1G"
vm_prompt_keyboard
vm_prompt_verbose "no"
prompt_router_input "BRG" "WAN BRIDGE" "Set a WAN Bridge" "vmbr0"
echo -e "${DGN}Using WAN Bridge: ${BGN}$BRG${CL}"
prompt_router_input "LAN_BRG" "LAN BRIDGE" "Set a LAN Bridge" "vmbr0"
echo -e "${DGN}Using LAN Bridge: ${BGN}$LAN_BRG${CL}"
prompt_router_ip "LAN_IP_ADDR" "LAN IP ADDRESS" "Set a router IP" "${LAN_IP_ADDR:-192.168.1.1}" "LAN IP ADDRESS"
prompt_router_ip "LAN_NETMASK" "LAN NETMASK" "Set a router netmask" "${LAN_NETMASK:-255.255.255.0}" "LAN NETMASK"
prompt_router_mac "MAC" "WAN MAC ADDRESS" "Set a WAN MAC Address" "$GEN_MAC" "WAN MAC address"
prompt_router_mac "LAN_MAC" "LAN MAC ADDRESS" "Set a LAN MAC Address" "$GEN_MAC_LAN" "LAN MAC address"
prompt_router_vlan "VLAN" "VLAN1" "WAN VLAN" "Set a WAN Vlan (leave blank for default)" ""
prompt_router_vlan "LAN_VLAN" "VLAN2" "LAN VLAN" "Set a LAN Vlan" "999"
prompt_router_mtu
vm_prompt_start_vm "yes"
if vm_confirm_advanced_settings "Ready to create OpenWrt VM?"; then
echo -e "${RD}Creating a OpenWrt 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• 1 CPU Core\n• 256 MB RAM\n• 1 GB Disk" 13 58
post_to_api_vm
vm_select_storage "$HN"
msg_info "Getting URL for OpenWrt Disk Image"
vm_latest_from_index "https://downloads.openwrt.org/.versions.json" '"stable_version": *"\K[0-9]+\.[0-9]+\.[0-9]+' || exit 115
var_version="$VM_INDEX_LATEST"
URL="https://downloads.openwrt.org/releases/$var_version/targets/x86/64/openwrt-$var_version-x86-64-generic-ext4-combined.img.gz"
msg_ok "${CL}${BL}${URL}${CL}"
# 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 --min-bytes $((5 * 1024 * 1024)) || exit 115
FILE="$TEMP_DIR/$(basename "${CACHE_FILE%.gz}")"
vm_extract_image "$CACHE_FILE" "$FILE" || exit 115
FILE="$VM_IMAGE_FILE"
vm_claim_vmid
msg_info "Creating OpenWrt VM"
qm create $VMID -cores $CORE_COUNT -memory $RAM_SIZE -name $HN \
-onboot 1 -ostype l26 -scsihw virtio-scsi-pci --tablet 0 >/dev/null
vm_mark_created
vm_import_disk "$VMID" "$FILE" "$STORAGE"
$STD qm set $VMID \
-scsi0 "${VM_IMPORTED_DISK}" \
-boot order=scsi0 \
-tags community-script
msg_ok "Attached disk"
msg_info "Resizing disk to ${DISK_SIZE}"
qm disk resize "$VMID" scsi0 "${DISK_SIZE}" >/dev/null
msg_ok "Resized disk to ${DISK_SIZE}"
set_description
msg_ok "Created OpenWrt VM ${CL}${BL}(${HN})"
msg_info "Booting OpenWrt to configure its network interfaces"
$STD qm start $VMID
sleep 15
VM_STATE=""
for i in {1..30}; do
if ! VM_STATE="$(qm status "$VMID" 2>&1)"; then
msg_error "VM $VMID no longer exists: ${VM_STATE}"
exit 226
fi
[[ "$VM_STATE" == *running* ]] && break
sleep 1
done
if [[ "$VM_STATE" != *running* ]]; then
msg_error "VM $VMID did not reach running state: ${VM_STATE}"
exit 226
fi
sleep 5
msg_ok "OpenWrt is running"
msg_info "Configuring network interfaces in OpenWrt"
$STD send_line_to_vm ""
$STD send_line_to_vm "uci delete network.@device[0]"
$STD send_line_to_vm "uci set network.wan=interface"
$STD send_line_to_vm "uci set network.wan.device=eth1"
$STD send_line_to_vm "uci set network.wan.proto=dhcp"
$STD send_line_to_vm "uci delete network.lan"
$STD send_line_to_vm "uci set network.lan=interface"
$STD send_line_to_vm "uci set network.lan.device=eth0"
$STD send_line_to_vm "uci set network.lan.proto=static"
$STD send_line_to_vm "uci set network.lan.ipaddr=${LAN_IP_ADDR}"
$STD send_line_to_vm "uci set network.lan.netmask=${LAN_NETMASK}"
$STD send_line_to_vm "uci commit"
$STD send_line_to_vm "poweroff"
msg_ok "Network interfaces configured in OpenWrt"
msg_info "Waiting for OpenWrt to shut down"
for i in {1..60}; do
VM_STATE="$(qm status "$VMID" 2>&1)" || break
[[ "$VM_STATE" == *stopped* ]] && break
sleep 2
done
if [[ "$VM_STATE" != *stopped* ]]; then
msg_error "OpenWrt did not shut down: ${VM_STATE}"
exit 226
fi
msg_ok "OpenWrt has shut down"
msg_info "Adding bridge interfaces on Proxmox side"
qm set $VMID \
-net0 virtio,bridge=${LAN_BRG},macaddr=${LAN_MAC}${LAN_VLAN}${MTU} \
-net1 virtio,bridge=${BRG},macaddr=${MAC}${VLAN}${MTU} >/dev/null
msg_ok "Bridge interfaces added"
if [ "$START_VM" = "yes" ]; then
msg_info "Starting OpenWrt VM"
$STD qm start $VMID
msg_ok "Started OpenWrt VM"
fi
VLAN_FINISH=""
if [ -z "$VLAN" ] && [ "${VLAN2:-999}" != "999" ]; then
VLAN_FINISH=" Please remember to adjust the VLAN tags to suit your network."
fi
vm_print_summary \
"LAN URL=http://${LAN_IP_ADDR}" \
"WAN Bridge=${BRG}" \
"LAN Bridge=${LAN_BRG}" \
"OpenWrt Version=${var_version}"
vm_next_steps \
"Open the LAN web UI at http://${LAN_IP_ADDR}." \
"Login as root with a blank initial password, then set a strong password immediately." \
"Keep WAN and LAN isolated appropriately; the defaults put both on vmbr0 until you adjust bridges/VLANs.${VLAN_FINISH}"
vm_finish