#!/bin/bash # ----------------------------------------------------------------------------------------------- # --------------------------------- Emby Database Repair --------------------------------------- # ----------------------------------------------------------------------------------------------- # Stops Emby, runs SQLite integrity checks on all Emby databases, and restarts. # Use when Emby reports database corruption, unexpected crashes, or playback state issues. # # Checks performed: # integrity_check — full SQLite integrity verification per database file # quick_check — faster check for common corruption patterns # # If corruption is found: # Reports which database files are corrupted # Does NOT automatically repair — corruption repair requires manual steps # Provides guidance on next steps per database type # # Emby database files checked: # library.db — media library metadata # library.db-wal — write-ahead log (if exists) # librarydb.db — legacy library database # users.db — user accounts and settings # authentication.db — API keys and sessions # activity.db — activity log # # HOST1 repairs its own Emby (HOST1_EMBY_CONTAINER). # HOST2 repairs its own Emby (HOST2_EMBY_CONTAINER). # detect_hosts() selects the correct container at runtime. # Container name defined in Host Configuration in Master.conf. # Supports --dry-run to show what would be checked without stopping Emby. # ----------------------------------------------------------------------------------------------- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/../Master.conf" source "$SCRIPT_DIR/../common.sh" parse_args "$@" # ----------------------------------------------------------------------------------------------- # ━━━ $ICON_GEAR Setup ━━━ # ----------------------------------------------------------------------------------------------- echo "" echo "━━━ $ICON_GEAR Setup ━━━" if [[ "$EUID" -ne 0 ]]; then error "Must be run as root" exit 1 fi success "Running as root" acquire_lock # Select correct Emby container based on which server is running this script detect_hosts if [[ "$LOCAL_SERVER_NAME" == "$HOST1" ]]; then EMBY_CONTAINER="$HOST1_EMBY_CONTAINER" else EMBY_CONTAINER="$HOST2_EMBY_CONTAINER" fi info "Emby container: $LOCAL_SERVER_NAME → $EMBY_CONTAINER" if ! command -v sqlite3 >/dev/null 2>&1; then error "sqlite3 not found — install sqlite package" exit 1 fi success "sqlite3 available" [[ "$DRY_RUN" == true ]] && warn "DRY RUN — Emby will not be stopped" # Detect Emby config path from Docker mount EMBY_CONFIG_HOST=$(docker inspect "$EMBY_CONTAINER" 2>/dev/null | \ jq -r '.[] | .Mounts[] | select(.Destination == "/config") | .Source' 2>/dev/null) if [[ -z "$EMBY_CONFIG_HOST" ]]; then error "Could not detect Emby config path from Docker mounts" error "Make sure $EMBY_CONTAINER is the correct container name in Master.conf" exit 1 fi success "Emby config path: $EMBY_CONFIG_HOST" # ----------------------------------------------------------------------------------------------- # ━━━ $ICON_STOP Stop Emby ━━━ # ----------------------------------------------------------------------------------------------- echo "" echo "━━━ $ICON_STOP Stop Emby ━━━" EMBY_WAS_RUNNING=false STATUS=$(docker inspect -f '{{.State.Running}}' "$EMBY_CONTAINER" 2>/dev/null) if [[ "$STATUS" == "true" ]]; then EMBY_WAS_RUNNING=true warn "Stopping $EMBY_CONTAINER — active sessions will be interrupted" if [[ "$DRY_RUN" == false ]]; then docker stop "$EMBY_CONTAINER" >/dev/null 2>&1 && \ success "$ICON_STOPPED $EMBY_CONTAINER stopped" || \ { error "Failed to stop $EMBY_CONTAINER"; exit 1; } sleep 3 # brief wait for file handles to release else warn "DRY RUN — would stop $EMBY_CONTAINER" fi else info "$EMBY_CONTAINER is not running — proceeding with checks" fi # ----------------------------------------------------------------------------------------------- # ━━━ $ICON_HEALTH Database Integrity Check ━━━ # ----------------------------------------------------------------------------------------------- echo "" echo "━━━ $ICON_HEALTH Database Integrity Check ━━━" START=$(date +%s) # Emby database files to check DB_FILES=( "data/library.db" "data/librarydb.db" "data/users.db" "data/authentication.db" "data/activity.db" ) PASS_DBS=() FAIL_DBS=() MISSING_DBS=() for db_rel in "${DB_FILES[@]}"; do db_path="${EMBY_CONFIG_HOST}/${db_rel}" db_name=$(basename "$db_rel") if [[ ! -f "$db_path" ]]; then log "$db_name — not found, skipping" MISSING_DBS+=("$db_name") continue fi DB_SIZE=$(du -sh "$db_path" 2>/dev/null | cut -f1) info "$ICON_HEALTH Checking $db_name ($DB_SIZE)..." if [[ "$DRY_RUN" == true ]]; then warn "DRY RUN — would check: $db_path" continue fi # Run integrity check RESULT=$(sqlite3 "$db_path" "PRAGMA integrity_check;" 2>/dev/null) EXIT_CODE=$? if [[ "$EXIT_CODE" -ne 0 ]]; then error "$db_name — sqlite3 could not open database (may be locked or corrupt)" FAIL_DBS+=("$db_name") elif [[ "$RESULT" == "ok" ]]; then success "$db_name — integrity check passed" PASS_DBS+=("$db_name") else error "$db_name — integrity check FAILED" echo "$RESULT" | head -10 | while IFS= read -r line; do error " $line" done FAIL_DBS+=("$db_name") fi done END=$(date +%s) # ----------------------------------------------------------------------------------------------- # ━━━ $ICON_START Restart Emby ━━━ # ----------------------------------------------------------------------------------------------- echo "" echo "━━━ $ICON_START Restart Emby ━━━" if [[ "$EMBY_WAS_RUNNING" == true && "$DRY_RUN" == false ]]; then docker start "$EMBY_CONTAINER" >/dev/null 2>&1 && \ success "$ICON_STARTED $EMBY_CONTAINER restarted" || \ error "Failed to restart $EMBY_CONTAINER — start it manually" elif [[ "$DRY_RUN" == true && "$EMBY_WAS_RUNNING" == true ]]; then warn "DRY RUN — would restart $EMBY_CONTAINER" else info "$EMBY_CONTAINER was not running — not restarting" fi # ----------------------------------------------------------------------------------------------- # ━━━ $ICON_SUMMARY Summary ━━━ # ----------------------------------------------------------------------------------------------- echo "" echo "━━━━━ $ICON_SUMMARY EMBY DATABASE REPAIR SUMMARY ━━━━━" echo "$ICON_HEALTH Container: $EMBY_CONTAINER" echo "$ICON_HEALTH Config path: $EMBY_CONFIG_HOST" echo "$ICON_TIME Duration: $(format_duration $((END - START)))" echo "" echo " $ICON_SUCCESS Passed: ${#PASS_DBS[@]} $ICON_ERROR Failed: ${#FAIL_DBS[@]} $ICON_INFO Missing: ${#MISSING_DBS[@]}" echo "" if [[ ${#PASS_DBS[@]} -gt 0 ]]; then for db in "${PASS_DBS[@]}"; do echo " $ICON_SUCCESS $db"; done fi if [[ ${#FAIL_DBS[@]} -gt 0 ]]; then for db in "${FAIL_DBS[@]}"; do echo " $ICON_ERROR $db"; done fi echo "" if [[ "$DRY_RUN" == true ]]; then echo "$ICON_WARN Status: DRY RUN — no checks performed" elif [[ ${#FAIL_DBS[@]} -gt 0 ]]; then echo "$ICON_ERROR Status: CORRUPTION FOUND" echo "" echo "$ICON_INFO Next steps for corrupted databases:" echo " library.db — Stop Emby, delete library.db, restart" echo " Emby will rebuild from media files (slow first start)" echo " users.db — Stop Emby, restore from backup or delete" echo " Deleting resets all user accounts" echo " authentication.db — Stop Emby, delete, restart" echo " API keys and sessions will be regenerated" echo " activity.db — Stop Emby, delete, restart — activity log only" echo "" echo "$ICON_WARN Always take a backup before deleting any database file" notify "Emby database corruption found on $(hostname) — failed: ${FAIL_DBS[*]} — manual intervention needed" "Emby DB Repair" "warning" else echo "$ICON_DONE Status: $ICON_SUCCESS ALL DATABASES HEALTHY" notify "Emby database integrity check passed on $(hostname) — ${#PASS_DBS[@]} databases healthy" "Emby DB Repair" "normal" fi echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"