#!/usr/bin/env bash # Proxframe installer (ADR-0032). Paste in a Proxmox VE node's Shell: # curl -fsSL https://get.proxframe.app | bash # To update a hub without the in-app updater, or to remove it: # curl -fsSL https://get.proxframe.app | bash -s -- --update (or --remove) # # It downloads the newest Proxframe release with your read-only key, # checks its exact size and SHA-256 against the release's own manifest, # then runs the package's setup, which shows its plan and asks before # changing anything. This file is public; it contains no product code. set -euo pipefail REPO="baki2001/Proxmox-alternative" API="https://api.github.com/repos/${REPO}" KEY_URL="https://github.com/settings/personal-access-tokens/new" # Where setup expects the package, the same place a manual upload goes. PACKAGE="/var/lib/vz/template/cache/proxframe-hub-test.tar.gz" TTY="${PROXFRAME_TTY:-/dev/tty}" # test hook (lab/test/install.test.sh) WORK="" say() { printf '%s\n' "$*"; } die() { printf '\nStopped: %s\n' "$*" >&2; exit 1; } cleanup() { if [[ -n "$WORK" ]]; then rm -rf "$WORK"; fi; } # holds the key # curl with the key read from a file, so it is never on a command line. # Only https; on a redirect to GitHub's file host curl drops the key. github() { curl --fail --silent --show-error --location --proto '=https' --tlsv1.2 \ --max-time 600 -H @"$WORK/auth" -H "X-GitHub-Api-Version: 2022-11-28" \ -H "User-Agent: proxframe-installer" "$@" } ask_key() { local key="" cat < Only select repositories -> Proxmox-alternative. Permissions -> Repository permissions -> Contents -> Read-only. The same key lets the hub install new versions by itself later. KEY printf 'Key (hidden while you type): ' { IFS= read -rs key <"$TTY"; } 2>/dev/null \ || { printf '\n'; die "could not ask for the key. Run this from the node's Shell in the Proxmox web UI."; } printf '\n' key="${key//[[:space:]]/}" [[ "$key" =~ ^[A-Za-z0-9_]{1,255}$ ]] \ || die "that doesn't look like a GitHub key (they start with github_pat_). Nothing was changed." (umask 077 printf '%s\n' "$key" >"$WORK/key" printf 'Authorization: Bearer %s\n' "$key" >"$WORK/auth") unset key } # Reads one asset's id and size from the release JSON: " ". asset() { # asset < release.json perl -MJSON::PP -e ' my $r = decode_json(do { local $/; }); for my $a (@{ $r->{assets} // [] }) { if ($a->{name} eq $ARGV[0]) { print "$a->{id} $a->{size}\n"; exit 0 } } exit 1' "$1" } field() { # field < release.txt; a key given twice counts as missing awk -F= -v k="$1" '$1 == k { n++; sub(/^[^=]*=/, ""); v = $0 } END { if (n != 1) exit 1; print v }' } # " " for one asset, both plain numbers; nothing else reaches the shell. asset_of() { # asset_of ; sets id and size read -r id size < <(asset "$1" <"$WORK/release.json") \ && [[ "$id" =~ ^[0-9]{1,20}$ && "$size" =~ ^[0-9]{1,15}$ ]] } main() { [[ "$(id -u)" == 0 ]] || die "this needs to run as root. Use the node's Shell in the Proxmox web UI (it logs you in as root)." command -v pveversion >/dev/null && command -v pct >/dev/null \ || die "this must run on a Proxmox VE node (pveversion not found)." [[ "$(dpkg --print-architecture)" == amd64 ]] \ || die "Proxframe runs only on Intel/AMD (amd64) nodes for now. Nothing was changed." WORK="$(mktemp -d)" chmod 700 "$WORK" trap cleanup EXIT ask_key say "" say "Finding the newest version..." local status status="$(github -o "$WORK/release.json" -w '%{http_code}' \ -H "Accept: application/vnd.github+json" "${API}/releases/latest" 2>/dev/null)" || true case "$status" in 200) ;; 401|403|404) die "GitHub didn't accept the key, or it can't read Proxmox-alternative. Make a new one (see above) and try again. Nothing was changed." ;; *) die "GitHub couldn't be reached (answer: ${status:-none}). Check this node's internet connection and try again. Nothing was changed." ;; esac local tag tag="$(perl -MJSON::PP -e 'my $r = decode_json(do { local $/; }); print $r->{tag_name} // ""' <"$WORK/release.json")" \ || die "GitHub's answer couldn't be read. Try again later. Nothing was changed." # Checked before it is shown or used anywhere. [[ "$tag" =~ ^v[0-9]{1,9}\.[0-9]{1,9}\.[0-9]{1,9}$ ]] \ || die "the newest release has an unexpected name. Nothing was changed." local id size asset_of release.txt \ || die "the newest release (${tag}) has no release.txt. Nothing was changed." [[ "$size" -le 16384 ]] || die "the newest release's release.txt is too big. Nothing was changed." github --max-filesize 16384 -H "Accept: application/octet-stream" -o "$WORK/release.txt" "${API}/releases/assets/${id}" \ || die "release.txt couldn't be downloaded. Try again. Nothing was changed." # The checks the hub's updater makes on what it installs (crates/updater, # manifest.rs), except min_updater_version: setup brings its own updater. local version name want_size want_sum ! LC_ALL=C grep -q '[[:cntrl:]]' "$WORK/release.txt" \ || die "release.txt is malformed. Nothing was changed." [[ "$(field product <"$WORK/release.txt")" == stillfield-hub ]] || die "the newest release isn't a Proxframe hub. Nothing was changed." [[ "$(field arch <"$WORK/release.txt")" == amd64 ]] || die "the newest release isn't for Intel/AMD. Nothing was changed." version="$(field version <"$WORK/release.txt")" || die "release.txt has no version. Nothing was changed." [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ && "v${version}" == "$tag" ]] \ || die "release.txt says another version than the release (${tag}). Nothing was changed." name="$(field package_name <"$WORK/release.txt")" || die "release.txt names no package. Nothing was changed." [[ "$name" =~ ^[A-Za-z0-9_-][A-Za-z0-9._-]{0,99}$ ]] || die "release.txt names an odd package. Nothing was changed." want_size="$(field package_size <"$WORK/release.txt")" || die "release.txt has no package size. Nothing was changed." want_sum="$(field package_sha256 <"$WORK/release.txt" | tr 'A-F' 'a-f')" || die "release.txt has no checksum. Nothing was changed." [[ "$want_size" =~ ^[1-9][0-9]{0,14}$ && "$want_sum" =~ ^[0-9a-f]{64}$ ]] || die "release.txt is malformed. Nothing was changed." asset_of "$name" \ || die "the newest release has no ${name}. Nothing was changed." [[ "$size" == "$want_size" ]] || die "${name} isn't the size release.txt says. Nothing was changed." say "Downloading Proxframe ${version}..." github --max-filesize "$want_size" -H "Accept: application/octet-stream" -o "$WORK/package.tar.gz" "${API}/releases/assets/${id}" \ || die "the download didn't finish. Try again. Nothing was changed." [[ "$(stat -c %s "$WORK/package.tar.gz")" == "$want_size" ]] \ || die "the download isn't the size it should be. Try again. Nothing was changed." [[ "$(sha256sum "$WORK/package.tar.gz" | cut -d' ' -f1)" == "$want_sum" ]] \ || die "the download's SHA-256 doesn't match release.txt, so it was not used. Nothing was changed." say "Checked: ${name}, SHA-256 ${want_sum}" # Kept where a manual upload goes, so setup's remove and repair lines work. install -D -m 644 "$WORK/package.tar.gz" "$PACKAGE" tar -xzOf "$WORK/package.tar.gz" setup.sh >"$WORK/setup.sh" \ || die "the package has no setup.sh. Nothing was changed." PROXFRAME_KEY_FILE="$WORK/key" bash "$WORK/setup.sh" --package "$PACKAGE" "$@"