Files
ProxMenux/scripts/menus/menu_Helper_Scripts.sh
2025-11-14 18:54:32 +01:00

384 lines
12 KiB
Bash

#!/bin/bash
# ==========================================================
# ProxMenux - A menu-driven script for Proxmox VE management
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
# License : (CC BY-NC 4.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE)
# Version : 1.2
# Last Updated: 14/11/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 ============================================
LOCAL_SCRIPTS="/usr/local/share/proxmenux/scripts"
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 '
group_by(.slug) | map(.[0])[] | .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 -a script_infos
mapfile -t script_infos < <(echo "$CACHE_JSON" | jq -r --arg slug "$slug" '.[] | select(.slug == $slug) | @base64')
if [[ ${#script_infos[@]} -eq 0 ]]; then
dialog --title "Helper Scripts" --msgbox "Error: No script data found for slug: $slug" 8 60
return
fi
decode() {
echo "$1" | base64 --decode | jq -r "$2"
}
local first="${script_infos[0]}"
local name desc notes
name=$(decode "$first" ".name")
desc=$(decode "$first" ".desc")
notes=$(decode "$first" ".notes | join(\"\n\")")
local notes_dialog=""
if [[ -n "$notes" ]]; then
while IFS= read -r line; do
[[ -z "$line" ]] && continue
notes_dialog+="$line\n"
done <<< "$notes"
notes_dialog="${notes_dialog%\\n}"
fi
local credentials
credentials=$(format_credentials "$first")
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"
# Add separator before menu options
msg+="\n\n$(translate "Choose how to run the script:"):"
declare -a MENU_OPTS=()
local idx=0
for s in "${script_infos[@]}"; do
local os script_url script_url_mirror script_name
os=$(decode "$s" ".os // empty")
[[ -z "$os" ]] && os="$(translate "default")"
script_name=$(decode "$s" ".name")
script_url=$(decode "$s" ".script_url")
script_url_mirror=$(decode "$s" ".script_url_mirror // empty")
MENU_OPTS+=("${idx}_GH" "$os | $script_name | GitHub")
if [[ -n "$script_url_mirror" ]]; then
MENU_OPTS+=("${idx}_MR" "$os | $script_name | Mirror")
fi
((idx++))
done
local choice
choice=$(dialog --clear --colors --backtitle "ProxMenux" \
--title "$name" \
--menu "$msg" 28 80 6 \
"${MENU_OPTS[@]}" 3>&1 1>&2 2>&3)
if [[ $? -ne 0 || -z "$choice" ]]; then
RETURN_TO_MAIN=false
return
fi
local sel_idx sel_src
IFS="_" read -r sel_idx sel_src <<< "$choice"
local selected="${script_infos[$sel_idx]}"
local gh_url mirror_url
gh_url=$(decode "$selected" ".script_url")
mirror_url=$(decode "$selected" ".script_url_mirror // empty")
if [[ "$sel_src" == "GH" ]]; then
download_script "$gh_url"
elif [[ "$sel_src" == "MR" ]]; then
if [[ -n "$mirror_url" ]]; then
download_script "$mirror_url"
else
dialog --title "Helper Scripts" --msgbox "$(translate "Mirror URL not available for this script.")" 8 60
RETURN_TO_MAIN=false
return
fi
fi
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
}
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 'group_by(.slug) | 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 '
group_by(.slug) | map(.[0]) | 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 'group_by(.slug) | 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 --clear --title "ProxMenux" \
--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
exec bash "$LOCAL_SCRIPTS/menus/main_menu.sh"
}
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}
]
| group_by(.slug)
| map(.[0])
| 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