Two mechanical sweeps, no behaviour change on a single-root install. The 14 `[[ "$p" == "$containers_dir"* ]]` prefix tests that decide manager-vs-container-user elevation become pathIsContainerData, so a file on a second storage root is no longer misclassified as manager-owned — which would have written it with the wrong owner and failed later, far from the cause. The 65 references to the WebUI's own tree become webuiDir(), which is pinned to the primary root by design. Two traps found while doing it: run_privileged.sh is sourced directly by init.sh without paths.sh, so it needs a fallback. Defining one named pathIsContainerData was wrong: generate_function_manifest.sh indexes top-level definitions, and the resulting autoload stub would have shadowed the real multi-root implementation with the primary-only fallback — silently classifying every file on a second disk as manager-owned, which is exactly the bug this sweep exists to prevent. Renamed to _runCfgIsContainerPath, which delegates when the real one is loaded. setup_lock.sh built its path in a top-level assignment, so it was evaluated at source time and needed the file flagged eager. Made it a function instead: the path resolves on call, and the file drops off LP_EAGER_FILES entirely. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
146 lines
5.9 KiB
Bash
Executable File
146 lines
5.9 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
dockerUninstallApp()
|
|
{
|
|
local app_name="$1"
|
|
# Optional: delete the app's docker images too. Default false so a
|
|
# plain `libreportal app uninstall <app>` keeps cached images for a
|
|
# quick reinstall. The WebUI's Uninstall modal exposes this as the
|
|
# "Also delete docker image" checkbox.
|
|
local delete_images="${2:-false}"
|
|
local delete_tasks="${3:-false}"
|
|
local stored_app_name=$app_name
|
|
|
|
if [[ "$stored_app_name" == "" ]]; then
|
|
isError "No app_name provided, unable to continue..."
|
|
else
|
|
isHeader "Uninstalling $stored_app_name"
|
|
initializeAppVariables $stored_app_name;
|
|
|
|
((menu_number++))
|
|
echo ""
|
|
echo "---- $menu_number. Removing app where docker compose is installed"
|
|
echo ""
|
|
|
|
dockerComposeDownRemove $stored_app_name;
|
|
|
|
# App-specific uninstall hook. Host-installed apps (and any app needing
|
|
# teardown beyond the generic docker/DB cleanup) define an uninstall<App>
|
|
# function — it runs the app-specific bits (apt purge, systemd units,
|
|
# cscli state, ...) while the shared compose/data/DB/WebUI teardown below
|
|
# stays generic. The CLI and menu both route uninstalls through here, so
|
|
# this is the single point every uninstall passes through.
|
|
local app_name_ucfirst="$(tr '[:lower:]' '[:upper:]' <<< ${stored_app_name:0:1})${stored_app_name:1}"
|
|
local uninstallFuncName="uninstall${app_name_ucfirst}"
|
|
if declare -f "$uninstallFuncName" >/dev/null 2>&1; then
|
|
((menu_number++))
|
|
echo ""
|
|
echo "---- $menu_number. Running $stored_app_name-specific uninstall steps"
|
|
echo ""
|
|
"$uninstallFuncName"
|
|
fi
|
|
|
|
((menu_number++))
|
|
echo ""
|
|
if [[ "$delete_images" == "true" ]]; then
|
|
echo "---- $menu_number. Removing Docker images for the app"
|
|
echo ""
|
|
dockerRemoveAppImages $stored_app_name;
|
|
else
|
|
echo "---- $menu_number. Keeping Docker images"
|
|
echo ""
|
|
isNotice "Docker images for '$stored_app_name' left in place. A reinstall will reuse them."
|
|
fi
|
|
|
|
((menu_number++))
|
|
echo ""
|
|
echo "---- $menu_number. Deleting all app data from docker folder"
|
|
echo ""
|
|
|
|
dockerDeleteData $stored_app_name;
|
|
|
|
((menu_number++))
|
|
echo ""
|
|
echo "---- $menu_number. Removing unused Docker networks."
|
|
echo ""
|
|
|
|
dockerPruneAppNetworks $stored_app_name;
|
|
|
|
((menu_number++))
|
|
echo ""
|
|
echo "---- $menu_number. Removing app from the database"
|
|
echo ""
|
|
|
|
ipRemoveFromDatabase $stored_app_name;
|
|
portsRemoveFromDatabase $stored_app_name;
|
|
databaseUninstallApp $stored_app_name;
|
|
|
|
((menu_number++))
|
|
echo ""
|
|
echo "---- $menu_number. Updating the WebUI config file."
|
|
echo ""
|
|
|
|
webuiContainerSetup $stored_app_name uninstall;
|
|
|
|
# Drop the app's rows from the updater's generated data right now.
|
|
# updates.json / cves.json are scan-time snapshots on a 30-minute
|
|
# cadence, so without this the Updates tab kept showing a ghost row —
|
|
# an uninstalled app still listed as "up to date" — until the next
|
|
# scan happened to run. Surgical delete rather than a rescan: a full
|
|
# updater scan re-runs CVE checks against every image and has no place
|
|
# inside an uninstall.
|
|
local _upd_gen="$(webuiDir)/frontend/data/updater/generated"
|
|
local _upd_f
|
|
for _upd_f in updates.json cves.json; do
|
|
if [[ -f "$_upd_gen/$_upd_f" ]] && command -v jq >/dev/null 2>&1; then
|
|
local _upd_tmp; _upd_tmp="$(mktemp)"
|
|
if jq --arg n "$stored_app_name" '.apps = [(.apps // [])[] | select(.name != $n)]' "$_upd_gen/$_upd_f" > "$_upd_tmp" 2>/dev/null && [ -s "$_upd_tmp" ]; then
|
|
runFileWrite "$_upd_gen/$_upd_f" < "$_upd_tmp"
|
|
fi
|
|
rm -f "$_upd_tmp"
|
|
fi
|
|
done
|
|
|
|
# A removed app may have been routed through a network gateway (e.g.
|
|
# gluetun); let each provider refresh its forwarded-port registration.
|
|
# Each hook self-skips when its provider isn't installed.
|
|
local _np_fn
|
|
for _np_fn in $(compgen -A function 2>/dev/null | grep '^appNetworkRegisterPorts_'); do
|
|
"$_np_fn"
|
|
done
|
|
|
|
if [[ "$delete_tasks" == "true" ]]; then
|
|
((menu_number++))
|
|
echo ""
|
|
echo "---- $menu_number. Removing related task history"
|
|
echo ""
|
|
local _tasks_dir="$(webuiDir)/frontend/data/tasks"
|
|
local _removed=0
|
|
if [[ -d "$_tasks_dir" ]]; then
|
|
for _tf in "$_tasks_dir"/task_*.json; do
|
|
[[ ! -f "$_tf" ]] && continue
|
|
# Skip in-flight tasks — that includes the uninstall task
|
|
# we're currently inside, plus anything queued or running.
|
|
if runFileOp grep -qE "\"status\"[[:space:]]*:[[:space:]]*\"(running|queued|pending)\"" "$_tf" 2>/dev/null; then
|
|
continue
|
|
fi
|
|
if runFileOp grep -q "\"app\"[[:space:]]*:[[:space:]]*\"${stored_app_name}\"" "$_tf" 2>/dev/null; then
|
|
local _id=$(basename "$_tf" .json)
|
|
runFileOp rm -f "$_tf" "$_tasks_dir/${_id}.log" "$_tasks_dir/${_id}.cancel" 2>/dev/null
|
|
_removed=$((_removed + 1))
|
|
fi
|
|
done
|
|
fi
|
|
isSuccessful "Removed $_removed task record(s) for $stored_app_name (in-flight task left for the WebUI to clean up after completion)."
|
|
fi
|
|
|
|
((menu_number++))
|
|
echo ""
|
|
isSuccessful "$stored_app_name has been removed from your system!"
|
|
echo ""
|
|
|
|
menu_number=0
|
|
cd
|
|
fi
|
|
}
|