diff --git a/scripts/config/password/bcrypt/password_export_bcrypt.sh b/scripts/config/password/bcrypt/password_export_bcrypt.sh index 0bf3571..f5a669b 100755 --- a/scripts/config/password/bcrypt/password_export_bcrypt.sh +++ b/scripts/config/password/bcrypt/password_export_bcrypt.sh @@ -20,7 +20,7 @@ exportBcryptPassword() # Extract the correct variable name (e.g., PASSWORD_HASH) before the placeholder local variable_name - variable_name=$(runInstallOp awk -F= '/'"$placeholder"'/ { gsub(/^[ \t-]+/, "", $1); print $1; exit }' "$file") + variable_name=$(runCfgOp awk -F= '/'"$placeholder"'/ { gsub(/^[ \t-]+/, "", $1); print $1; exit }' "$file") if [ -n "$variable_name" ]; then # Remove old password entries for this app & variable diff --git a/scripts/config/password/bcrypt/password_process_bcrypt.sh b/scripts/config/password/bcrypt/password_process_bcrypt.sh index 776017d..40a9408 100755 --- a/scripts/config/password/bcrypt/password_process_bcrypt.sh +++ b/scripts/config/password/bcrypt/password_process_bcrypt.sh @@ -8,7 +8,7 @@ processBcryptPassword() # Extract the variable name before the placeholder local variable_name - variable_name=$(runInstallOp awk -F= '/'"$placeholder"'/ { gsub(/^[ \t-]+/, "", $1); print $1; exit }' "$file") + variable_name=$(runCfgOp awk -F= '/'"$placeholder"'/ { gsub(/^[ \t-]+/, "", $1); print $1; exit }' "$file") if [ -z "$variable_name" ]; then isError " Could not extract variable name before $placeholder." @@ -35,11 +35,11 @@ processBcryptPassword() # Remove any single quotes from the bcrypt hash bcrypt_password=$(echo "$bcrypt_password" | tr -d "'") - local result=$(runInstallOp sed -i -E "s#$placeholder#$bcrypt_password#g" "$file") + local result=$(runCfgOp sed -i -E "s#$placeholder#$bcrypt_password#g" "$file") checkSuccess "Use sed to replace placeholder with bcrypt hash" # Verify replacement - if runInstallOp grep -q "$bcrypt_password" "$file"; then + if runCfgOp grep -q "$bcrypt_password" "$file"; then isSuccessful "Updated $variable_name in $(basename "$file")." else isError "ERROR: sed failed to replace $placeholder in $file." diff --git a/scripts/config/password/bcrypt/password_replace_bcrypt.sh b/scripts/config/password/bcrypt/password_replace_bcrypt.sh index 3440024..6cb337d 100755 --- a/scripts/config/password/bcrypt/password_replace_bcrypt.sh +++ b/scripts/config/password/bcrypt/password_replace_bcrypt.sh @@ -7,7 +7,7 @@ replaceBcryptPasswords() app_name=$(basename "$(dirname "$file")") # Only scan for bcrypt placeholders that actually exist in the file - local existing_placeholders=$(runInstallOp grep -oE 'RANDOMIZEDBCRYPTPASSWORD[0-9]*' "$file" 2>/dev/null | sort -u) + local existing_placeholders=$(runCfgOp grep -oE 'RANDOMIZEDBCRYPTPASSWORD[0-9]*' "$file" 2>/dev/null | sort -u) if [[ -n "$existing_placeholders" ]]; then while IFS= read -r placeholder; do diff --git a/scripts/config/password/password_replace hex.sh b/scripts/config/password/password_replace hex.sh index de15d9b..9947caa 100755 --- a/scripts/config/password/password_replace hex.sh +++ b/scripts/config/password/password_replace hex.sh @@ -5,7 +5,7 @@ replaceHexKeys() local file="$1" # Only scan for hex placeholders that actually exist in the file - local existing_placeholders=$(runInstallOp grep -oE 'RANDOMIZEDHEX[0-9]*' "$file" 2>/dev/null | sort -u) + local existing_placeholders=$(runCfgOp grep -oE 'RANDOMIZEDHEX[0-9]*' "$file" 2>/dev/null | sort -u) if [[ -n "$existing_placeholders" ]]; then while IFS= read -r placeholder; do @@ -13,7 +13,7 @@ replaceHexKeys() local hex_key hex_key=$(openssl rand -hex 32) - runInstallOp sed -i "s/${placeholder}/${hex_key}/g" "$file" + runCfgOp sed -i "s/${placeholder}/${hex_key}/g" "$file" checkSuccess "Updated ${placeholder} in $(basename "$file") with a new hex key." fi done <<< "$existing_placeholders" diff --git a/scripts/config/password/password_replace vapid.sh b/scripts/config/password/password_replace vapid.sh index 9bec4ff..9010a3e 100755 --- a/scripts/config/password/password_replace vapid.sh +++ b/scripts/config/password/password_replace vapid.sh @@ -5,7 +5,7 @@ replaceVAPIDKeys() local file="$1" # Only scan for VAPID placeholders that actually exist in the file - local existing_placeholders=$(runInstallOp grep -oE 'RANDOMIZEDVAPID[0-9]*' "$file" 2>/dev/null | sort -u) + local existing_placeholders=$(runCfgOp grep -oE 'RANDOMIZEDVAPID[0-9]*' "$file" 2>/dev/null | sort -u) if [[ -n "$existing_placeholders" ]]; then while IFS= read -r placeholder; do @@ -13,7 +13,7 @@ replaceVAPIDKeys() local vapid_key vapid_key=$(openssl rand -base64 32 | tr -d '+/=' | tr -cd '[:alnum:]') - runInstallOp sed -i "s/${placeholder}/${vapid_key}/g" "$file" + runCfgOp sed -i "s/${placeholder}/${vapid_key}/g" "$file" checkSuccess "Updated ${placeholder} in $(basename "$file") with a new VAPID key." fi done <<< "$existing_placeholders" diff --git a/scripts/config/password/password_replace.sh b/scripts/config/password/password_replace.sh index 216d874..eb92b8c 100755 --- a/scripts/config/password/password_replace.sh +++ b/scripts/config/password/password_replace.sh @@ -5,13 +5,13 @@ replacePlainPasswords() local file="$1" # Only scan for placeholders that actually exist in the file - local existing_placeholders=$(runInstallOp grep -oE 'RANDOMIZEDPASSWORD[0-9]+' "$file" 2>/dev/null | sort -u) + local existing_placeholders=$(runCfgOp grep -oE 'RANDOMIZEDPASSWORD[0-9]+' "$file" 2>/dev/null | sort -u) if [[ -n "$existing_placeholders" ]]; then while IFS= read -r password_placeholder; do if [[ -n "$password_placeholder" ]]; then local random_password=$(generateRandomPassword) - runInstallOp sed -i 's/'"${password_placeholder}"'/'"${random_password}"'/g' "$file" + runCfgOp sed -i 's/'"${password_placeholder}"'/'"${random_password}"'/g' "$file" checkSuccess "Updated ${password_placeholder} in $(basename "$file")." fi done <<< "$existing_placeholders" diff --git a/scripts/config/password/password_replace_appkey.sh b/scripts/config/password/password_replace_appkey.sh index dc20e4b..60d3fe4 100644 --- a/scripts/config/password/password_replace_appkey.sh +++ b/scripts/config/password/password_replace_appkey.sh @@ -8,14 +8,14 @@ replaceLaravelAppKeys() { local file="$1" - local existing_placeholders=$(runInstallOp grep -oE 'RANDOMIZEDAPPKEY[0-9]*' "$file" 2>/dev/null | sort -u) + local existing_placeholders=$(runCfgOp grep -oE 'RANDOMIZEDAPPKEY[0-9]*' "$file" 2>/dev/null | sort -u) if [[ -n "$existing_placeholders" ]]; then while IFS= read -r placeholder; do if [[ -n "$placeholder" ]]; then local app_key app_key="base64:$(openssl rand -base64 32)" - runInstallOp sed -i "s#${placeholder}#${app_key}#g" "$file" + runCfgOp sed -i "s#${placeholder}#${app_key}#g" "$file" checkSuccess "Updated ${placeholder} in $(basename "$file") with a new Laravel APP_KEY." fi done <<< "$existing_placeholders" diff --git a/scripts/config/password/password_user_replace.sh b/scripts/config/password/password_user_replace.sh index 6a1052f..0f7bedd 100755 --- a/scripts/config/password/password_user_replace.sh +++ b/scripts/config/password/password_user_replace.sh @@ -5,13 +5,13 @@ replaceRandomUsernames() local file="$1" # Only scan for placeholders that actually exist in the file - local existing_placeholders=$(runInstallOp grep -oE 'RANDOMIZEDUSERNAME[0-9]+' "$file" 2>/dev/null | sort -u) + local existing_placeholders=$(runCfgOp grep -oE 'RANDOMIZEDUSERNAME[0-9]+' "$file" 2>/dev/null | sort -u) if [[ -n "$existing_placeholders" ]]; then while IFS= read -r username_placeholder; do if [[ -n "$username_placeholder" ]]; then local random_username=$(generateRandomUsername) - runInstallOp sed -i 's/'"${username_placeholder}"'/'"${random_username}"'/g' "$file" + runCfgOp sed -i 's/'"${username_placeholder}"'/'"${random_username}"'/g' "$file" checkSuccess "Updated ${username_placeholder} in $(basename "$file")." fi done <<< "$existing_placeholders" diff --git a/scripts/docker/command/run_privileged.sh b/scripts/docker/command/run_privileged.sh index 3c8a914..ff59f4f 100644 --- a/scripts/docker/command/run_privileged.sh +++ b/scripts/docker/command/run_privileged.sh @@ -77,6 +77,23 @@ runInstallWrite() { runAsManager tee "${append_flag[@]}" "$dest" >/dev/null } +# Run a read/edit op against a CONFIG FILE, auto-selecting elevation by where the +# file lives: the container data-plane (/libreportal-containers, install-user-owned +# in rootless) -> runFileOp; the manager-owned control plane (configs/, the clone, +# backup-location configs) -> runInstallOp. The target file must be the LAST arg +# (true for the grep/sed/awk calls in the password replacers). Without this, +# sed -i EACCES'd its own temp file whenever the manager edited an app config +# copied into the container tree (the adguard.config "couldn't open temporary +# file" bug — the substitution silently failed, leaving the placeholder). +runCfgOp() { + local _file="${!#}" + if [[ -n "$containers_dir" && "$_file" == "$containers_dir"* ]]; then + runFileOp "$@" + else + runInstallOp "$@" + fi +} + # 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 diff --git a/scripts/source/files/arrays/function_manifest.sh b/scripts/source/files/arrays/function_manifest.sh index 85fa4a2..0def572 100644 --- a/scripts/source/files/arrays/function_manifest.sh +++ b/scripts/source/files/arrays/function_manifest.sh @@ -742,6 +742,7 @@ declare -gA LP_FN_MAP=( [runAsManager]="docker/command/run_privileged.sh" [runBackupOp]="docker/command/run_privileged.sh" [runBinInstall]="docker/command/run_privileged.sh" + [runCfgOp]="docker/command/run_privileged.sh" [runCrowdsec]="docker/command/run_privileged.sh" [runFileOp]="docker/command/run_privileged.sh" [runFileWrite]="docker/command/run_privileged.sh" @@ -1648,6 +1649,7 @@ declare -gA LP_FN_ROOT=( [runAsManager]="scripts" [runBackupOp]="scripts" [runBinInstall]="scripts" + [runCfgOp]="scripts" [runCrowdsec]="scripts" [runFileOp]="scripts" [runFileWrite]="scripts" @@ -2574,6 +2576,7 @@ runAppCfg() { source "${install_scripts_dir}docker/command/run_privileged.sh"; r runAsManager() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runAsManager "$@"; } runBackupOp() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runBackupOp "$@"; } runBinInstall() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runBinInstall "$@"; } +runCfgOp() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runCfgOp "$@"; } runCrowdsec() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runCrowdsec "$@"; } runFileOp() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runFileOp "$@"; } runFileWrite() { source "${install_scripts_dir}docker/command/run_privileged.sh"; runFileWrite "$@"; } diff --git a/scripts/webui/data/generators/updater/webui_updater_scan.sh b/scripts/webui/data/generators/updater/webui_updater_scan.sh index 0f2cc6b..b7af812 100644 --- a/scripts/webui/data/generators/updater/webui_updater_scan.sh +++ b/scripts/webui/data/generators/updater/webui_updater_scan.sh @@ -68,7 +68,12 @@ EOF { "generated_at": "$now", "apps": [${entries} ] } EOF - runFileOp cp "$tmp" "$out_dir/updates.json" 2>/dev/null || cp "$tmp" "$out_dir/updates.json" + # Write as the container user that owns out_dir. cp'ing the manager-owned + # mktemp would fail (the container user can't read a 600 /tmp file), so the + # old `runFileOp cp || cp` fell through to a manager cp that EACCES'd on the + # container-owned dir. runFileWrite reads the tmp in this (manager) shell and + # tees it as the container user — works in both modes. + runFileWrite "$out_dir/updates.json" < "$tmp" rm -f "$tmp" # CVE data — pluggable. Wire trivy/grype per image here and emit per-app @@ -76,14 +81,14 @@ EOF if [ ! -f "$out_dir/cves.json" ]; then local ctmp; ctmp="$(mktemp)" printf '{ "generated_at": "%s", "apps": [], "totals": { "critical": 0, "high": 0, "medium": 0, "low": 0 } }\n' "$now" > "$ctmp" - runFileOp cp "$ctmp" "$out_dir/cves.json" 2>/dev/null || cp "$ctmp" "$out_dir/cves.json" + runFileWrite "$out_dir/cves.json" < "$ctmp" rm -f "$ctmp" fi # Ensure a valid (possibly empty) history file exists for the WebUI. if [ ! -f "$out_dir/history.json" ]; then local htmp; htmp="$(mktemp)" printf '{ "entries": [] }\n' > "$htmp" - runFileOp cp "$htmp" "$out_dir/history.json" 2>/dev/null || cp "$htmp" "$out_dir/history.json" + runFileWrite "$out_dir/history.json" < "$htmp" rm -f "$htmp" fi