From 75dfb3849b4fb0083823b59370b487489e05432f Mon Sep 17 00:00:00 2001 From: librelad Date: Sun, 24 May 2026 14:04:06 +0100 Subject: [PATCH] fix(rootless): backup/ssh WebUI generators write as the container owner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The backup + ssh generators created their frontend/data dirs via plain/sudo mkdir and wrote files via sudo tee/mv (root-owned), then called createTouch (dockerinstall) which can't re-own a root file — so every write hit 'touch: Permission denied' in rootless and left root-owned data the dockerinstall container/generators can't rewrite. Convert dir creation to runFileOp mkdir and file writes to runFileWrite (both run as the container owner: dockerinstall in rootless, manager in rooted), dropping the temp/mv/createTouch dance. Also make the createFolders chokepoint mode-aware (containers/ paths created via runFileOp) so it mirrors createTouch. Co-Authored-By: Claude Opus 4.7 Signed-off-by: librelad --- scripts/function/folder/create_folder.sh | 15 +++++++++++++++ .../generators/backup/webui_backup_app_status.sh | 6 ++---- .../generators/backup/webui_backup_dashboard.sh | 6 ++---- .../generators/backup/webui_backup_engines.sh | 8 +++----- .../generators/backup/webui_backup_locations.sh | 6 ++---- .../generators/backup/webui_backup_passwords.sh | 10 +++------- .../data/generators/backup/webui_backup_schema.sh | 5 ++--- .../generators/backup/webui_backup_snapshots.sh | 6 ++---- .../data/generators/system/webui_ssh_access.sh | 5 ++--- 9 files changed, 33 insertions(+), 34 deletions(-) diff --git a/scripts/function/folder/create_folder.sh b/scripts/function/folder/create_folder.sh index 523ca4e..389cf60 100755 --- a/scripts/function/folder/create_folder.sh +++ b/scripts/function/folder/create_folder.sh @@ -9,6 +9,21 @@ createFolders() local folder_name=$(basename "$dir_path") local clean_dir=$(echo "$dir_path" | sed 's#//*#/#g') + # Under /docker/containers// the owner is the docker install user + # (the rootless container + host generators run as it), so create the dir + # AS that user via runFileOp — creating it as the right owner avoids a + # chown-to-another-user the unprivileged runtime can't do. Mirrors + # createTouch; the $user_name hint is advisory for these paths. + if [[ "$clean_dir" == "$containers_dir"* || "$clean_dir" == /docker/containers/* ]]; then + if [ ! -d "$dir_path" ]; then + local result=$(runFileOp mkdir -p "$dir_path") + [ "$silent_flag" == "loud" ] && checkSuccess "Creating $folder_name directory" + elif [ "$silent_flag" == "loud" ]; then + isNotice "$folder_name directory already exists" + fi + continue + fi + if [ ! -d "$dir_path" ]; then local result=$(sudo mkdir -p "$dir_path") if [ "$silent_flag" == "loud" ]; then diff --git a/scripts/webui/data/generators/backup/webui_backup_app_status.sh b/scripts/webui/data/generators/backup/webui_backup_app_status.sh index b8a9620..8a4aec2 100644 --- a/scripts/webui/data/generators/backup/webui_backup_app_status.sh +++ b/scripts/webui/data/generators/backup/webui_backup_app_status.sh @@ -4,7 +4,7 @@ webuiGenerateBackupAppStatus() { local app_name="$1" local output_dir="$containers_dir/libreportal/frontend/data/backup/generated/apps" - mkdir -p "$output_dir" + runFileOp mkdir -p "$output_dir" if [[ -z "$app_name" ]]; then if [[ -f "$docker_dir/$db_file" ]]; then @@ -51,7 +51,5 @@ webuiGenerateBackupAppStatus() done < <(resticEnabledLocations) content+="]}" - echo "$content" | sudo tee "$temp_file" >/dev/null - sudo mv "$temp_file" "$output_file" - createTouch "$output_file" "$docker_install_user" "silent" + echo "$content" | runFileWrite "$output_file" } diff --git a/scripts/webui/data/generators/backup/webui_backup_dashboard.sh b/scripts/webui/data/generators/backup/webui_backup_dashboard.sh index 537c785..53bd2ea 100644 --- a/scripts/webui/data/generators/backup/webui_backup_dashboard.sh +++ b/scripts/webui/data/generators/backup/webui_backup_dashboard.sh @@ -6,7 +6,7 @@ webuiGenerateBackupDashboard() local output_file="$output_dir/dashboard.json" local temp_file="${output_file}.tmp.$$" - mkdir -p "$output_dir" + runFileOp mkdir -p "$output_dir" local generated_at generated_at=$(date -Iseconds) @@ -79,8 +79,6 @@ webuiGenerateBackupDashboard() content+="\"apps\":$apps_json" content+="}" - echo "$content" | sudo tee "$temp_file" >/dev/null - sudo mv "$temp_file" "$output_file" - createTouch "$output_file" "$docker_install_user" "silent" + echo "$content" | runFileWrite "$output_file" isSuccessful "Backup dashboard JSON regenerated" } diff --git a/scripts/webui/data/generators/backup/webui_backup_engines.sh b/scripts/webui/data/generators/backup/webui_backup_engines.sh index d11c107..94aeb7b 100644 --- a/scripts/webui/data/generators/backup/webui_backup_engines.sh +++ b/scripts/webui/data/generators/backup/webui_backup_engines.sh @@ -8,7 +8,7 @@ webuiGenerateBackupEngines() { local src_dir="$install_scripts_dir/backup/engines" local out_dir="$containers_dir/libreportal/frontend/data/backup/generated/engines" - sudo mkdir -p "$out_dir" + runFileOp mkdir -p "$out_dir" if [[ ! -d "$src_dir" ]]; then isNotice "No engines directory at $src_dir — skipping engines regen" @@ -22,8 +22,7 @@ webuiGenerateBackupEngines() [[ -f "$f" ]] || continue local base base=$(basename "$f") - sudo cp "$f" "$out_dir/$base" - createTouch "$out_dir/$base" "$docker_install_user" "silent" + runFileOp cp "$f" "$out_dir/$base" local id="${base%.json}" $first || index+="," first=false @@ -32,8 +31,7 @@ webuiGenerateBackupEngines() index+="]" local idx_file="$out_dir/index.json" - echo "{\"engines\":$index,\"generated_at\":\"$(date -Iseconds)\"}" | sudo tee "$idx_file" >/dev/null - createTouch "$idx_file" "$docker_install_user" "silent" + echo "{\"engines\":$index,\"generated_at\":\"$(date -Iseconds)\"}" | runFileWrite "$idx_file" isSuccessful "Engines JSON regenerated" } diff --git a/scripts/webui/data/generators/backup/webui_backup_locations.sh b/scripts/webui/data/generators/backup/webui_backup_locations.sh index d3dcb0c..52908e8 100644 --- a/scripts/webui/data/generators/backup/webui_backup_locations.sh +++ b/scripts/webui/data/generators/backup/webui_backup_locations.sh @@ -5,7 +5,7 @@ webuiGenerateBackupLocations() local output_dir="$containers_dir/libreportal/frontend/data/backup/generated" local output_file="$output_dir/locations.json" local temp_file="${output_file}.tmp.$$" - mkdir -p "$output_dir" + runFileOp mkdir -p "$output_dir" if declare -f backupLocationsMigrate >/dev/null 2>&1; then backupLocationsMigrate >/dev/null 2>&1 || true @@ -99,8 +99,6 @@ webuiGenerateBackupLocations() done < <(resticAllLocationIndices) content+="]}" - echo "$content" | sudo tee "$temp_file" >/dev/null - sudo mv "$temp_file" "$output_file" - createTouch "$output_file" "$docker_install_user" "silent" + echo "$content" | runFileWrite "$output_file" isSuccessful "Locations JSON regenerated" } diff --git a/scripts/webui/data/generators/backup/webui_backup_passwords.sh b/scripts/webui/data/generators/backup/webui_backup_passwords.sh index 30674e6..38ea7f8 100644 --- a/scripts/webui/data/generators/backup/webui_backup_passwords.sh +++ b/scripts/webui/data/generators/backup/webui_backup_passwords.sh @@ -6,7 +6,7 @@ webuiGenerateBackupPasswords() local output_file="$output_dir/passwords.txt" local temp_file="${output_file}.tmp.$$" - mkdir -p "$output_dir" + runFileOp mkdir -p "$output_dir" local generated_at generated_at=$(date -Iseconds) @@ -33,10 +33,6 @@ webuiGenerateBackupPasswords() echo "CFG_BACKUP_LOC_${idx}_PASSWORD=${pass}" echo "" done < <(resticAllLocationIndices) - } > "$temp_file" - - chmod 0600 "$temp_file" - mv "$temp_file" "$output_file" - createTouch "$output_file" "${docker_install_user:-${sudo_user_name:-libreportal}}" "silent" - chmod 0600 "$output_file" + } | runFileWrite "$output_file" + runFileOp chmod 0600 "$output_file" } diff --git a/scripts/webui/data/generators/backup/webui_backup_schema.sh b/scripts/webui/data/generators/backup/webui_backup_schema.sh index f75e99b..8790d6d 100644 --- a/scripts/webui/data/generators/backup/webui_backup_schema.sh +++ b/scripts/webui/data/generators/backup/webui_backup_schema.sh @@ -12,7 +12,7 @@ webuiGenerateBackupSchema() { local out_dir="$containers_dir/libreportal/frontend/data/backup/generated" local out_file="$out_dir/schema.json" - sudo mkdir -p "$out_dir" + runFileOp mkdir -p "$out_dir" # "|FIELD,FIELD,..." — order here is the form's render order. local rows=( @@ -45,7 +45,6 @@ webuiGenerateBackupSchema() done json+="}}" - echo "$json" | sudo tee "$out_file" >/dev/null - createTouch "$out_file" "$docker_install_user" "silent" + echo "$json" | runFileWrite "$out_file" isSuccessful "Backup location schema regenerated" } diff --git a/scripts/webui/data/generators/backup/webui_backup_snapshots.sh b/scripts/webui/data/generators/backup/webui_backup_snapshots.sh index 3058630..b927202 100644 --- a/scripts/webui/data/generators/backup/webui_backup_snapshots.sh +++ b/scripts/webui/data/generators/backup/webui_backup_snapshots.sh @@ -4,7 +4,7 @@ webuiGenerateBackupSnapshots() { local scope="${1:-all}" local output_dir="$containers_dir/libreportal/frontend/data/backup/generated" - mkdir -p "$output_dir" + runFileOp mkdir -p "$output_dir" local indices=() if [[ "$scope" == "all" ]]; then @@ -32,9 +32,7 @@ webuiGenerateBackupSnapshots() content+="\"snapshots\":$raw" content+="}" - echo "$content" | sudo tee "$temp_file" >/dev/null - sudo mv "$temp_file" "$output_file" - createTouch "$output_file" "$docker_install_user" "silent" + echo "$content" | runFileWrite "$output_file" done isSuccessful "Snapshots JSON regenerated (${#indices[@]} location(s))" diff --git a/scripts/webui/data/generators/system/webui_ssh_access.sh b/scripts/webui/data/generators/system/webui_ssh_access.sh index 18063fb..656289b 100644 --- a/scripts/webui/data/generators/system/webui_ssh_access.sh +++ b/scripts/webui/data/generators/system/webui_ssh_access.sh @@ -8,7 +8,7 @@ webuiGenerateSshAccess() { local out_dir="$containers_dir/libreportal/frontend/data/ssh" local out_file="$out_dir/access.json" - sudo mkdir -p "$out_dir" + runFileOp mkdir -p "$out_dir" local jsonEscape jsonEscape() { @@ -45,7 +45,6 @@ webuiGenerateSshAccess() printf '{"generated_at":"%s","user":"%s","password_auth":%s,"keys":%s}\n' \ "$(date -Iseconds)" "$(jsonEscape "$user")" "$pw_auth" "$keys_json" \ - | sudo tee "$out_file" >/dev/null - createTouch "$out_file" "$docker_install_user" "silent" + | runFileWrite "$out_file" isSuccessful "SSH access snapshot regenerated" }