diff --git a/tests/aptwizard.conf b/tests/aptwizard.conf new file mode 100644 index 000000000..6f57f0c9b --- /dev/null +++ b/tests/aptwizard.conf @@ -0,0 +1,8 @@ +ENABLED=true +RELEASE="bookworm:jammy:noble" +TESTNAME="AptWizard" + +testcase() {( + set -e + ./bin/armbian-config --api module_aptwizard help +)} diff --git a/tools/json/config.software.json b/tools/json/config.software.json index 21d40721a..25544469d 100644 --- a/tools/json/config.software.json +++ b/tools/json/config.software.json @@ -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", diff --git a/tools/modules/functions/interface_checklist.sh b/tools/modules/functions/interface_checklist.sh new file mode 100644 index 000000000..296c2d531 --- /dev/null +++ b/tools/modules/functions/interface_checklist.sh @@ -0,0 +1,118 @@ +module_options+=( + ["interface_checklist,author"]="@Tearran" + ["interface_checklist,maintainer"]="@Tearran" + ["interface_checklist,feature"]="interface_checklist" + ["interface_checklist,example"]="interface_checklist <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." +} diff --git a/tools/modules/software/module_aptwizard.sh b/tools/modules/software/module_aptwizard.sh new file mode 100644 index 000000000..90bcf7147 --- /dev/null +++ b/tools/modules/software/module_aptwizard.sh @@ -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 +}