Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,24 @@ See [raspberry-pi-analyser.md](raspberry-pi-analyser.md) for the full setup, inc

The website exposes analyser endpoints:

- `GET /queue` returns the next app to process.
- `GET /queue` returns the next app to process in the response body and its one-use assignment token in the `X-Analysis-Claim-Token` response header.
- `GET /ping` marks the analyser online.
- `POST /uploadAnalysis` stores successful analysis results.
- `POST /reportAnalysisFailure` stores failed analysis results.
- `POST /uploadAnalysis` stores successful analysis results when sent with the assignment's `X-Analysis-Claim-Token` header.
- `POST /reportAnalysisFailure` stores failed analysis results when sent with the assignment's `X-Analysis-Claim-Token` header.

Analyzer requests authenticate with `Authorization: Bearer $UPLOAD_PASSWORD`.
Authentication and assignment tokens serve different purposes: the bearer token authorises the analyser, while the assignment token prevents an expired worker from overwriting a newer result.

### Claim-token rollout

The claim-token migration deliberately requeues every in-flight assignment that predates tokens. Use a coordinated deployment:

1. Stop all analyser queue processors.
2. Deploy the website and run `npm run migrate` (or start it with `npm run start`).
3. Deploy the matching `analyser/processQueue.sh` to every analyser host.
4. Restart the analysers.

The `/queue` response body remains a plain bundle ID, but older analyser scripts do not return the required claim header. Their completion requests will be rejected and must not remain running after the migration. Any interrupted work is safely available to the updated analysers because the migration requeues it.

Health checks:

Expand Down
51 changes: 40 additions & 11 deletions analyser/processQueue.sh
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ fi
curl_auth_config=$(mktemp)
chmod 600 "$curl_auth_config"
printf 'header = "Authorization: Bearer %s"\n' "$UPLOAD_PASSWORD" > "$curl_auth_config"
trap 'rm -f "$curl_auth_config"' EXIT
queue_headers=$(mktemp)
chmod 600 "$queue_headers"
trap 'rm -f "$curl_auth_config" "$queue_headers"' EXIT

mkdir -p ipas
mkdir -p classes
Expand Down Expand Up @@ -408,20 +410,37 @@ urlencode()

report_analysis_failure()
{
appId="$1"
local appId="$1"
local claimToken="$2"
curl -sS --fail -K "$curl_auth_config" \
"$SERVER/reportAnalysisFailure?appId=$(urlencode "$appId")&analysisVersion=$(urlencode "$ANALYSIS_VERSION")" \
-H "X-Analysis-Claim-Token: $claimToken" \
--data-binary "@$log" -H "Content-Type: text/plain" > /dev/null
}

upload_analysis()
{
appId="$1"
local appId="$1"
local claimToken="$2"
curl -sS --fail -K "$curl_auth_config" \
"$SERVER/uploadAnalysis?appId=$(urlencode "$appId")&analysisVersion=$(urlencode "$ANALYSIS_VERSION")" \
-H "X-Analysis-Claim-Token: $claimToken" \
-d @"analysis/$appId.json" -H "Content-Type: application/json" > /dev/null
}

fetch_queue_app()
{
local queueUrl="$1"
: > "$queue_headers"
appId=$(curl -sS -K "$curl_auth_config" -D "$queue_headers" "$queueUrl" --fail) || return 1
claimToken=$(awk 'tolower($1) == "x-analysis-claim-token:" { gsub(/\r/, "", $2); print $2; exit }' "$queue_headers")

if [ -n "$appId" ] && [ -z "$claimToken" ]; then
echo "Queue response for $appId did not include an analysis claim token." >&2
return 1
fi
}

download()
{
# ipatool's combined download path handles already-owned apps and purchases.
Expand Down Expand Up @@ -641,7 +660,8 @@ fi

process_app()
{
appId="$1"
local appId="$1"
local claimToken="$2"
success=0

# empty the log file
Expand Down Expand Up @@ -697,7 +717,7 @@ process_app()

if [ "$download_stop_reason" = "too-large" ] && [ ! -f "$f" ]; then
show_log_tail
if report_analysis_failure "$appId"; then
if report_analysis_failure "$appId" "$claimToken"; then
echo "Reported non-retryable oversized download for $appId."
else
echo "Failed to report oversized download for $appId."
Expand All @@ -711,7 +731,7 @@ process_app()
if [ "$size" -gt "$MAX_APP_SIZE_BYTES" ]; then
echo "Skipping $appId: downloaded IPA is $((size / 1000000)) MB, above MAX_APP_SIZE_BYTES=$((MAX_APP_SIZE_BYTES / 1000000)) MB." >> "$log"
show_log_tail
if report_analysis_failure "$appId"; then
if report_analysis_failure "$appId" "$claimToken"; then
echo "Reported analysis failure for $appId."
else
echo "Failed to report analysis failure for $appId."
Expand Down Expand Up @@ -739,7 +759,7 @@ process_app()
if [ "$RUN_ONCE" = "1" ] && [ "$LIVE_LOG" != "1" ]; then
show_log_tail
fi
if upload_analysis "$appId"; then
if upload_analysis "$appId" "$claimToken"; then
echo "Uploaded analysis for $appId."
consecutive_failures=0
success=1
Expand All @@ -751,7 +771,7 @@ process_app()

if [ ! -f "analysis/$appId.json" ]; then
show_log_tail
if report_analysis_failure "$appId"; then
if report_analysis_failure "$appId" "$claimToken"; then
echo "Reported analysis failure for $appId."
else
echo "Failed to report analysis failure for $appId."
Expand Down Expand Up @@ -782,7 +802,12 @@ if [ -n "$ONLY_APP_ID" ]; then
exit 1
fi

process_app "$ONLY_APP_ID"
if ! fetch_queue_app "$SERVER/queue?appId=$(urlencode "$ONLY_APP_ID")" || [ -z "$appId" ]; then
echo "Could not claim $ONLY_APP_ID for exact processing."
exit 1
fi

process_app "$appId" "$claimToken"
exit $?
fi

Expand All @@ -805,12 +830,16 @@ while true; do
fi

echo "Fetching apps to install"
appId=`curl -s -K "$curl_auth_config" "$SERVER/queue" --fail`
if ! fetch_queue_app "$SERVER/queue"; then
echo "Failed to fetch a queue assignment."
appId=""
claimToken=""
fi

if [ "$appId" == "" ] ; then
echo "No app to process.."
else
process_app "$appId"
process_app "$appId" "$claimToken"
fi

if [ "$RUN_ONCE" = "1" ]; then
Expand Down
19 changes: 19 additions & 0 deletions migrations/010_analysis_claim_tokens.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- Tie every processing assignment to a one-use random claim token. Completion
-- compares this token before writing so a timed-out or explicitly reset worker
-- cannot overwrite the result of a newer assignment.

ALTER TABLE apps
ADD COLUMN analysis_claim_token uuid;

-- Existing processing rows predate claim tokens. Requeue them rather than
-- accepting an unverifiable completion during rollout.
UPDATE apps
SET status = 'queued',
processing_started = NULL
WHERE status = 'processing';

ALTER TABLE apps
ADD CONSTRAINT apps_processing_claim_token_check CHECK (
(status = 'processing' AND analysis_claim_token IS NOT NULL)
OR (status <> 'processing' AND analysis_claim_token IS NULL)
);
Loading
Loading