2025-02-09 14:29:28 +01:00
#!/bin/bash
# ==========================================================
2025-09-10 18:47:55 +02:00
# ProxMenux - A menu-driven script for Proxmox VE management
2025-02-09 14:29:28 +01:00
# ==========================================================
# Author : MacRimi
# Copyright : (c) 2024 MacRimi
2025-11-01 00:10:46 +01:00
# License : (CC BY-NC 4.0) (https://github.com/MacRimi/ProxMenux/blob/main/LICENSE)
2025-11-14 18:54:32 +01:00
# Version : 1.2
# Last Updated: 14/11/2025
2025-02-09 14:29:28 +01:00
# ==========================================================
# Description:
2025-06-06 17:29:06 +02:00
# This script provides a simple and efficient way to access and execute Proxmox VE scripts
2025-02-09 14:29:28 +01:00
# 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 ============================================
2025-11-03 01:06:04 +00:00
LOCAL_SCRIPTS = "/usr/local/share/proxmenux/scripts"
2025-02-09 14:29:28 +01:00
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
# ==========================================================
2025-06-06 17:29:06 +02:00
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
2025-11-14 18:54:32 +01:00
for id in $( echo " $CACHE_JSON " | jq -r '
group_by( .slug) | map( .[ 0] ) [ ] | .categories[ ] ' ) ; do
2025-06-06 17:29:06 +02:00
( ( 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
}
2025-02-09 14:29:28 +01:00
2025-04-27 19:05:36 +02:00
download_script( ) {
2025-06-06 17:29:06 +02:00
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
}
2025-04-27 19:05:36 +02:00
2025-06-06 17:29:06 +02:00
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 "
2025-04-27 19:05:36 +02:00
fi
2025-06-06 17:29:06 +02:00
fi
echo " $credentials_info "
2025-04-27 19:05:36 +02:00
}
2025-06-06 17:29:06 +02:00
run_script_by_slug( ) {
local slug = " $1 "
2025-11-14 18:54:32 +01:00
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
2025-06-06 17:29:06 +02:00
decode( ) {
echo " $1 " | base64 --decode | jq -r " $2 "
}
2025-11-14 18:54:32 +01:00
local first = " ${ script_infos [0] } "
local name desc notes
name = $( decode " $first " ".name" )
desc = $( decode " $first " ".desc" )
notes = $( decode " $first " ".notes | join(\"\n\")" )
2025-06-06 17:29:06 +02:00
local notes_dialog = ""
if [ [ -n " $notes " ] ] ; then
while IFS = read -r line; do
2025-11-14 18:54:32 +01:00
[ [ -z " $line " ] ] && continue
2025-06-06 17:29:06 +02:00
notes_dialog += " • $line \n "
done <<< " $notes "
2025-11-14 18:54:32 +01:00
notes_dialog = " ${ notes_dialog % \\ n } "
2025-06-06 17:29:06 +02:00
fi
local credentials
2025-11-14 18:54:32 +01:00
credentials = $( format_credentials " $first " )
2025-02-09 14:29:28 +01:00
2025-06-06 17:29:06 +02:00
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 "
2025-11-14 18:54:32 +01:00
# 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
2025-02-09 14:29:28 +01:00
2025-11-14 18:54:32 +01:00
( ( idx++) )
done
2025-06-06 17:29:06 +02:00
2025-11-14 18:54:32 +01:00
local choice
choice = $( dialog --clear --colors --backtitle "ProxMenux" \
--title " $name " \
--menu " $msg " 28 80 6 \
" ${ MENU_OPTS [@] } " 3>& 1 1>& 2 2>& 3)
2025-06-06 17:29:06 +02:00
2025-11-14 18:54:32 +01:00
if [ [ $? -ne 0 || -z " $choice " ] ] ; then
RETURN_TO_MAIN = false
return
fi
2025-06-06 17:29:06 +02:00
2025-11-14 18:54:32 +01:00
local sel_idx sel_src
IFS = "_" read -r sel_idx sel_src <<< " $choice "
2025-06-06 17:29:06 +02:00
2025-11-14 18:54:32 +01:00
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" )
2025-06-06 17:29:06 +02:00
2025-11-14 18:54:32 +01:00
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
2025-06-06 17:29:06 +02:00
fi
2025-11-14 18:54:32 +01:00
fi
echo
echo
if [ [ -n " $desc " || -n " $notes " || -n " $credentials " ] ] ; then
echo -e " $TAB \e[1;36mScript Information:\e[0m "
2025-06-06 17:29:06 +02:00
2025-11-14 18:54:32 +01:00
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
2025-06-06 17:29:06 +02:00
fi
2025-11-14 18:54:32 +01:00
msg_success "Press Enter to return to the main menu..."
read -r
RETURN_TO_MAIN = true
}
2025-06-06 17:29:06 +02:00
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
2025-11-14 18:54:32 +01:00
count = $( echo " $filtered_json " | jq 'group_by(.slug) | length' )
2025-06-06 17:29:06 +02:00
if [ [ $count -eq 0 ] ] ; then
dialog --msgbox " No scripts found for: ' $search_term '\n\nTry a different search term. " 8 50
continue
fi
2025-02-09 14:29:28 +01:00
2025-02-09 15:56:14 +01:00
while true; do
2025-06-06 17:29:06 +02:00
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 '
2025-11-14 18:54:32 +01:00
group_by( .slug) | map( .[ 0] ) | sort_by( .name) [ ] | [ .slug, .name, .type] | @tsv' )
2025-06-06 17:29:06 +02:00
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 "
2025-11-14 18:54:32 +01:00
count = $( echo " $filtered_json " | jq 'group_by(.slug) | length' )
2025-06-06 17:29:06 +02:00
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
2025-02-09 14:29:28 +01:00
done
2025-06-06 17:29:06 +02:00
done
2025-02-09 14:29:28 +01:00
}
2025-06-06 17:29:06 +02:00
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
2025-02-25 19:14:55 +01:00
2025-06-06 17:29:06 +02:00
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) || {
2025-11-14 18:54:32 +01:00
dialog --clear --title "ProxMenux" \
2025-06-06 17:29:06 +02:00
--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
2025-11-03 12:32:19 +00:00
exec bash " $LOCAL_SCRIPTS /menus/main_menu.sh "
2025-06-06 17:29:06 +02:00
}
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++) )
2025-11-14 18:54:32 +01:00
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' )
2025-06-06 17:29:06 +02:00
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
2025-06-10 14:34:30 +02:00
done