c885b5acf4
Measured on demo-felhom (N100, 16 GB): 61 apps deployed and removed through the product, sampled every 5 s; heavy apps with one real action (Immich 200 photos, Jellyfin transcode, Paperless OCR, Nextcloud 500 MB, ...). sizing.json for the estimator (tools/build_sizing.py), SIZING.md with the proposed minimum machine, METHOD.md. Power is RAPL CPU-package only (no meter). R-993 asks the operator to approve the minimum machine; R-994 lists catalog limits/requests that are too low; R-995 records leftover unnamed volumes after removal. R-231's dated check measured: dooplex-backup.service success, journal green.
116 lines
7.4 KiB
Bash
116 lines
7.4 KiB
Bash
#!/bin/bash
|
|
# actions.sh APP — one real action per heavy app, run INSIDE the guest, through the app's own front
|
|
# door (its container address on the Docker network; the setup gate sits in traefik, in front of it).
|
|
# Content is in /root/sizing/content (made by make_content.py: generated or CC0, no personal data).
|
|
# PW (env) is a password generated for this run; it is never printed and never stored.
|
|
# Every line it prints is evidence; it prints no secret.
|
|
set -u
|
|
APP=${1:?app}; C=/root/sizing/content; PW=${PW:?pw}
|
|
ip() { docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}' "$1" | awk '{print $1}'; }
|
|
src() { docker inspect -f "{{range .Mounts}}{{if eq .Destination \"$2\"}}{{.Source}}{{end}}{{end}}" "$1"; }
|
|
waitup() { for i in $(seq 1 120); do c=$(curl -s -o /dev/null -w '%{http_code}' "$1"); case $c in 200|302|401) echo "up after $((i*5))s ($c)"; return 0;; esac; sleep 5; done; echo "never up ($c)"; return 1; }
|
|
ts() { echo "@$(date +%s) $*"; }
|
|
|
|
case $APP in
|
|
immich)
|
|
U=http://$(ip immich-server):2283; waitup $U/api/server/ping
|
|
curl -s -o /dev/null -w 'admin-sign-up %{http_code}\n' -X POST $U/api/auth/admin-sign-up -H 'Content-Type: application/json' \
|
|
-d "{\"email\":\"bench@example.invalid\",\"password\":\"$PW\",\"name\":\"Bench\"}"
|
|
T=$(curl -s -X POST $U/api/auth/login -H 'Content-Type: application/json' -d "{\"email\":\"bench@example.invalid\",\"password\":\"$PW\"}" | grep -o '"accessToken":"[^"]*"' | cut -d'"' -f4)
|
|
echo "token length ${#T}"
|
|
ts action_start upload
|
|
ok=0; bad=0
|
|
for f in $C/photos/*.jpg; do
|
|
c=$(curl -s -o /tmp/immich-up.json -w '%{http_code}' -H "Authorization: Bearer $T" -F "deviceAssetId=$(basename $f)" -F deviceId=bench \
|
|
-F fileCreatedAt=2026-01-01T00:00:00.000Z -F fileModifiedAt=2026-01-01T00:00:00.000Z -F "assetData=@$f" $U/api/assets)
|
|
case $c in 200|201) ok=$((ok+1));; *) bad=$((bad+1)); [ $bad -le 3 ] && echo "upload $(basename $f) -> $c $(head -c 200 /tmp/immich-up.json)";; esac
|
|
done
|
|
ts upload_done ok=$ok bad=$bad
|
|
# wait until every job queue is empty (thumbnails, metadata, smart search, faces)
|
|
for i in $(seq 1 720); do
|
|
J=$(curl -s -H "Authorization: Bearer $T" $U/api/jobs); [ -z "$J" ] || echo "$J" | grep -q '"statusCode":404' && J=$(curl -s -H "Authorization: Bearer $T" $U/api/queues)
|
|
n=$(echo "$J" | grep -oE '"(active|waiting|delayed)":[0-9]+' | awk -F: '{s+=$2} END{print s+0}')
|
|
[ $((i % 12)) -eq 1 ] && echo " t=$((i*5))s jobs pending=$n"
|
|
if [ "$n" = 0 ] && [ $i -gt 6 ]; then break; fi
|
|
sleep 5
|
|
done
|
|
ts action_end jobs-idle
|
|
curl -s -H "Authorization: Bearer $T" $U/api/server/statistics | head -c 300; echo
|
|
curl -s -H "Authorization: Bearer $T" "$U/api/people" | grep -oE '"total":[0-9]+' | head -1
|
|
;;
|
|
jellyfin)
|
|
U=http://$(ip jellyfin):8096; waitup $U/health
|
|
M=$(src jellyfin /media); echo "media source: $M"; mkdir -p "$M/movies"; cp -r "$C/video/Bench Films/." "$M/movies/"; ls "$M/movies"
|
|
H='Content-Type: application/json'
|
|
curl -s -o /dev/null -w 'startup-config %{http_code}\n' -X POST $U/Startup/Configuration -H "$H" -d '{"UICulture":"en-US","MetadataCountryCode":"US","PreferredMetadataLanguage":"en"}'
|
|
curl -s -o /dev/null $U/Startup/User
|
|
curl -s -o /dev/null -w 'startup-user %{http_code}\n' -X POST $U/Startup/User -H "$H" -d "{\"Name\":\"bench\",\"Password\":\"$PW\"}"
|
|
curl -s -o /dev/null -w 'startup-complete %{http_code}\n' -X POST $U/Startup/Complete
|
|
A='Authorization: MediaBrowser Client="bench", Device="bench", DeviceId="sizing-bench", Version="1.0"'
|
|
T=$(curl -s -X POST $U/Users/AuthenticateByName -H "$H" -H "$A" -d "{\"Username\":\"bench\",\"Pw\":\"$PW\"}" | grep -o '"AccessToken":"[^"]*"' | cut -d'"' -f4)
|
|
echo "token length ${#T}"
|
|
ts action_start scan
|
|
curl -s -o /dev/null -w 'add-library %{http_code}\n' -X POST "$U/Library/VirtualFolders?name=Films&collectionType=movies&refreshLibrary=true" \
|
|
-H "$H" -H "X-Emby-Token: $T" -d '{"LibraryOptions":{"PathInfos":[{"Path":"/media/movies"}]}}'
|
|
for i in $(seq 1 120); do
|
|
n=$(curl -s -H "X-Emby-Token: $T" "$U/Items?Recursive=true&IncludeItemTypes=Movie" | grep -oE '"TotalRecordCount":[0-9]+' | cut -d: -f2)
|
|
run=$(curl -s -H "X-Emby-Token: $T" "$U/ScheduledTasks?IsHidden=false" | grep -o '"State":"Running"' | wc -l)
|
|
[ "${n:-0}" -ge 3 ] && [ "$run" = 0 ] && break; sleep 5
|
|
done
|
|
ts scan_done movies=${n:-0}
|
|
ID=$(curl -s -H "X-Emby-Token: $T" "$U/Items?Recursive=true&IncludeItemTypes=Movie&Limit=1" | grep -oE '"Id":"[0-9a-f]{32}"' | head -1 | cut -d'"' -f4)
|
|
ts transcode_start
|
|
curl -s -o /tmp/jf.mp4 -w 'transcode %{http_code} %{size_download} bytes %{time_total}s\n' \
|
|
"$U/Videos/$ID/stream.mp4?static=false&mediaSourceId=$ID&videoCodec=h264&audioCodec=aac&maxWidth=1920&videoBitRate=3000000&audioBitRate=128000&api_key=$T"
|
|
rm -f /tmp/jf.mp4
|
|
ts action_end transcode_done
|
|
;;
|
|
paperless-ngx)
|
|
U=http://$(ip paperless-webserver):8000; waitup $U/api/
|
|
for i in $(seq 1 60); do c=$(curl -s -o /dev/null -w '%{http_code}' -u "admin:$PW" $U/api/documents/); [ "$c" = 200 ] && break; sleep 5; done; echo "api auth $c"
|
|
ts action_start ocr
|
|
for f in $C/pdfs/*.pdf; do curl -s -o /dev/null -w "%{http_code} " -u "admin:$PW" -F "document=@$f" $U/api/documents/post_document/; done; echo
|
|
for i in $(seq 1 360); do
|
|
n=$(curl -s -u "admin:$PW" "$U/api/documents/?page_size=1" | grep -oE '"count":[0-9]+' | cut -d: -f2)
|
|
p=$(curl -s -u "admin:$PW" "$U/api/tasks/" | grep -oE '"status":"(PENDING|STARTED)"' | wc -l)
|
|
[ $((i % 12)) -eq 1 ] && echo " t=$((i*5))s documents=${n:-0} pending=$p"
|
|
[ "${n:-0}" -ge 20 ] && [ "$p" = 0 ] && break; sleep 5
|
|
done
|
|
ts action_end documents=${n:-0}
|
|
;;
|
|
nextcloud)
|
|
NIP=$(ip nextcloud); TD=$(docker inspect -f '{{range .Config.Env}}{{println .}}{{end}}' nextcloud | sed -n 's/^NEXTCLOUD_TRUSTED_DOMAINS=//p' | awk '{print $1}')
|
|
echo "trusted domain: $TD"
|
|
for i in $(seq 1 120); do s=$(curl -s -H "Host: $TD" http://$NIP/status.php); echo "$s" | grep -q '"installed":true' && break; sleep 5; done
|
|
echo "status: $(echo $s | head -c 160)"
|
|
D="http://$NIP/remote.php/dav/files/admin"
|
|
ts action_start upload
|
|
curl -s -o /dev/null -w 'mkcol %{http_code}\n' -X MKCOL -H "Host: $TD" -u "admin:$PW" $D/bench
|
|
ok=0; for f in $C/upload/*.bin; do c=$(curl -s -o /dev/null -w '%{http_code}' -H "Host: $TD" -u "admin:$PW" -T "$f" "$D/bench/$(basename $f)"); case $c in 201|204) ok=$((ok+1));; *) echo "put $(basename $f) -> $c";; esac; done
|
|
ts action_end uploaded=$ok
|
|
;;
|
|
home-assistant)
|
|
U=http://$(ip home-assistant):8123; waitup $U/
|
|
ts action_start onboarding
|
|
curl -s -o /dev/null -w 'onboarding-user %{http_code}\n' -X POST $U/api/onboarding/users -H 'Content-Type: application/json' \
|
|
-d "{\"client_id\":\"$U/\",\"name\":\"Bench\",\"username\":\"bench\",\"password\":\"$PW\",\"language\":\"en\"}"
|
|
ts action_end onboarding
|
|
;;
|
|
calibre-web)
|
|
I=$(src calibre-web /cwa-book-ingest); L=$(src calibre-web /calibre-library); echo "ingest: $I library: $L"
|
|
waitup http://$(ip calibre-web):8083/
|
|
ts action_start ingest
|
|
cp $C/books/*.epub "$I/"
|
|
for i in $(seq 1 240); do n=$(ls "$I" 2>/dev/null | grep -c epub); [ "$n" = 0 ] && break; sleep 5; done
|
|
sleep 20
|
|
ts action_end ingest-folder-left=$n library-epubs=$(find "$L" -name '*.epub' 2>/dev/null | wc -l)
|
|
;;
|
|
romm)
|
|
R=$(src romm /romm/library); echo "library: $R"; waitup http://$(ip romm):8080/
|
|
ts action_start copy-library
|
|
mkdir -p "$R/roms"; cp -r $C/roms/. "$R/roms/"; find "$R" -type f | wc -l
|
|
ts action_end copied-only-scan-not-triggered
|
|
;;
|
|
*) echo "no action for $APP";;
|
|
esac
|