diff --git a/scripts/cloudron-memlimits.sh b/scripts/cloudron-memlimits.sh index f778361..5e79e29 100755 --- a/scripts/cloudron-memlimits.sh +++ b/scripts/cloudron-memlimits.sh @@ -1,6 +1,6 @@ #!/bin/sh # cloudron-memlimits.sh - right-size app memory limits [#685] -# Reads CLOUDRON_URL + CLOUDRON_TOKEN from ~/.creds/cloudron.env +# Reads CLOUDRON_URL + CLOUDRON_API_TOKEN from ~/.creds/cloudron.env # Usage: cloudron-memlimits.sh [canary|apply|plan] # plan (default) show current vs proposed, change nothing # canary apply axiosheartstudios.com only, then verify @@ -10,7 +10,7 @@ set -eu ENV_FILE="$HOME/.creds/cloudron.env" [ -f "$ENV_FILE" ] || { echo "missing $ENV_FILE"; exit 1; } . "$ENV_FILE" -: "${CLOUDRON_URL:?}" "${CLOUDRON_TOKEN:?}" +: "${CLOUDRON_URL:?}" "${CLOUDRON_API_TOKEN:?}" MODE="${1:-plan}" @@ -20,7 +20,9 @@ axiosheartstudios.com:512 community.turnsys.com:4096 nextcloud.knownelement.com:2048 bookmarks.knownelement.com:2048 -photos.knownelement.com:3072 +# photos floor: Immich package manifest declares 3758096384B (3584MB) as +# the minimum user-settable limit; the census's 3072 was unsatisfiable +photos.knownelement.com:3584 share.knownelement.com:512 hfnfc.net:512 notes.knownelement.com:1024 @@ -35,20 +37,37 @@ cmdb.knownelement.com:512 " api() { - curl -s -m 30 -H "Authorization: Bearer $CLOUDRON_TOKEN" "$@" + curl -s -m 30 -H "Authorization: Bearer $CLOUDRON_API_TOKEN" "$@" } -APPS=$(api "$CLOUDRON_URL/api/v1/apps?per_page=200") -echo "$APPS" | jq -e '.apps' >/dev/null 2>&1 || { echo "API auth/list failed"; exit 1; } +# fetch to a temp file with one retry: the 1MB apps list occasionally +# stalls mid-transfer, and a truncated body would otherwise fail jq later +fetch_apps() { + local attempt out + for attempt in 1 2; do + out=$(api -o "$APPS_JSON" -w '%{http_code} %{size_download}' \ + "$CLOUDRON_URL/api/v1/apps?per_page=200") || true + [ "${out%% *}" = "200" ] && [ "${out##* }" -gt 1000 ] && return 0 + echo "fetch attempt $attempt failed ($out), retrying" >&2 + sleep 2 + done + return 1 +} +APPS_JSON="/tmp/cloudron-apps.$$.json" +trap 'rm -f "$APPS_JSON"' EXIT +fetch_apps || { echo "API auth/list failed"; exit 1; } +jq -e '.apps' "$APPS_JSON" >/dev/null 2>&1 || { echo "API auth/list failed (bad JSON)"; exit 1; } reclaimed=0 for line in $PROPOSALS; do fqdn=${line%%:*}; newmb=${line##*:} - match=$(echo "$APPS" | jq -r --arg f "https://$fqdn" \ - '.apps[] | select(.fqdn==$f or (.domain==$f and (.location=="" or .location=="@"))) | "\(.id) \(.memoryLimit)"' | head -1) + # API returns bare hosts in .fqdn (no scheme); subdomain apps carry + # location:null, so matching fqdn alone is sufficient + match=$(jq -r --arg f "$fqdn" \ + '.apps[] | select(.fqdn==$f) | "\(.id) \(.memoryLimit)"' "$APPS_JSON" | head -1) [ -z "$match" ] && { echo "SKIP $fqdn (not an app)"; continue; } appid=${match%% *}; curmb=$(( ${match##* } / 1048576 )) - [ "$curmb" -le "$newmb" ] && { echo "SKIP $fqdn (already $curmbMB <= $newmb)"; continue; } + [ "$curmb" -le "$newmb" ] && { echo "SKIP $fqdn (already ${curmb}MB <= ${newmb}MB)"; continue; } case "$MODE" in plan) @@ -57,12 +76,12 @@ for line in $PROPOSALS; do canary) [ "$fqdn" != "axiosheartstudios.com" ] && continue echo "CANARY $fqdn: ${curmb}MB -> ${newmb}MB" - api -X POST "$CLOUDRON_URL/api/v1/apps/$appid" -H 'Content-Type: application/json' \ + api -X POST "$CLOUDRON_URL/api/v1/apps/$appid/configure/memory_limit" -H 'Content-Type: application/json' \ -d "{\"memoryLimit\": $((newmb * 1048576))}" | jq -r '.status // .message // .' ;; apply) echo "APPLY $fqdn: ${curmb}MB -> ${newmb}MB" - api -X POST "$CLOUDRON_URL/api/v1/apps/$appid" -H 'Content-Type: application/json' \ + api -X POST "$CLOUDRON_URL/api/v1/apps/$appid/configure/memory_limit" -H 'Content-Type: application/json' \ -d "{\"memoryLimit\": $((newmb * 1048576))}" | jq -r '.status // .message // .' sleep 8 ;;