Skip to content

Instantly share code, notes, and snippets.

@yowmamasita
Created December 1, 2025 00:40
Show Gist options
  • Select an option

  • Save yowmamasita/b0098af185fcd9e00b4841b7e0d7a6a6 to your computer and use it in GitHub Desktop.

Select an option

Save yowmamasita/b0098af185fcd9e00b4841b7e0d7a6a6 to your computer and use it in GitHub Desktop.
Zurg on_library_update script for triggering Stash scans via GraphQL API

Zurg on_library_update Script for Stash

This script triggers Stash library scans when zurg updates its library.

Features

  • Path-specific scanning - Only scans directories that were added/changed
  • Clean removed content - Removes entries from Stash when content is deleted
  • Full scan fallback - If no specific paths are provided
  • Configurable scan options - Enable/disable cover, preview, sprite generation

Requirements

  • curl
  • jq
  • Stash API key (if authentication is enabled)

Configuration

Set these environment variables before running or export them in your shell profile:

Variable Description Default
STASH_URL Stash server URL http://localhost:9999
STASH_API_KEY API key from Settings > Security > Authentication (empty)
STASH_LOG_FILE Log file path /tmp/stash_scan.log
SCAN_GENERATE_COVERS Generate covers during scan false
SCAN_GENERATE_PREVIEWS Generate video previews false
SCAN_GENERATE_SPRITES Generate scrubber sprites false
SCAN_GENERATE_PHASHES Generate perceptual hashes false
SCAN_GENERATE_THUMBNAILS Generate image thumbnails false

Usage in zurg config.yml

on_library_update: /path/to/on_library_update.stash.sh

Getting Your Stash API Key

  1. Open Stash
  2. Go to Settings > Security > Authentication
  3. Generate or copy your API Key

Environment Variables from Zurg

The script receives these environment variables from zurg:

  • ZURG_ADDED_DIRS - Space-separated list of added directories
  • ZURG_REMOVED_DIRS - Space-separated list of removed directories

GraphQL Mutations Used

  • metadataScan - Triggers a library scan for new/updated content
  • metadataClean - Removes database entries for files that no longer exist

References

