Skip to content

[UX/UI] Add interactive checklist shell script modules for package management #530

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 29 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
ac6ff62
new file: tools/modules/functions/interface_checklist.sh
Tearran Apr 15, 2025
b96b968
renamed
Tearran Apr 15, 2025
6e1c60c
Update tools/modules/software/module_aptwizard.sh
Tearran Apr 15, 2025
cdd0368
Update tools/modules/software/module_aptwizard.sh
Tearran Apr 15, 2025
f081e79
fix typo
Tearran Apr 15, 2025
c0d5c33
test remove all but mising pack and exspected error
Tearran Apr 15, 2025
a6fffff
typo fix
Tearran Apr 15, 2025
c7ef88f
fix typo
Tearran Apr 15, 2025
4a43399
templaing _checklist_proftpd
Tearran Apr 15, 2025
b94d03c
replace dpkg i with ai sugeted
Tearran Apr 15, 2025
1c65e1b
nitpick fix and templating
Tearran Apr 15, 2025
0079578
comment Scaffold
Tearran Apr 15, 2025
dee6505
oops
Tearran Apr 15, 2025
b359ab9
oops webmin
Tearran Apr 15, 2025
be58e1f
fix quote
Tearran Apr 15, 2025
d4adb83
king typo fix spam
Tearran Apr 15, 2025
92b43e1
nitpick fix moulare replace array with read
Tearran Apr 15, 2025
afce610
reuse
Tearran Apr 15, 2025
7a5446a
Update tools/modules/software/module_aptwizard.sh
Tearran Apr 15, 2025
bdb1dbb
typo
Tearran Apr 15, 2025
28db510
rename
Tearran Apr 15, 2025
64e62d1
Add module_aptwizard to json
Tearran Apr 15, 2025
1de1214
style
Tearran Apr 15, 2025
5306c0e
Update tools/modules/functions/interface_checklist.sh
Tearran Apr 15, 2025
e8c6123
📝 CodeRabbit Chat: Add test_interface_checklist.sh to tools/modules/t…
coderabbitai[bot] Apr 16, 2025
d6f62c8
Removed test
Tearran Apr 16, 2025
736d6c1
Merge branch 'main' into web-browsers
Tearran Apr 17, 2025
4c06e60
modified: tools/modules/software/module_aptwizard.sh
Tearran Apr 17, 2025
7175468
Merge branch 'main' into web-browsers
Tearran Apr 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions tests/aptwizard.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ENABLED=true
RELEASE="bookworm:jammy:noble"
TESTNAME="AptWizard"

testcase() {(
set -e
./bin/armbian-config --api module_aptwizard help
)}
10 changes: 10 additions & 0 deletions tools/json/config.software.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@
"id": "Software",
"description": "Run/Install 3rd party applications",
"sub": [
{
"id": "AptWizard",
"description": "Manage popular Debian packages with AptWizard",
"command": [
"see_menu module_aptwizard"
],
"status": "",
"author": "Github Username",
"condition": ""
},
{
"id": "WebHosting",
"description": "Web server, LEMP, reverse proxy, Let's Encrypt SSL",
Expand Down
118 changes: 118 additions & 0 deletions tools/modules/functions/interface_checklist.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
module_options+=(
["interface_checklist,author"]="@Tearran"
["interface_checklist,maintainer"]="@Tearran"
["interface_checklist,feature"]="interface_checklist"
["interface_checklist,example"]="interface_checklist <title> <prompt> <options_array>"
["interface_checklist,desc"]="Reusable helper function to display a checklist using whiptail, dialog, or read."
["interface_checklist,status"]="Active"
["interface_checklist,group"]="Helpers"
["interface_checklist,arch"]="arm64"
)
# Helper function to display a checklist using whiptail/dialog/read
function interface_checklist() {
local title="$1"
local prompt="$2"
local -n options_array="$3" # Use a nameref to pass the array by reference
local dialog_height=20
local dialog_width=78
local menu_height=10

# Prepare options for the checklist
local checklist_items=()
for ((i = 0; i < ${#options_array[@]}; i += 3)); do
checklist_items+=("${options_array[i]}" "${options_array[i+1]}" "${options_array[i+2]}")
done

# Display the checklist based on the dialog tool
local selected_items=""
case $DIALOG in
"whiptail")
selected_items=$(whiptail --title "$title" --checklist \
"$prompt" $dialog_height $dialog_width $menu_height \
"${checklist_items[@]}" 3>&1 1>&2 2>&3)
;;
"dialog")
selected_items=$(dialog --title "$title" --checklist \
"$prompt" $dialog_height $dialog_width $menu_height \
"${checklist_items[@]}" 2>&1 >/dev/tty)
;;
"read")
echo "$title"
echo "$prompt"
for ((i = 0; i < ${#options_array[@]}; i += 3)); do
echo "$((i / 3 + 1)). ${options_array[i]} - ${options_array[i+1]} (Default: ${options_array[i+2]})"
done
echo "Enter the numbers of the items you want to select, separated by spaces:"
read -r selected_indexes
selected_items=""
for index in $selected_indexes; do
selected_items+=" ${options_array[((index - 1) * 3)]}"
done
;;
esac

# Return the selected items
if [[ -z "$selected_items" ]]; then
echo "Checklist canceled."
return 1
fi

echo "$selected_items"
}

module_options+=(
["process_package_selection,author"]="@Tearran"
["process_package_selection,maintainer"]="@Tearran"
["process_package_selection,feature"]="process_package_selection"
["process_package_selection,example"]="process_package_selection <title> <prompt> <checklist_options_array>"
["process_package_selection,desc"]="Reusable helper function to process user-selected packages for installation or removal."
["process_package_selection,status"]="Active"
["process_package_selection,group"]="Helpers"
["process_package_selection,arch"]="x86-64 arm64 armhf"
)
#
function process_package_selection() {
local title="$1"
local prompt="$2"
local -a checklist_options=("${!3}") # Accept checklist array as reference

# Display checklist to user and get selected packages
local selected_packages
selected_packages=$(interface_checklist "$title Management" "$prompt" checklist_options)

# Check if user canceled or made no selection
if [[ $? -ne 0 ]]; then
echo "No changes made."
return 1
fi

# Processing all packages from the checklist
echo "Processing package selections..."
for ((i = 0; i < ${#checklist_options[@]}; i += 3)); do
local package="${checklist_options[i]}"
local current_state="${checklist_options[i+2]}" # Current state in checklist (ON/OFF)
local is_selected="OFF" # Default to OFF

# Check if the package is in the selected list
if [[ "$selected_packages" == *"$package"* ]]; then
is_selected="ON"
fi

# Compare current state with selected state and act accordingly
if [[ "$is_selected" == "ON" && "$current_state" == "OFF" ]]; then
# Package is selected but not installed, install it
echo "Installing $package..."
if ! pkg_install "$package"; then
echo "Failed to install $package." >&2
fi
elif [[ "$is_selected" == "OFF" && "$current_state" == "ON" ]]; then
# Package is deselected but installed, remove it
echo "Removing $package..."
if ! pkg_remove "$package"; then
echo "Failed to remove $package." >&2
fi
fi
done

echo "Package management complete."
}
248 changes: 248 additions & 0 deletions tools/modules/software/module_aptwizard.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
module_options+=(
["_checklist_proftpd,author"]="@Tearran"
["_checklist_proftpd,maintainer"]="@Tearran"
["_checklist_proftpd,feature"]="_checklist_proftpd"
["_checklist_proftpd,example"]=""
["_checklist_proftpd,desc"]="Dynamic ProFTPD package management with install/remove toggle."
["_checklist_proftpd,status"]="Active"
["_checklist_proftpd,group"]="Internet"
["_checklist_proftpd,arch"]="x86-64 arm64 armhf"
)
# Scaffold for an app that has multiple candidates, such as ProFTPD and modules.
function _checklist_proftpd() {
local title="proftpd"

# Convert the example string to an array
local commands
IFS=' ' read -r -a commands <<< "${module_options["_checklist_proftpd,example"]}"

## Dynamically manage ProFTPD packages
echo "Fetching $title-related packages..."
local package_list
# get a list of all packages
package_list=$(apt-cache search "$title" | awk '{print $1}')
if [[ -z "$package_list" ]]; then
echo "No $title-related packages found."
return 1
fi

# Prepare checklist options dynamically
local checklist_options=()
for package in $package_list; do
if dpkg-query -W -f='${Status}' "$package" 2>/dev/null | grep -q "^install ok installed$"; then
checklist_options+=("$package" "Installed" "ON")
else
checklist_options+=("$package" "Not installed" "OFF")
fi
done

process_package_selection "$title" "Select $title packages to install/remove:" checklist_options[@]
}


module_options+=(
["_checklist_browsers,author"]="@Tearran"
["_checklist_browsers,maintainer"]="@Tearran"
["_checklist_browsers,feature"]="_checklist_browsers"
["_checklist_browsers,example"]=""
["_checklist_browsers,desc"]="Browser installation and management (Firefox-ESR and Chromium and more)."
["_checklist_browsers,status"]="Active"
["_checklist_browsers,group"]="Internet"
["_checklist_browsers,arch"]="x86-64 arm64 armhf"
)
# Scaffold for app with specific single or dummy candidates.
function _checklist_browsers() {
local title="Browsers"

# List of base browser packages to manage
#
local browser_packages=(
"firefox-esr"
"chromium"
"lynx"
"google-chrome"
)

# Manage browser installation/removal
echo "Fetching browser package details..."

# Prepare checklist options dynamically with descriptions
local checklist_options=()
for base_package in "${browser_packages[@]}"; do
# Find the main package and exclude auxiliary or irrelevant ones
local main_package
main_package=$(apt-cache search "^${base_package}$" | awk -F' - ' '{print $1 " - " $2}')

# Check if the main package exists and fetch its description
if [[ -n "$main_package" ]]; then
local package_name package_description
package_name=$(echo "$main_package" | awk -F' - ' '{print $1}')
package_description=$(echo "$main_package" | awk -F' - ' '{print $2}')

# Check if the package is installed and set its state
if dpkg-query -W -f='${Status}' "$package_name" 2>/dev/null | grep -q "^install ok installed$"; then
checklist_options+=("$package_name" "$package_description" "ON")
else
checklist_options+=("$package_name" "$package_description" "OFF")
fi
fi
done
if [[ ${#checklist_options[@]} -eq 0 ]]; then
echo "No $title packages found."
return 1
fi

process_package_selection "$title" "Select packages to install/remove:" checklist_options[@]
}

module_options+=(
["_checklist_editors,author"]="@Tearran"
["_checklist_editors,maintainer"]="@Tearran"
["_checklist_editors,feature"]="_checklist_editors"
["_checklist_editors,example"]="nano code codium notepadqq"
["_checklist_editors,desc"]="Editor installation and management (codium notepadqq and more)."
["_checklist_editors,status"]="Active"
["_checklist_editors,group"]="Internet"
["_checklist_editors,arch"]="x86-64 arm64 armhf"
)

# Scaffold for app with specific single or dummy candidates.
function _checklist_editors() {
local title="Editors"
local self="${module_options["_checklist_editors,feature"]}"
local _packages
IFS=' ' read -r -a _packages <<< "${module_options["$self,example"]}"

# Manage editor installation/removal
echo "Fetching $title package details..."

# Prepare checklist options dynamically with descriptions
local checklist_options=()
for base_package in "${_packages[@]}"; do
# Find the main package and exclude auxiliary or irrelevant ones
local main_package
main_package=$(apt-cache search "^${base_package}$" | awk -F' - ' '{print $1 " - " $2}')

# Check if the main package exists and fetch its description
if [[ -n "$main_package" ]]; then
local package_name package_description
package_name=$(echo "$main_package" | awk -F' - ' '{print $1}')
package_description=$(echo "$main_package" | awk -F' - ' '{print $2}')

# Check if the package is installed and set its state
if dpkg-query -W -f='${Status}' "$package_name" 2>/dev/null | grep -q "^install ok installed$"; then
checklist_options+=("$package_name" "$package_description" "ON")
else
checklist_options+=("$package_name" "$package_description" "OFF")
fi
fi
done
if [[ ${#checklist_options[@]} -eq 0 ]]; then
echo "No $title packages found."
return 1
fi

process_package_selection "$title" "Select packages to install/remove:" checklist_options[@]
}

module_options+=(
["_checklist_imaging,author"]="@Tearran"
["_checklist_imaging,maintainer"]="@Tearran"
["_checklist_imaging,feature"]="_checklist_imaging"
["_checklist_imaging,example"]="inkscape gimp"
["_checklist_imaging,desc"]="Imaging Editor installation and management (gimp inkscape)."
["_checklist_imaging,status"]="Active"
["_checklist_imaging,group"]="Internet"
["_checklist_imaging,arch"]="x86-64 arm64 armhf"
)
# Scaffold for app with specific single or dummy candidates.
function _checklist_imaging() {
local title="Imaging"
local self="${module_options["_checklist_imaging,feature"]}"
local _packages
IFS=' ' read -r -a _packages <<< "${module_options["$self,example"]}"

# Manage editor installation/removal
echo "Fetching $title package details..."

# Prepare checklist options dynamically with descriptions
local checklist_options=()
for base_package in "${_packages[@]}"; do
# Find the main package and exclude auxiliary or irrelevant ones
local main_package
main_package=$(apt-cache search "^${base_package}$" | awk -F' - ' '{print $1 " - " $2}')

# Check if the main package exists and fetch its description
if [[ -n "$main_package" ]]; then
local package_name package_description
package_name=$(echo "$main_package" | awk -F' - ' '{print $1}')
package_description=$(echo "$main_package" | awk -F' - ' '{print $2}')

# Check if the package is installed and set its state
if dpkg-query -W -f='${Status}' "$package_name" 2>/dev/null | grep -q "^install ok installed$"; then
checklist_options+=("$package_name" "$package_description" "ON")
else
checklist_options+=("$package_name" "$package_description" "OFF")
fi
fi
done
if [[ ${#checklist_options[@]} -eq 0 ]]; then
echo "No $title packages found."
return 1
fi

process_package_selection "$title" "Select packages to install/remove:" checklist_options[@]
}

module_options+=(
["module_aptwizard,author"]="@Tearran"
["module_aptwizard,maintainer"]="@Tearran"
["module_aptwizard,feature"]="module_aptwizard"
["module_aptwizard,example"]="help Editors Browsers Proftpd Imaging"
["module_aptwizard,desc"]="Apt wizard TUI deb packages similar to softy"
["module_aptwizard,status"]="Active"
["module_aptwizard,doc_link"]=""
["module_aptwizard,group"]="aptwizard"
["module_aptwizard,port"]=""
["module_aptwizard,arch"]="x86-64 arm64 armhf"
)
# Scafold for software module tites
function module_aptwizard() {
local title="Packages"
local self="${module_options["module_aptwizard,feature"]}"
# Convert the example string to an array
local commands
IFS=' ' read -r -a commands <<< "${module_options["$self,example"]}"

case "$1" in
"${commands[0]}")
## help/menu options for the module
echo -e "\nUsage: $self <command>"
echo -e "Commands: ${module_options["$self,example"]}"
echo "Available commands:"
# Loop through all commands (starting from index 1)
for ((i = 1; i < ${#commands[@]}; i++)); do
printf "\t%-10s - Manage %s %s\n" "${commands[i]}" "${commands[i]}" "$title"
#echo -e "\t${commands[i]}\t- Manage ${commands[i]} $title."
done
echo
;;
"${commands[1]}")
_checklist_editors
;;
"${commands[2]}")
_checklist_browsers
;;

"${commands[3]}")
_checklist_proftpd
;;
"${commands[4]}")
_checklist_imaging
;;
*)
echo "Invalid command. Try one of: ${module_options["$self,example"]}"

;;
esac
}