#!/bin/bash # ========================================================== # ProxMenu - A menu-driven script for Proxmox VE management # ========================================================== # Author : MacRimi # Copyright : (c) 2024 MacRimi # License : MIT (https://raw.githubusercontent.com/MacRimi/ProxMenux/main/LICENSE) # Version : 1.1 # Last Updated: 04/06/2025 # ========================================================== # Description: # This script provides a simple and efficient way to access and execute Proxmox VE scripts # from the Community Scripts project (https://community-scripts.github.io/ProxmoxVE/). # # It serves as a convenient tool to run key automation scripts that simplify system management, # continuing the great work and legacy of tteck in making Proxmox VE more accessible. # A streamlined solution for executing must-have tools in Proxmox VE. # ========================================================== # Configuration ============================================ REPO_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/main" BASE_DIR="/usr/local/share/proxmenux" UTILS_FILE="$BASE_DIR/utils.sh" VENV_PATH="/opt/googletrans-env" if [[ -f "$UTILS_FILE" ]]; then source "$UTILS_FILE" fi load_language initialize_cache # ========================================================== HELPERS_JSON_URL="https://raw.githubusercontent.com/MacRimi/ProxMenux/refs/heads/main/json/helpers_cache.json" METADATA_URL="https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/frontend/public/json/metadata.json" for cmd in curl jq dialog; do if ! command -v "$cmd" >/dev/null; then echo "Missing required command: $cmd" exit 1 fi done CACHE_JSON=$(curl -s "$HELPERS_JSON_URL") META_JSON=$(curl -s "$METADATA_URL") declare -A CATEGORY_NAMES while read -r id name; do CATEGORY_NAMES[$id]="$name" done < <(echo "$META_JSON" | jq -r '.categories[] | "\(.id)\t\(.name)"') declare -A CATEGORY_COUNT for id in $(echo "$CACHE_JSON" | jq -r '.[].categories[]'); do ((CATEGORY_COUNT[$id]++)) done get_type_label() { local type="$1" case "$type" in ct) echo $'\Z1LXC\Zn' ;; vm) echo $'\Z4VM\Zn' ;; pve) echo $'\Z3PVE\Zn' ;; addon) echo $'\Z2ADDON\Zn' ;; *) echo $'\Z7GEN\Zn' ;; esac } download_script() { local url="$1" local fallback_pve="${url/misc\/tools\/pve}" local fallback_addon="${url/misc\/tools\/addon}" local fallback_copydata="${url/misc\/tools\/copy-data}" if curl --silent --head --fail "$url" >/dev/null; then bash <(curl -s "$url") elif curl --silent --head --fail "$fallback_pve" >/dev/null; then bash <(curl -s "$fallback_pve") elif curl --silent --head --fail "$fallback_addon" >/dev/null; then bash <(curl -s "$fallback_addon") elif curl --silent --head --fail "$fallback_copydata" >/dev/null; then bash <(curl -s "$fallback_copydata") else dialog --title "Helper Scripts" --msgbox "Error: Failed to download the script." 12 70 fi } RETURN_TO_MAIN=false format_credentials() { local script_info="$1" local credentials_info="" local has_credentials has_credentials=$(echo "$script_info" | base64 --decode | jq -r 'has("default_credentials")') if [[ "$has_credentials" == "true" ]]; then local username password username=$(echo "$script_info" | base64 --decode | jq -r '.default_credentials.username // empty') password=$(echo "$script_info" | base64 --decode | jq -r '.default_credentials.password // empty') if [[ -n "$username" && -n "$password" ]]; then credentials_info="Username: $username | Password: $password" elif [[ -n "$username" ]]; then credentials_info="Username: $username" elif [[ -n "$password" ]]; then credentials_info="Password: $password" fi fi echo "$credentials_info" } run_script_by_slug() { local slug="$1" local script_info script_info=$(echo "$CACHE_JSON" | jq -r --arg slug "$slug" '.[] | select(.slug == $slug) | @base64') decode() { echo "$1" | base64 --decode | jq -r "$2" } local name desc script_url notes name=$(decode "$script_info" ".name") desc=$(decode "$script_info" ".desc") script_url=$(decode "$script_info" ".script_url") notes=$(decode "$script_info" ".notes | join(\"\n\")") local notes_dialog="" if [[ -n "$notes" ]]; then while IFS= read -r line; do notes_dialog+="• $line\n" done <<< "$notes" notes_dialog="${notes_dialog%\\n}" fi local credentials credentials=$(format_credentials "$script_info") local msg="\Zb\Z4Descripción:\Zn\n$desc" [[ -n "$notes_dialog" ]] && msg+="\n\n\Zb\Z4Notes:\Zn\n$notes_dialog" [[ -n "$credentials" ]] && msg+="\n\n\Zb\Z4Default Credentials:\Zn\n$credentials" dialog --clear --colors --backtitle "ProxMenux" --title "$name" --yesno "$msg\n\nExecute this script?" 22 85 if [[ $? -eq 0 ]]; then download_script "$script_url" echo echo if [[ -n "$desc" || -n "$notes" || -n "$credentials" ]]; then echo -e "$TAB\e[1;36mScript Information:\e[0m" if [[ -n "$notes" ]]; then echo -e "$TAB\e[1;33mNotes:\e[0m" while IFS= read -r line; do [[ -z "$line" ]] && continue echo -e "$TAB• $line" done <<< "$notes" echo fi if [[ -n "$credentials" ]]; then echo -e "$TAB\e[1;32mDefault Credentials:\e[0m" echo "$TAB$credentials" echo fi fi msg_success "Press Enter to return to the main menu..." read -r RETURN_TO_MAIN=true fi } search_and_filter_scripts() { local search_term="" while true; do search_term=$(dialog --inputbox "Enter search term (leave empty to show all scripts):" \ 8 65 "$search_term" 3>&1 1>&2 2>&3) [[ $? -ne 0 ]] && return local filtered_json if [[ -z "$search_term" ]]; then filtered_json="$CACHE_JSON" else local search_lower search_lower=$(echo "$search_term" | tr '[:upper:]' '[:lower:]') filtered_json=$(echo "$CACHE_JSON" | jq --arg term "$search_lower" ' [.[] | select( (.name | ascii_downcase | contains($term)) or (.desc | ascii_downcase | contains($term)) )]') fi local count count=$(echo "$filtered_json" | jq length) if [[ $count -eq 0 ]]; then dialog --msgbox "No scripts found for: '$search_term'\n\nTry a different search term." 8 50 continue fi while true; do declare -A index_to_slug local menu_items=() local i=1 while IFS=$'\t' read -r slug name type; do index_to_slug[$i]="$slug" local label label=$(get_type_label "$type") local padded_name padded_name=$(printf "%-42s" "$name") local entry="$padded_name $label" menu_items+=("$i" "$entry") ((i++)) done < <(echo "$filtered_json" | jq -r ' sort_by(.name)[] | [.slug, .name, .type] | @tsv') menu_items+=("" "") menu_items+=("new_search" "New Search") menu_items+=("show_all" "Show All Scripts") local title="Search Results" if [[ -n "$search_term" ]]; then title="Search Results for: '$search_term' ($count found)" else title="All Available Scripts ($count total)" fi local selected selected=$(dialog --colors --backtitle "ProxMenux" \ --title "$title" \ --menu "Select a script or action:" \ 22 75 15 "${menu_items[@]}" 3>&1 1>&2 2>&3) if [[ $? -ne 0 ]]; then return fi case "$selected" in "new_search") break ;; "show_all") search_term="" filtered_json="$CACHE_JSON" count=$(echo "$filtered_json" | jq length) continue ;; "back"|"") return ;; *) if [[ -n "${index_to_slug[$selected]}" ]]; then run_script_by_slug "${index_to_slug[$selected]}" [[ "$RETURN_TO_MAIN" == true ]] && { RETURN_TO_MAIN=false; return; } fi ;; esac done done } while true; do MENU_ITEMS=() MENU_ITEMS+=("search" "Search/Filter Scripts") MENU_ITEMS+=("" "") for id in $(printf "%s\n" "${!CATEGORY_COUNT[@]}" | sort -n); do name="${CATEGORY_NAMES[$id]:-Category $id}" count="${CATEGORY_COUNT[$id]}" padded_name=$(printf "%-35s" "$name") padded_count=$(printf "(%2d)" "$count") MENU_ITEMS+=("$id" "$padded_name $padded_count") done SELECTED=$(dialog --backtitle "ProxMenux" --title "Proxmox VE Helper-Scripts" --menu \ "Select a category or search for scripts:" 20 70 14 \ "${MENU_ITEMS[@]}" 3>&1 1>&2 2>&3) || { dialog --title "Proxmox VE Helper-Scripts" \ --msgbox "\n\n$(translate "Visit the website to discover more scripts, stay updated with the latest updates, and support the project:")\n\nhttps://community-scripts.github.io/ProxmoxVE" 15 70 clear break } if [[ "$SELECTED" == "search" ]]; then search_and_filter_scripts continue fi while true; do declare -A INDEX_TO_SLUG SCRIPTS=() i=1 while IFS=$'\t' read -r slug name type; do INDEX_TO_SLUG[$i]="$slug" label=$(get_type_label "$type") padded_name=$(printf "%-42s" "$name") entry="$padded_name $label" SCRIPTS+=("$i" "$entry") ((i++)) done < <(echo "$CACHE_JSON" | jq -r --argjson id "$SELECTED" \ '[.[] | select(.categories | index($id)) | {slug, name, type}] | sort_by(.name)[] | [.slug, .name, .type] | @tsv') SCRIPT_INDEX=$(dialog --colors --backtitle "ProxMenux" --title "Scripts in ${CATEGORY_NAMES[$SELECTED]}" --menu \ "Choose a script to execute:" 20 70 14 \ "${SCRIPTS[@]}" 3>&1 1>&2 2>&3) || break SCRIPT_SELECTED="${INDEX_TO_SLUG[$SCRIPT_INDEX]}" run_script_by_slug "$SCRIPT_SELECTED" [[ "$RETURN_TO_MAIN" == true ]] && { RETURN_TO_MAIN=false; break; } done done