#!/usr/bin/env bash # ---------------------------------------------------------------------- # vyos_upgrade.sh # # Fetches VyOS nightly builds, lets the user select an ISO, downloads it, # optionally verifies it, and prints VyOS installation commands. # # Dependencies: # Required: bash, curl, awk, sed # Verification: sha256sum and/or minisign # # Usage: # ./vyos_upgrade.sh [-u URL] [-s] [-n N] [-y] [-k] [-h] # # Options: # -u URL URL of the page listing builds # -s Print installation commands after downloading # -n N Select the N-th build; default is 1 # -y Skip the installation confirmation prompt # -k Skip checksum/signature verification # -h Show this help # ---------------------------------------------------------------------- set -e PAGE_URL="https://vyos.net/get/nightly-builds/" DO_INSTALL=false SELECT_NUM=1 AUTO_CONFIRM=false SKIP_VERIFY=false usage() { cat <&2 } die() { error "$*" exit 1 } # ---------------------------------------------------------------------- # Parse command-line options # ---------------------------------------------------------------------- while getopts ':u:sn:ykh' opt; do case "$opt" in u) PAGE_URL=$OPTARG ;; s) DO_INSTALL=true ;; n) SELECT_NUM=$OPTARG ;; y) AUTO_CONFIRM=true ;; k) SKIP_VERIFY=true ;; h) usage ;; \?) die "Invalid option: -$OPTARG" ;; :) die "Option -$OPTARG requires an argument" ;; esac done shift $((OPTIND - 1)) case "$SELECT_NUM" in ''|*[!0-9]*) die "Selection number must be a positive integer" ;; esac [ "$SELECT_NUM" -ge 1 ] || die "Selection number must be at least 1" # ---------------------------------------------------------------------- # Download and parse the build page # # This parser is intentionally limited to the structure used by the page: # #
# ... #
  • # ... # ... #
  • #
    # # It does not require Python, Perl, or an HTML parsing module. # ---------------------------------------------------------------------- log "Downloading page $PAGE_URL ..." HTML_FILE=$(mktemp "${TMPDIR:-/tmp}/vyos-upgrade.XXXXXX") trap 'rm -f "$HTML_FILE"' EXIT HUP INT TERM curl --fail --silent --show-error --location \ "$PAGE_URL" >"$HTML_FILE" || die "Failed to download page" BUILDS_FILE=$(mktemp "${TMPDIR:-/tmp}/vyos-builds.XXXXXX") trap 'rm -f "$HTML_FILE" "$BUILDS_FILE"' EXIT HUP INT TERM awk ' BEGIN { in_container = 0 in_item = 0 href_count = 0 iso_name = "" iso_url = "" sig_url = "" } { line = $0 if (line ~ /]*id[ \t]*=[ \t]*["'\'']rolling-current["'\'']/) { in_container = 1 } if (!in_container) next if (line ~ /<\/div>/ && line !~ /
  • ]*>/) { in_item = 1 href_count = 0 iso_name = "" iso_url = "" sig_url = "" } if (in_item) { while (match(line, /]*href[ \t]*=[ \t]*["'\''][^"'\'']+["'\''][^>]*>/)) { tag = substr(line, RSTART, RLENGTH) href = tag sub(/^.*href[ \t]*=[ \t]*["'\'']/, "", href) sub(/["'\''].*$/, "", href) href_count++ if (href_count == 1) { iso_url = href } else if (href_count == 2) { sig_url = href } line = substr(line, RSTART + RLENGTH) } if (iso_name == "" && iso_url != "") { iso_name = iso_url sub(/^.*\//, "", iso_name) sub(/[?#].*$/, "", iso_name) } } if (in_item && line ~ /<\/li>/) { if (iso_url != "" && sig_url != "") { printf "%s\t%s\t%s\n", iso_name, iso_url, sig_url } in_item = 0 href_count = 0 iso_name = "" iso_url = "" sig_url = "" } } ' "$HTML_FILE" >"$BUILDS_FILE" [ -s "$BUILDS_FILE" ] || die "No ISO entries were found on the page" BUILD_COUNT=$(wc -l <"$BUILDS_FILE" | sed 's/[[:space:]]//g') log "Found $BUILD_COUNT nightly build(s):" i=1 while IFS="$(printf '\t')" read -r iso_name iso_url sig_url; do printf ' %s) %s\n' "$i" "$iso_name" i=$((i + 1)) done <"$BUILDS_FILE" [ "$SELECT_NUM" -le "$BUILD_COUNT" ] || die "Selection $SELECT_NUM is out of range (1-$BUILD_COUNT)" CHOSEN_LINE=$(sed -n "${SELECT_NUM}p" "$BUILDS_FILE") IFS="$(printf '\t')" read -r ISO_NAME ISO_URL SIG_URL </dev/null 2>&1 || die "sha256sum is required for checksum verification" # Ensure the checksum file refers to the downloaded filename. CHECKSUM_LINE=$(sed "s#^[^ ]*#$ISO_FILE#" "$SIG_FILE") printf '%s\n' "$CHECKSUM_LINE" | sha256sum -c - || die "SHA-256 verification failed" log "SHA-256 verification succeeded" ;; *.minisig|*.sig) command -v minisign >/dev/null 2>&1 || die "minisign is required for signature verification" MINISIGN_PUBKEY="RWSIhkR/dkM2DSaBRniv/bbbAf8hmDqdbOEmgXkf1RxRoxzodgKcDyGq" minisign -Vm "$ISO_FILE" \ -x "$SIG_FILE" \ -P "$MINISIGN_PUBKEY" || die "minisign verification failed" log "minisign verification succeeded" ;; *) # Try minisign as a fallback if the server uses an unexpected # signature filename. if command -v minisign >/dev/null 2>&1; then MINISIGN_PUBKEY="RWSIhkR/dkM2DSaBRniv/bbbAf8hmDqdbOEmgXkf1RxRoxzodgKcDyGq" minisign -Vm "$ISO_FILE" \ -x "$SIG_FILE" \ -P "$MINISIGN_PUBKEY" || die "Unable to verify the ISO" log "minisign verification succeeded" else die "Unknown signature format and minisign is not installed" fi ;; esac fi log "ISO download completed successfully" # ---------------------------------------------------------------------- # Optional installation instructions # ---------------------------------------------------------------------- if $DO_INSTALL; then if ! $AUTO_CONFIRM; then printf 'Do you want to install this image now? (y/N) ' IFS= read -r answer case "$answer" in y|Y) ;; *) log "Installation aborted by user"; exit 0 ;; esac fi ISO_PATH=$(pwd)/$ISO_FILE log "Run these commands inside the VyOS CLI:" printf 'add system image %s\n' "$ISO_PATH" printf 'set system image default-boot %s\n' "$ISO_NAME" printf 'commit\n' printf 'save\n' fi log "All done." exit 0