From 0b27ed10729a4ac8daace1d9ee0b82da96bd98e3 Mon Sep 17 00:00:00 2001 From: librelad Date: Sun, 24 May 2026 18:01:51 +0100 Subject: [PATCH] refactor(desudo): funnel backup-engine privilege drop through runBackupOp The borg/restic/kopia engines all dropped to the dedicated backup user via scattered 'sudo -E -u $docker_install_user'. Centralize that into a single runBackupOp helper so the backup subsystem has one audit point and the scoped sudoers needs only the (dockerinstall) drop rule. Also: - owncloud config heredoc tees -> runSystem (container-UID file) - webui_display_logins: fix the broken 'command -v sudo sqlite3' guard to 'command -v sqlite3' (body already runs sqlite3 via runInstallOp) Co-Authored-By: Claude Opus 4.7 Signed-off-by: librelad --- scripts/app/containers/owncloud/owncloud_setup_config.sh | 4 ++-- scripts/backup/app/backup_app_delete.sh | 4 ++-- scripts/backup/engine/borg_backup.sh | 2 +- scripts/backup/engine/borg_check.sh | 4 ++-- scripts/backup/engine/borg_forget.sh | 4 ++-- scripts/backup/engine/borg_init.sh | 4 ++-- scripts/backup/engine/borg_restore.sh | 8 ++++---- scripts/backup/engine/borg_snapshots.sh | 6 +++--- scripts/backup/engine/kopia_backup.sh | 2 +- scripts/backup/engine/kopia_check.sh | 4 ++-- scripts/backup/engine/kopia_forget.sh | 4 ++-- scripts/backup/engine/kopia_init.sh | 4 ++-- scripts/backup/engine/kopia_restore.sh | 6 +++--- scripts/backup/engine/kopia_snapshots.sh | 4 ++-- scripts/backup/engine/restic_backup.sh | 2 +- scripts/backup/engine/restic_check.sh | 4 ++-- scripts/backup/engine/restic_dump.sh | 4 ++-- scripts/backup/engine/restic_forget.sh | 2 +- scripts/backup/engine/restic_init.sh | 4 ++-- scripts/backup/engine/restic_restore.sh | 2 +- scripts/backup/engine/restic_snapshots.sh | 6 +++--- scripts/docker/command/run_privileged.sh | 9 +++++++++ scripts/webui/webui_display_logins.sh | 2 +- 23 files changed, 52 insertions(+), 43 deletions(-) diff --git a/scripts/app/containers/owncloud/owncloud_setup_config.sh b/scripts/app/containers/owncloud/owncloud_setup_config.sh index e3bee54..839822f 100755 --- a/scripts/app/containers/owncloud/owncloud_setup_config.sh +++ b/scripts/app/containers/owncloud/owncloud_setup_config.sh @@ -89,7 +89,7 @@ appOwnCloudSetupConfig() if [[ $public == "true" ]]; then # Add new lines at the end of the file -sudo tee -a "$tmp_awk_output" > /dev/null < /dev/null < 'https://$host_setup/', 'Overwriteprotocol' => 'https', 'trusted_domains' => @@ -104,7 +104,7 @@ EOL checkSuccess "Add overwrite and trusted_domain (public) lines to the config" elif [[ $public == "false" ]]; then # Add new lines at the end of the file -sudo tee -a "$tmp_awk_output" > /dev/null < /dev/null < array( 0 => '$ip_setup', diff --git a/scripts/backup/app/backup_app_delete.sh b/scripts/backup/app/backup_app_delete.sh index 58317dc..5106eb0 100644 --- a/scripts/backup/app/backup_app_delete.sh +++ b/scripts/backup/app/backup_app_delete.sh @@ -16,7 +16,7 @@ backupAppDeleteSnapshot() fi resticEnvExport "$idx" || return 1 - sudo -E -u "$docker_install_user" restic forget "$snapshot_id" $([[ "$CFG_BACKUP_PRUNE_AFTER_FORGET" == "true" ]] && echo "--prune") + runBackupOp restic forget "$snapshot_id" $([[ "$CFG_BACKUP_PRUNE_AFTER_FORGET" == "true" ]] && echo "--prune") local rc=$? resticEnvUnset if [[ $rc -eq 0 ]]; then @@ -47,7 +47,7 @@ backupAppDeleteAll() continue fi resticEnvExport "$idx" || continue - sudo -E -u "$docker_install_user" restic forget --tag "app=$app_name" $([[ "$CFG_BACKUP_PRUNE_AFTER_FORGET" == "true" ]] && echo "--prune") + runBackupOp restic forget --tag "app=$app_name" $([[ "$CFG_BACKUP_PRUNE_AFTER_FORGET" == "true" ]] && echo "--prune") resticEnvUnset done < <(resticEnabledLocations) } diff --git a/scripts/backup/engine/borg_backup.sh b/scripts/backup/engine/borg_backup.sh index c63334f..d318b85 100644 --- a/scripts/backup/engine/borg_backup.sh +++ b/scripts/backup/engine/borg_backup.sh @@ -32,7 +32,7 @@ borgBackupAppToLocation() # Logs to stderr; stdout is only the archive name for the caller's $(). isNotice "Snapshotting $app_name → $loc_name (archive: $archive)" >&2 - sudo -E -u "$docker_install_user" borg create \ + runBackupOp borg create \ --comment "$comment" \ --compression auto,zstd \ "${exclude_args[@]}" \ diff --git a/scripts/backup/engine/borg_check.sh b/scripts/backup/engine/borg_check.sh index 18f1fa9..dc56e58 100644 --- a/scripts/backup/engine/borg_check.sh +++ b/scripts/backup/engine/borg_check.sh @@ -13,7 +13,7 @@ borgCheckLocation() fi isNotice "Checking $(resticLocationName "$idx")${read_data:+ (full data verify)}" - sudo -E -u "$docker_install_user" borg "${args[@]}" + runBackupOp borg "${args[@]}" local rc=$? borgEnvUnset if [[ $rc -eq 0 ]]; then @@ -29,7 +29,7 @@ borgLocationStats() local idx="$1" borgEnvExport "$idx" || return 1 local raw - raw=$(sudo -E -u "$docker_install_user" borg info --json 2>/dev/null) + raw=$(runBackupOp borg info --json 2>/dev/null) borgEnvUnset [[ -z "$raw" ]] && { echo '{"total_size":0,"total_file_count":0}'; return 0; } diff --git a/scripts/backup/engine/borg_forget.sh b/scripts/backup/engine/borg_forget.sh index d0aed88..5ec7d1e 100644 --- a/scripts/backup/engine/borg_forget.sh +++ b/scripts/backup/engine/borg_forget.sh @@ -28,11 +28,11 @@ borgForgetApp() [[ -n "$keep_yearly" ]] && args+=(--keep-yearly "$keep_yearly") isNotice "Applying retention for $app_name on $(resticLocationName "$idx")" - sudo -E -u "$docker_install_user" borg "${args[@]}" + runBackupOp borg "${args[@]}" local rc=$? if [[ "$CFG_BACKUP_PRUNE_AFTER_FORGET" == "true" && $rc -eq 0 ]]; then - sudo -E -u "$docker_install_user" borg compact + runBackupOp borg compact fi borgEnvUnset diff --git a/scripts/backup/engine/borg_init.sh b/scripts/backup/engine/borg_init.sh index 43414e9..957fda9 100644 --- a/scripts/backup/engine/borg_init.sh +++ b/scripts/backup/engine/borg_init.sh @@ -16,14 +16,14 @@ borgInitLocation() runFileOp chown -R "$docker_install_user":"$docker_install_user" "$BORG_REPO" fi - if sudo -E -u "$docker_install_user" borg info "$BORG_REPO" >/dev/null 2>&1; then + if runBackupOp borg info "$BORG_REPO" >/dev/null 2>&1; then isNotice "$(resticLocationName "$idx") already initialized at $BORG_REPO" borgEnvUnset return 0 fi isNotice "Initializing $(resticLocationName "$idx") at $BORG_REPO" - if sudo -E -u "$docker_install_user" borg init --encryption=repokey-blake2 "$BORG_REPO"; then + if runBackupOp borg init --encryption=repokey-blake2 "$BORG_REPO"; then isSuccessful "$(resticLocationName "$idx") initialized" else isError "Failed to initialize $(resticLocationName "$idx")" diff --git a/scripts/backup/engine/borg_restore.sh b/scripts/backup/engine/borg_restore.sh index 0af5b25..31bd77b 100644 --- a/scripts/backup/engine/borg_restore.sh +++ b/scripts/backup/engine/borg_restore.sh @@ -19,10 +19,10 @@ borgRestoreSnapshot() local rc if [[ -n "$include_path" ]]; then local stripped="${include_path#/}" - ( cd "$target_dir" && sudo -E -u "$docker_install_user" borg extract "::$snapshot_id" "$stripped" ) + ( cd "$target_dir" && runBackupOp borg extract "::$snapshot_id" "$stripped" ) rc=$? else - ( cd "$target_dir" && sudo -E -u "$docker_install_user" borg extract "::$snapshot_id" ) + ( cd "$target_dir" && runBackupOp borg extract "::$snapshot_id" ) rc=$? fi borgEnvUnset @@ -39,9 +39,9 @@ borgDumpFile() borgEnvExport "$idx" || return 1 local stripped="${file_path#/}" if [[ -n "$target_file" ]]; then - sudo -E -u "$docker_install_user" borg extract --stdout "::$snapshot_id" "$stripped" | runFileWrite "$target_file" + runBackupOp borg extract --stdout "::$snapshot_id" "$stripped" | runFileWrite "$target_file" else - sudo -E -u "$docker_install_user" borg extract --stdout "::$snapshot_id" "$stripped" + runBackupOp borg extract --stdout "::$snapshot_id" "$stripped" fi local rc=$? borgEnvUnset diff --git a/scripts/backup/engine/borg_snapshots.sh b/scripts/backup/engine/borg_snapshots.sh index b091e35..23c6068 100644 --- a/scripts/backup/engine/borg_snapshots.sh +++ b/scripts/backup/engine/borg_snapshots.sh @@ -19,7 +19,7 @@ borgSnapshotsJson() fi local raw - raw=$(sudo -E -u "$docker_install_user" borg "${args[@]}" 2>/dev/null) + raw=$(runBackupOp borg "${args[@]}" 2>/dev/null) local rc=$? borgEnvUnset [[ $rc -ne 0 || -z "$raw" ]] && { echo "[]"; return $rc; } @@ -63,7 +63,7 @@ borgSnapshotLatestId() borgEnvExport "$idx" || return 1 local id - id=$(sudo -E -u "$docker_install_user" borg list --json --glob-archives "${app_name}-${host}-*" --last 1 2>/dev/null \ + id=$(runBackupOp borg list --json --glob-archives "${app_name}-${host}-*" --last 1 2>/dev/null \ | grep -o '"name":"[^"]*"' | head -1 | cut -d'"' -f4) borgEnvUnset echo "$id" @@ -74,7 +74,7 @@ borgSnapshotListFiles() local idx="$1" local snapshot_id="$2" borgEnvExport "$idx" || return 1 - sudo -E -u "$docker_install_user" borg list --json-lines "::$snapshot_id" 2>/dev/null + runBackupOp borg list --json-lines "::$snapshot_id" 2>/dev/null local rc=$? borgEnvUnset return $rc diff --git a/scripts/backup/engine/kopia_backup.sh b/scripts/backup/engine/kopia_backup.sh index b5bc23e..f414cb6 100644 --- a/scripts/backup/engine/kopia_backup.sh +++ b/scripts/backup/engine/kopia_backup.sh @@ -41,7 +41,7 @@ kopiaBackupAppToLocation() fi local output - output=$(sudo -E -u "$docker_install_user" kopia snapshot create "$source_path" "${tags[@]}" --json 2>&1) + output=$(runBackupOp kopia snapshot create "$source_path" "${tags[@]}" --json 2>&1) local rc=$? [[ "$wrote_ignore" == true ]] && runFileOp rm -f "$ignore_file" diff --git a/scripts/backup/engine/kopia_check.sh b/scripts/backup/engine/kopia_check.sh index 83ee17f..f6635a2 100644 --- a/scripts/backup/engine/kopia_check.sh +++ b/scripts/backup/engine/kopia_check.sh @@ -15,7 +15,7 @@ kopiaCheckLocation() fi isNotice "Checking $(resticLocationName "$idx") with Kopia${read_data:+ (data sample: ${read_data}%)}" - sudo -E -u "$docker_install_user" kopia "${args[@]}" + runBackupOp kopia "${args[@]}" local rc=$? kopiaEnvUnset if [[ $rc -eq 0 ]]; then @@ -31,7 +31,7 @@ kopiaLocationStats() local idx="$1" kopiaEnvExport "$idx" || return 1 local raw - raw=$(sudo -E -u "$docker_install_user" kopia repository status --json 2>/dev/null) + raw=$(runBackupOp kopia repository status --json 2>/dev/null) kopiaEnvUnset [[ -z "$raw" ]] && { echo '{"total_size":0,"total_file_count":0}'; return 0; } diff --git a/scripts/backup/engine/kopia_forget.sh b/scripts/backup/engine/kopia_forget.sh index e3ff072..5aa8e86 100644 --- a/scripts/backup/engine/kopia_forget.sh +++ b/scripts/backup/engine/kopia_forget.sh @@ -33,10 +33,10 @@ kopiaForgetApp() [[ -n "$keep_monthly" ]] && policy_args+=(--keep-monthly "$keep_monthly") [[ -n "$keep_yearly" ]] && policy_args+=(--keep-annual "$keep_yearly") - sudo -E -u "$docker_install_user" kopia "${policy_args[@]}" >/dev/null 2>&1 + runBackupOp kopia "${policy_args[@]}" >/dev/null 2>&1 isNotice "Running Kopia maintenance for $app_name on $(resticLocationName "$idx")" - sudo -E -u "$docker_install_user" kopia maintenance run --full + runBackupOp kopia maintenance run --full local rc=$? kopiaEnvUnset return $rc diff --git a/scripts/backup/engine/kopia_init.sh b/scripts/backup/engine/kopia_init.sh index 249dcff..6073b57 100644 --- a/scripts/backup/engine/kopia_init.sh +++ b/scripts/backup/engine/kopia_init.sh @@ -16,7 +16,7 @@ kopiaInitLocation() # Already initialized? `kopia repository status` returns 0 only if the # config file is connected to a repo. - if sudo -E -u "$docker_install_user" kopia repository status --json >/dev/null 2>&1; then + if runBackupOp kopia repository status --json >/dev/null 2>&1; then isNotice "$(resticLocationName "$idx") already initialized" kopiaEnvUnset return 0 @@ -60,7 +60,7 @@ kopiaInitLocation() esac isNotice "Initializing $(resticLocationName "$idx") with Kopia" - if sudo -E -u "$docker_install_user" kopia "${args[@]}"; then + if runBackupOp kopia "${args[@]}"; then isSuccessful "$(resticLocationName "$idx") initialized" else isError "Failed to initialize $(resticLocationName "$idx") with Kopia" diff --git a/scripts/backup/engine/kopia_restore.sh b/scripts/backup/engine/kopia_restore.sh index efb33b0..f4616db 100644 --- a/scripts/backup/engine/kopia_restore.sh +++ b/scripts/backup/engine/kopia_restore.sh @@ -24,7 +24,7 @@ kopiaRestoreSnapshot() final_target="$target_dir/${include_path#/}" runFileOp mkdir -p "$final_target" fi - sudo -E -u "$docker_install_user" kopia snapshot restore "$snapshot_id" "$final_target" + runBackupOp kopia snapshot restore "$snapshot_id" "$final_target" local rc=$? kopiaEnvUnset return $rc @@ -40,9 +40,9 @@ kopiaDumpFile() kopiaEnvExport "$idx" || return 1 # `kopia show` writes the file contents from a snapshot to stdout. if [[ -n "$target_file" ]]; then - sudo -E -u "$docker_install_user" kopia show "${snapshot_id}:${file_path}" | sudo tee "$target_file" >/dev/null + runBackupOp kopia show "${snapshot_id}:${file_path}" | sudo tee "$target_file" >/dev/null else - sudo -E -u "$docker_install_user" kopia show "${snapshot_id}:${file_path}" + runBackupOp kopia show "${snapshot_id}:${file_path}" fi local rc=$? kopiaEnvUnset diff --git a/scripts/backup/engine/kopia_snapshots.sh b/scripts/backup/engine/kopia_snapshots.sh index 2b2042c..2efd8b0 100644 --- a/scripts/backup/engine/kopia_snapshots.sh +++ b/scripts/backup/engine/kopia_snapshots.sh @@ -14,7 +14,7 @@ kopiaSnapshotsJson() local args=(snapshot list --all --json) local raw - raw=$(sudo -E -u "$docker_install_user" kopia "${args[@]}" 2>/dev/null) + raw=$(runBackupOp kopia "${args[@]}" 2>/dev/null) local rc=$? kopiaEnvUnset [[ $rc -ne 0 || -z "$raw" ]] && { echo "[]"; return $rc; } @@ -62,7 +62,7 @@ kopiaSnapshotListFiles() local idx="$1" local snapshot_id="$2" kopiaEnvExport "$idx" || return 1 - sudo -E -u "$docker_install_user" kopia snapshot list "$snapshot_id" --json 2>/dev/null + runBackupOp kopia snapshot list "$snapshot_id" --json 2>/dev/null local rc=$? kopiaEnvUnset return $rc diff --git a/scripts/backup/engine/restic_backup.sh b/scripts/backup/engine/restic_backup.sh index 248bfeb..409d14b 100644 --- a/scripts/backup/engine/restic_backup.sh +++ b/scripts/backup/engine/restic_backup.sh @@ -36,7 +36,7 @@ resticBackupAppToLocation() # the caller captures it with $() and feeds it to verify/retention. isNotice "Snapshotting $app_name → $loc_name" >&2 local output - output=$(sudo -E -u "$docker_install_user" restic backup \ + output=$(runBackupOp restic backup \ --host "$host_tag" \ "${extra_tags[@]}" \ "${exclude_args[@]}" \ diff --git a/scripts/backup/engine/restic_check.sh b/scripts/backup/engine/restic_check.sh index b2b70aa..f6e43b0 100644 --- a/scripts/backup/engine/restic_check.sh +++ b/scripts/backup/engine/restic_check.sh @@ -15,7 +15,7 @@ resticCheckLocation() fi isNotice "Checking $(resticLocationName "$idx")${read_data:+ (sample: ${read_data}%)}" - sudo -E -u "$docker_install_user" restic "${args[@]}" + runBackupOp restic "${args[@]}" local rc=$? resticEnvUnset if [[ $rc -eq 0 ]]; then @@ -42,7 +42,7 @@ resticLocationStats() { local idx="$1" resticEnvExport "$idx" || return 1 - sudo -E -u "$docker_install_user" restic stats --json --no-lock 2>/dev/null + runBackupOp restic stats --json --no-lock 2>/dev/null local rc=$? resticEnvUnset return $rc diff --git a/scripts/backup/engine/restic_dump.sh b/scripts/backup/engine/restic_dump.sh index 66d1908..92948ca 100644 --- a/scripts/backup/engine/restic_dump.sh +++ b/scripts/backup/engine/restic_dump.sh @@ -10,9 +10,9 @@ resticDumpFile() resticEnvExport "$idx" || return 1 if [[ -n "$target_file" ]]; then - sudo -E -u "$docker_install_user" restic dump "$snapshot_id" "$file_path" | sudo tee "$target_file" >/dev/null + runBackupOp restic dump "$snapshot_id" "$file_path" | sudo tee "$target_file" >/dev/null else - sudo -E -u "$docker_install_user" restic dump "$snapshot_id" "$file_path" + runBackupOp restic dump "$snapshot_id" "$file_path" fi local rc=$? resticEnvUnset diff --git a/scripts/backup/engine/restic_forget.sh b/scripts/backup/engine/restic_forget.sh index 0a79132..3114007 100644 --- a/scripts/backup/engine/restic_forget.sh +++ b/scripts/backup/engine/restic_forget.sh @@ -28,7 +28,7 @@ resticForgetApp() [[ "$CFG_BACKUP_PRUNE_AFTER_FORGET" == "true" ]] && args+=(--prune) isNotice "Applying retention for $app_name on $(resticLocationName "$idx")" - sudo -E -u "$docker_install_user" restic "${args[@]}" + runBackupOp restic "${args[@]}" local rc=$? resticEnvUnset return $rc diff --git a/scripts/backup/engine/restic_init.sh b/scripts/backup/engine/restic_init.sh index 06029b3..a84a051 100644 --- a/scripts/backup/engine/restic_init.sh +++ b/scripts/backup/engine/restic_init.sh @@ -16,14 +16,14 @@ resticInitLocation() runFileOp chown -R "$docker_install_user":"$docker_install_user" "$RESTIC_REPOSITORY" fi - if sudo -E -u "$docker_install_user" restic snapshots --json --no-lock >/dev/null 2>&1; then + if runBackupOp restic snapshots --json --no-lock >/dev/null 2>&1; then isNotice "$(resticLocationName "$idx") already initialized at $RESTIC_REPOSITORY" resticEnvUnset return 0 fi isNotice "Initializing $(resticLocationName "$idx") at $RESTIC_REPOSITORY" - if sudo -E -u "$docker_install_user" restic init; then + if runBackupOp restic init; then isSuccessful "$(resticLocationName "$idx") initialized" else isError "Failed to initialize $(resticLocationName "$idx")" diff --git a/scripts/backup/engine/restic_restore.sh b/scripts/backup/engine/restic_restore.sh index f1dde74..759fdf1 100644 --- a/scripts/backup/engine/restic_restore.sh +++ b/scripts/backup/engine/restic_restore.sh @@ -20,7 +20,7 @@ resticRestoreSnapshot() [[ -n "$include_path" ]] && args+=(--include "$include_path") isNotice "Restoring ${snapshot_id:0:8} from $(resticLocationName "$idx") → $target_dir" - sudo -E -u "$docker_install_user" restic "${args[@]}" + runBackupOp restic "${args[@]}" local rc=$? resticEnvUnset return $rc diff --git a/scripts/backup/engine/restic_snapshots.sh b/scripts/backup/engine/restic_snapshots.sh index 556eaa4..f0d262d 100644 --- a/scripts/backup/engine/restic_snapshots.sh +++ b/scripts/backup/engine/restic_snapshots.sh @@ -12,7 +12,7 @@ resticSnapshotsJson() [[ -n "$app_filter" ]] && args+=(--tag "app=$app_filter") [[ -n "$host_filter" ]] && args+=(--host "$host_filter") - sudo -E -u "$docker_install_user" restic "${args[@]}" 2>/dev/null + runBackupOp restic "${args[@]}" 2>/dev/null local rc=$? resticEnvUnset return $rc @@ -26,7 +26,7 @@ resticSnapshotLatestId() resticEnvExport "$idx" || return 1 local id - id=$(sudo -E -u "$docker_install_user" restic snapshots \ + id=$(runBackupOp restic snapshots \ --tag "app=$app_name" --host "$host" \ --latest 1 --json --no-lock 2>/dev/null | \ grep -o '"short_id":"[^"]*"' | head -1 | cut -d'"' -f4) @@ -40,7 +40,7 @@ resticSnapshotListFiles() local snapshot_id="$2" resticEnvExport "$idx" || return 1 - sudo -E -u "$docker_install_user" restic ls --json --no-lock "$snapshot_id" 2>/dev/null + runBackupOp restic ls --json --no-lock "$snapshot_id" 2>/dev/null local rc=$? resticEnvUnset return $rc diff --git a/scripts/docker/command/run_privileged.sh b/scripts/docker/command/run_privileged.sh index 787de61..1cf5224 100644 --- a/scripts/docker/command/run_privileged.sh +++ b/scripts/docker/command/run_privileged.sh @@ -77,6 +77,15 @@ runInstallWrite() { runAsManager tee "${append_flag[@]}" "$dest" >/dev/null } +# Backup-engine command (borg/restic/kopia) run AS the dedicated backup user +# ($docker_install_user), with the environment preserved (-E) so the repo +# password and BORG_/RESTIC_/KOPIA_ env vars reach the tool. Never root — the +# scoped sudoers lets the manager drop to this user. Single funnel so the +# backup subsystem's privilege drop has one audit point. +runBackupOp() { + sudo -E -u "$docker_install_user" "$@" +} + # Genuine system-administration command (ufw/systemctl/apt/sysctl/useradd, /etc # edits). Needs real root in both modes; funnelled through one place so it can # later be confined to a scoped sudoers allowlist. diff --git a/scripts/webui/webui_display_logins.sh b/scripts/webui/webui_display_logins.sh index afae0f4..27ee75d 100755 --- a/scripts/webui/webui_display_logins.sh +++ b/scripts/webui/webui_display_logins.sh @@ -10,7 +10,7 @@ getLibrePortalWebUIUrls() local db_file="${db_file:-database.db}" # Check if sqlite3 is available and database exists - if command -v sudo sqlite3 &> /dev/null && [ -f "$docker_dir/$db_file" ]; then + if command -v sqlite3 &> /dev/null && [ -f "$docker_dir/$db_file" ]; then # Check if LibrePortal app is installed local libreportal_check=$(runInstallOp sqlite3 "$docker_dir/$db_file" "SELECT name FROM apps WHERE name = 'libreportal' AND status = 1;" 2>/dev/null)