fix(rootless): apps/categories/config/system generators write as container owner
The remaining WebUI generators built JSON into a temp file inside the output dir then placed it with mv/sudo mv + a createTouch that can't re-own, so in rootless they produced root/libreportal-owned data and 'touch: Permission denied' spam. Two problems: the temp lived in the (now dockerinstall-owned) output dir, which the cron updater — running as libreportal — can't write; and the final file landed wrong-owned. Move each temp to mktemp (/tmp, writable by whoever runs the updater) and place the result via runFileWrite (writes as the container owner: dockerinstall in rootless, manager in rooted), dropping the redundant createTouch; convert the dir mkdirs to runFileOp. Covers apps (services/config/tools/app_status/gluetun/config_patch), categories (app/config-categories/field-mappings), config (configs.json) and system (info/memory/disk/update). The logs file is handled by the now mode-aware createFolders + createTouch. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> Signed-off-by: librelad <librelad@digitalangels.vip>
This commit is contained in:
parent
bd4887f889
commit
ed9697cdc0
@ -40,7 +40,7 @@ webuiUpdateAppStatus() {
|
||||
done < "$output_file"
|
||||
|
||||
# Replace the original file with the updated one
|
||||
mv "$temp_file" "$output_file"
|
||||
runFileWrite "$output_file" < "$temp_file"; rm -f "$temp_file"
|
||||
|
||||
if [[ "$found_app" == true ]]; then
|
||||
isSuccessful "Updated $app_name status to: $is_installed"
|
||||
|
||||
@ -40,8 +40,8 @@ webuiGenerateLibrePortalConfig() {
|
||||
isNotice "Generating LibrePortal apps.json from config files..."
|
||||
[[ -n "$specific_app" ]] && isNotice "Filtering to specific app: $specific_app"
|
||||
|
||||
mkdir -p "$(dirname "$output_file")"
|
||||
local temp_file="${output_file}.tmp.$$"
|
||||
runFileOp mkdir -p "$(dirname "$output_file")"
|
||||
local temp_file="$(mktemp)"
|
||||
|
||||
cat > "$temp_file" << 'EOF'
|
||||
{
|
||||
@ -163,7 +163,7 @@ EOF
|
||||
fi
|
||||
if [[ -n "$icon_file" ]]; then
|
||||
local icons_apps_dir="${containers_dir}libreportal/frontend/icons/apps"
|
||||
mkdir -p "$icons_apps_dir"
|
||||
runFileOp mkdir -p "$icons_apps_dir"
|
||||
cp "$dir/$icon_file" "$icons_apps_dir/$icon_file" 2>/dev/null
|
||||
fi
|
||||
|
||||
@ -239,8 +239,7 @@ EOF
|
||||
|
||||
# Already root via start.sh — drop redundant sudo.
|
||||
if [ $? -eq 0 ]; then
|
||||
mv "$temp_file" "$output_file"
|
||||
createTouch "$output_file" "$docker_install_user" "silent"
|
||||
runFileWrite "$output_file" < "$temp_file"; rm -f "$temp_file"
|
||||
else
|
||||
rm -f "$temp_file" 2>/dev/null
|
||||
fi
|
||||
|
||||
@ -53,11 +53,11 @@ webuiPatchAppConfigJson() {
|
||||
[[ "$row" == "1" ]] && installed="true"
|
||||
fi
|
||||
|
||||
local tmp="${apps_json}.tmp.$$"
|
||||
local tmp="$(mktemp)"
|
||||
if jq --arg slug "$app_name" --argjson cfg "$cfg_json" --argjson inst "$installed" '
|
||||
.apps |= map(if ((.command // "") | endswith(" " + $slug)) then .config = $cfg | .installed = $inst else . end)
|
||||
' "$apps_json" > "$tmp" 2>/dev/null; then
|
||||
sudo mv "$tmp" "$apps_json"
|
||||
runFileWrite "$apps_json" < "$tmp"; rm -f "$tmp"
|
||||
sudo chown "$docker_install_user:$docker_install_user" "$apps_json" 2>/dev/null || true
|
||||
return 0
|
||||
fi
|
||||
|
||||
@ -8,10 +8,10 @@
|
||||
webuiGenerateGluetunProviders() {
|
||||
local output_file="${containers_dir}libreportal/frontend/data/apps/generated/gluetun-providers.json"
|
||||
local upstream="https://raw.githubusercontent.com/qdm12/gluetun/master/internal/storage/servers.json"
|
||||
local tmp="${output_file}.tmp.$$"
|
||||
local tmp="$(mktemp)"
|
||||
local raw="${output_file}.raw.$$"
|
||||
|
||||
mkdir -p "$(dirname "$output_file")"
|
||||
runFileOp mkdir -p "$(dirname "$output_file")"
|
||||
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
isNotice "jq not installed; skipping gluetun provider refresh."
|
||||
@ -70,8 +70,7 @@ webuiGenerateGluetunProviders() {
|
||||
rm -f "$raw"
|
||||
|
||||
if [ -s "$tmp" ]; then
|
||||
mv "$tmp" "$output_file"
|
||||
createTouch "$output_file" "$docker_install_user" "silent"
|
||||
runFileWrite "$output_file" < "$tmp"; rm -f "$tmp"
|
||||
[[ -n "$new_etag" ]] && echo "$new_etag" | tee "$etag_file" >/dev/null
|
||||
isSuccessful "Refreshed gluetun provider snapshot ($(jq '.providers | length' "$output_file") providers)."
|
||||
else
|
||||
|
||||
@ -10,10 +10,10 @@ webuiGenerateAppsServicesConfig() {
|
||||
|
||||
isNotice "Generating apps-services.json from database..."
|
||||
|
||||
mkdir -p "$(dirname "$output_file")"
|
||||
runFileOp mkdir -p "$(dirname "$output_file")"
|
||||
|
||||
# Create temp file first, then atomic move
|
||||
local temp_file="${output_file}.tmp.$$"
|
||||
local temp_file="$(mktemp)"
|
||||
|
||||
# Create header
|
||||
cat > "$temp_file" << 'EOF'
|
||||
@ -27,8 +27,7 @@ EOF
|
||||
# Write empty array and close
|
||||
echo " ]" >> "$temp_file"
|
||||
echo "}" >> "$temp_file"
|
||||
mv "$temp_file" "$output_file"
|
||||
createTouch "$output_file" "$docker_install_user" "silent"
|
||||
runFileWrite "$output_file" < "$temp_file"; rm -f "$temp_file"
|
||||
fi
|
||||
|
||||
# Get all installed apps
|
||||
@ -39,8 +38,7 @@ EOF
|
||||
# Write empty array and close
|
||||
echo " ]" >> "$temp_file"
|
||||
echo "}" >> "$temp_file"
|
||||
mv "$temp_file" "$output_file"
|
||||
createTouch "$output_file" "$docker_install_user" "silent"
|
||||
runFileWrite "$output_file" < "$temp_file"; rm -f "$temp_file"
|
||||
fi
|
||||
|
||||
# Process each app
|
||||
@ -292,8 +290,7 @@ EOF
|
||||
# No services found, write empty entry
|
||||
echo " ]" >> "$temp_file"
|
||||
echo "}" >> "$temp_file"
|
||||
mv "$temp_file" "$output_file"
|
||||
createTouch "$output_file" "$docker_install_user" "silent"
|
||||
runFileWrite "$output_file" < "$temp_file"; rm -f "$temp_file"
|
||||
echo "No services found to generate"
|
||||
fi
|
||||
|
||||
@ -302,9 +299,8 @@ EOF
|
||||
|
||||
# Atomic move to final location
|
||||
if [ $? -eq 0 ]; then
|
||||
mv "$temp_file" "$output_file"
|
||||
runFileWrite "$output_file" < "$temp_file"; rm -f "$temp_file"
|
||||
# Set proper ownership for web UI access
|
||||
createTouch "$output_file" "$docker_install_user" "silent"
|
||||
else
|
||||
rm -f "$temp_file" 2>/dev/null
|
||||
fi
|
||||
|
||||
@ -45,9 +45,9 @@ webuiGenerateAppsToolsConfig() {
|
||||
fi
|
||||
|
||||
local output_file="${containers_dir}libreportal/frontend/data/apps/generated/apps-tools.json"
|
||||
local tmp="${output_file}.tmp.$$"
|
||||
local tmp="$(mktemp)"
|
||||
|
||||
mkdir -p "$(dirname "$output_file")"
|
||||
runFileOp mkdir -p "$(dirname "$output_file")"
|
||||
|
||||
# Heredoc carries the JSON literal verbatim — much easier to edit
|
||||
# than escaping nested quotes through jq -n.
|
||||
@ -422,7 +422,6 @@ JSON
|
||||
fi
|
||||
fi
|
||||
|
||||
mv "$tmp" "$output_file"
|
||||
createTouch "$output_file" "$docker_install_user" "silent"
|
||||
runFileWrite "$output_file" < "$tmp"; rm -f "$tmp"
|
||||
isSuccessful "Generated apps-tools.json ($(jq '[.apps[].tools | length] | add' "$output_file" 2>/dev/null || echo "?") tool(s) across $(jq '.apps | length' "$output_file" 2>/dev/null || echo "?") app(s))."
|
||||
}
|
||||
|
||||
@ -13,10 +13,10 @@ webuiCreateAppsCategories() {
|
||||
echo "Generating apps-categories.json..."
|
||||
|
||||
# Create output directory if it doesn't exist
|
||||
mkdir -p "$output_dir"
|
||||
runFileOp mkdir -p "$output_dir"
|
||||
|
||||
# Create temp file first, then atomic move
|
||||
local temp_file="${output_dir}/apps-categories.json.tmp.$$"
|
||||
local temp_file="$(mktemp)"
|
||||
local final_file="${output_dir}/apps-categories.json"
|
||||
|
||||
# Generate apps-categories.json for data-loader.js
|
||||
@ -95,9 +95,7 @@ EOF
|
||||
|
||||
# Atomic move to final location
|
||||
if [ $? -eq 0 ]; then
|
||||
sudo mv "$temp_file" "$final_file"
|
||||
# Set proper ownership for web UI access using createTouch
|
||||
createTouch "$final_file" "$docker_install_user" "silent"
|
||||
runFileWrite "$final_file" < "$temp_file"; rm -f "$temp_file"
|
||||
else
|
||||
rm -f "$temp_file" 2>/dev/null
|
||||
fi
|
||||
|
||||
@ -13,10 +13,10 @@ webuiCreateAppsConfigCategories() {
|
||||
echo "Creating apps-config-categories.json..."
|
||||
|
||||
# Create output directory if it doesn't exist
|
||||
mkdir -p "$output_dir"
|
||||
runFileOp mkdir -p "$output_dir"
|
||||
|
||||
# Create temp file first, then atomic move
|
||||
local temp_file="${output_dir}/apps-config-categories.json.tmp.$$"
|
||||
local temp_file="$(mktemp)"
|
||||
local final_file="${output_dir}/apps-config-categories.json"
|
||||
|
||||
# Generate apps-config-categories.json
|
||||
@ -77,9 +77,7 @@ EOF
|
||||
|
||||
# Atomic move to final location
|
||||
if [ $? -eq 0 ]; then
|
||||
sudo mv "$temp_file" "$final_file"
|
||||
# Set proper ownership for web UI access using createTouch
|
||||
createTouch "$final_file" "$docker_install_user" "silent"
|
||||
runFileWrite "$final_file" < "$temp_file"; rm -f "$temp_file"
|
||||
else
|
||||
rm -f "$temp_file" 2>/dev/null
|
||||
fi
|
||||
|
||||
@ -13,10 +13,10 @@ webuiCreateAppFieldMappings() {
|
||||
echo "Creating apps-field-mappings.json..."
|
||||
|
||||
# Create output directory if it doesn't exist
|
||||
mkdir -p "$output_dir"
|
||||
runFileOp mkdir -p "$output_dir"
|
||||
|
||||
# Create temp file first, then atomic move
|
||||
local temp_file="${output_dir}/apps-field-mappings.json.tmp.$$"
|
||||
local temp_file="$(mktemp)"
|
||||
local final_file="${output_dir}/apps-field-mappings.json"
|
||||
|
||||
# Generate the complete JSON in one go
|
||||
@ -804,9 +804,7 @@ RESTEOF
|
||||
install_name_safe=$(printf '%s' "${CFG_INSTALL_NAME:-LibrePortal}" \
|
||||
| sed -e 's/[\/&]/\\&/g' -e 's/"/\\"/g')
|
||||
sed -i "s|__INSTALL_NAME__|${install_name_safe}|g" "$temp_file"
|
||||
mv "$temp_file" "$final_file"
|
||||
# Set proper ownership for web UI access using createTouch
|
||||
createTouch "$final_file" "$docker_install_user" "silent"
|
||||
runFileWrite "$final_file" < "$temp_file"; rm -f "$temp_file"
|
||||
else
|
||||
rm -f "$temp_file" 2>/dev/null
|
||||
fi
|
||||
|
||||
@ -79,9 +79,9 @@ webuiGenerateSystemConfigs() {
|
||||
}
|
||||
|
||||
local output_file="${containers_dir}libreportal/frontend/data/config/generated/configs.json"
|
||||
local temp_file="${output_file}.tmp.$$"
|
||||
local temp_file="$(mktemp)"
|
||||
|
||||
mkdir -p "$(dirname "$output_file")"
|
||||
runFileOp mkdir -p "$(dirname "$output_file")"
|
||||
|
||||
# Cache category metadata in one read pass per .category file.
|
||||
# Previously each of TITLE/DESCRIPTION/ICON/SUBCATEGORY_ORDER/ORDER cost
|
||||
@ -367,8 +367,7 @@ EOF
|
||||
|
||||
# Already running as root via start.sh — sudo was redundant overhead.
|
||||
if [ $? -eq 0 ]; then
|
||||
mv "$temp_file" "$output_file"
|
||||
createTouch "$output_file" "$docker_install_user" "silent"
|
||||
runFileWrite "$output_file" < "$temp_file"; rm -f "$temp_file"
|
||||
else
|
||||
rm -f "$temp_file" 2>/dev/null
|
||||
fi
|
||||
|
||||
@ -31,7 +31,7 @@ webuiSystemDisk() {
|
||||
createFolders "quiet" $sudo_user_name "$system_dir"
|
||||
|
||||
# Create temp file first, then atomic move
|
||||
local temp_file="${system_dir}/disk_usage.json.tmp.$$"
|
||||
local temp_file="$(mktemp)"
|
||||
local final_file="${system_dir}/disk_usage.json"
|
||||
|
||||
# Output JSON to temp file
|
||||
@ -55,9 +55,7 @@ EOF
|
||||
|
||||
# Atomic move to final location
|
||||
if [ $? -eq 0 ]; then
|
||||
mv "$temp_file" "$final_file"
|
||||
# Set proper ownership for web UI access using createTouch
|
||||
createTouch "$final_file" "$sudo_user_name" "silent"
|
||||
runFileWrite "$final_file" < "$temp_file"; rm -f "$temp_file"
|
||||
else
|
||||
rm -f "$temp_file" 2>/dev/null
|
||||
fi
|
||||
|
||||
@ -35,7 +35,7 @@ webuiSystemInfo() {
|
||||
createFolders "quiet" $sudo_user_name "$system_dir"
|
||||
|
||||
# Create temp file first, then atomic move
|
||||
local temp_file="${system_dir}/system_info.json.tmp.$$"
|
||||
local temp_file="$(mktemp)"
|
||||
local final_file="${system_dir}/system_info.json"
|
||||
|
||||
# Output JSON to temp file
|
||||
@ -52,9 +52,7 @@ EOF
|
||||
|
||||
# Atomic move to final location
|
||||
if [ $? -eq 0 ]; then
|
||||
mv "$temp_file" "$final_file"
|
||||
# Set proper ownership for web UI access using createTouch
|
||||
createTouch "$final_file" "$sudo_user_name" "silent"
|
||||
runFileWrite "$final_file" < "$temp_file"; rm -f "$temp_file"
|
||||
else
|
||||
rm -f "$temp_file" 2>/dev/null
|
||||
fi
|
||||
|
||||
@ -27,7 +27,7 @@ webuiSystemMemory() {
|
||||
createFolders "quiet" $sudo_user_name "$system_dir"
|
||||
|
||||
# Create temp file first, then atomic move
|
||||
local temp_file="${system_dir}/memory_usage.json.tmp.$$"
|
||||
local temp_file="$(mktemp)"
|
||||
local final_file="${system_dir}/memory_usage.json"
|
||||
|
||||
# Output JSON to temp file
|
||||
@ -49,9 +49,7 @@ EOF
|
||||
|
||||
# Atomic move to final location
|
||||
if [ $? -eq 0 ]; then
|
||||
mv "$temp_file" "$final_file"
|
||||
# Set proper ownership for web UI access using createTouch
|
||||
createTouch "$final_file" "$sudo_user_name" "silent"
|
||||
runFileWrite "$final_file" < "$temp_file"; rm -f "$temp_file"
|
||||
else
|
||||
rm -f "$temp_file" 2>/dev/null
|
||||
fi
|
||||
|
||||
@ -70,7 +70,7 @@ webuiSystemUpdateCheck() {
|
||||
local _error_json="null"
|
||||
[[ -n "$_error" ]] && _error_json="\"$_error\""
|
||||
|
||||
local temp_file="${final_file}.tmp.$$"
|
||||
local temp_file="$(mktemp)"
|
||||
cat << EOF > "$temp_file"
|
||||
{
|
||||
"update_available": ${_update_available},
|
||||
@ -91,8 +91,7 @@ webuiSystemUpdateCheck() {
|
||||
}
|
||||
EOF
|
||||
if [ $? -eq 0 ]; then
|
||||
mv "$temp_file" "$final_file"
|
||||
createTouch "$final_file" "$sudo_user_name" "silent"
|
||||
runFileWrite "$final_file" < "$temp_file"; rm -f "$temp_file"
|
||||
else
|
||||
rm -f "$temp_file" 2>/dev/null
|
||||
fi
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user