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
"
+ ["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 "
+ ["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 "
+ 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
+}