#!/bin/bash
#
# on_library_update.stash.sh
# Sample script to trigger Stash scans when zurg library is updated
#
# Usage in config.yml:
# on_library_update: /path/to/on_library_update.stash.sh
#
# Environment variables passed by zurg:
# ZURG_ADDED_DIRS - Space-separated list of added directories
# ZURG_REMOVED_DIRS - Space-separated list of removed directories
#
# Configuration - adjust these values
STASH_URL="${STASH_URL:-http://localhost:9999}"
STASH_API_KEY="${STASH_API_KEY:-}" # Get from Settings > Security > Authentication
LOG_FILE="${STASH_LOG_FILE:-/tmp/stash_scan.log}"
# Scan options - set to true/false as needed
SCAN_GENERATE_COVERS="${SCAN_GENERATE_COVERS:-false}"
SCAN_GENERATE_PREVIEWS="${SCAN_GENERATE_PREVIEWS:-false}"
SCAN_GENERATE_SPRITES="${SCAN_GENERATE_SPRITES:-false}"
SCAN_GENERATE_PHASHES="${SCAN_GENERATE_PHASHES:-false}"
SCAN_GENERATE_THUMBNAILS="${SCAN_GENERATE_THUMBNAILS:-false}"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE"
}
# Function to trigger a scan for specific paths
scan_paths() {
local paths="$1"
if [ -z "$paths" ]; then
log "No paths provided for scanning"
return 0
fi
# Convert space-separated paths to JSON array
local json_paths=""
for path in $paths; do
if [ -n "$json_paths" ]; then
json_paths="$json_paths,"
fi
json_paths="$json_paths\"$path\""
done
local query='mutation MetadataScan($input: ScanMetadataInput!) { metadataScan(input: $input) }'
local variables="{
\"input\": {
\"paths\": [$json_paths],
\"scanGenerateCovers\": $SCAN_GENERATE_COVERS,
\"scanGeneratePreviews\": $SCAN_GENERATE_PREVIEWS,
\"scanGenerateSprites\": $SCAN_GENERATE_SPRITES,
\"scanGeneratePhashes\": $SCAN_GENERATE_PHASHES,
\"scanGenerateThumbnails\": $SCAN_GENERATE_THUMBNAILS
}
}"
local payload=$(jq -n \
--arg query "$query" \
--argjson variables "$variables" \
'{query: $query, variables: $variables}')
log "Triggering scan for paths: $paths"
local curl_args=(-s -X POST "${STASH_URL}/graphql" \
-H "Content-Type: application/json")
if [ -n "$STASH_API_KEY" ]; then
curl_args+=(-H "ApiKey: ${STASH_API_KEY}")
fi
curl_args+=(--data "$payload")
local response
response=$(curl "${curl_args[@]}" 2>&1)
local exit_code=$?
if [ $exit_code -ne 0 ]; then
log "ERROR: curl failed with exit code $exit_code: $response"
return 1
fi
# Check for errors in response
if echo "$response" | jq -e '.errors' > /dev/null 2>&1; then
log "ERROR: GraphQL error: $response"
return 1
fi
local job_id
job_id=$(echo "$response" | jq -r '.data.metadataScan // empty')
if [ -n "$job_id" ]; then
log "Scan started successfully, job ID: $job_id"
else
log "Scan response: $response"
fi
return 0
}
# Function to trigger a full library scan (no specific paths)
scan_full() {
local query='mutation MetadataScan($input: ScanMetadataInput!) { metadataScan(input: $input) }'
local variables="{
\"input\": {
\"scanGenerateCovers\": $SCAN_GENERATE_COVERS,
\"scanGeneratePreviews\": $SCAN_GENERATE_PREVIEWS,
\"scanGenerateSprites\": $SCAN_GENERATE_SPRITES,
\"scanGeneratePhashes\": $SCAN_GENERATE_PHASHES,
\"scanGenerateThumbnails\": $SCAN_GENERATE_THUMBNAILS
}
}"
local payload=$(jq -n \
--arg query "$query" \
--argjson variables "$variables" \
'{query: $query, variables: $variables}')
log "Triggering full library scan"
local curl_args=(-s -X POST "${STASH_URL}/graphql" \
-H "Content-Type: application/json")
if [ -n "$STASH_API_KEY" ]; then
curl_args+=(-H "ApiKey: ${STASH_API_KEY}")
fi
curl_args+=(--data "$payload")
local response
response=$(curl "${curl_args[@]}" 2>&1)
local exit_code=$?
if [ $exit_code -ne 0 ]; then
log "ERROR: curl failed with exit code $exit_code: $response"
return 1
fi
local job_id
job_id=$(echo "$response" | jq -r '.data.metadataScan // empty')
if [ -n "$job_id" ]; then
log "Full scan started successfully, job ID: $job_id"
else
log "Scan response: $response"
fi
return 0
}
# Function to clean removed content from Stash
clean_paths() {
local paths="$1"
if [ -z "$paths" ]; then
return 0
fi
# Convert space-separated paths to JSON array
local json_paths=""
for path in $paths; do
if [ -n "$json_paths" ]; then
json_paths="$json_paths,"
fi
json_paths="$json_paths\"$path\""
done
local query='mutation MetadataClean($input: CleanMetadataInput!) { metadataClean(input: $input) }'
local variables="{
\"input\": {
\"paths\": [$json_paths],
\"dryRun\": false
}
}"
local payload=$(jq -n \
--arg query "$query" \
--argjson variables "$variables" \
'{query: $query, variables: $variables}')
log "Triggering clean for removed paths: $paths"
local curl_args=(-s -X POST "${STASH_URL}/graphql" \
-H "Content-Type: application/json")
if [ -n "$STASH_API_KEY" ]; then
curl_args+=(-H "ApiKey: ${STASH_API_KEY}")
fi
curl_args+=(--data "$payload")
local response
response=$(curl "${curl_args[@]}" 2>&1)
log "Clean response: $response"
}
# Main execution
log "=== Library update triggered ==="
log "Added dirs: ${ZURG_ADDED_DIRS:-none}"
log "Removed dirs: ${ZURG_REMOVED_DIRS:-none}"
# Scan new/updated content
if [ -n "$ZURG_ADDED_DIRS" ]; then
scan_paths "$ZURG_ADDED_DIRS"
fi
# Clean removed content
if [ -n "$ZURG_REMOVED_DIRS" ]; then
clean_paths "$ZURG_REMOVED_DIRS"
fi
# If no specific paths, trigger a full scan
if [ -z "$ZURG_ADDED_DIRS" ] && [ -z "$ZURG_REMOVED_DIRS" ]; then
log "No specific paths provided, triggering full scan"
scan_full
fi
log "=== Library update complete ==="
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment