Created
April 14, 2026 00:13
-
-
Save rockerBOO/6ab64b91f73273b70f6fc81258ad9522 to your computer and use it in GitHub Desktop.
Checks thumbnailers for conflicting or incorrect setup
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # check-thumbnailers.sh — diagnose thumbnail generation issues | |
| # Checks: MIME conflicts, broken binaries, missing libs, cache state, env vars, sandbox | |
| set -uo pipefail | |
| THUMBNAILER_DIR="${THUMBNAILER_DIR:-/usr/share/thumbnailers}" | |
| THUMB_CACHE="${XDG_CACHE_HOME:-$HOME/.cache}/thumbnails" | |
| RED='\033[0;31m'; YELLOW='\033[1;33m'; GREEN='\033[0;32m'; BOLD='\033[1m'; RESET='\033[0m' | |
| ok() { echo -e " ${GREEN}✓${RESET} $*"; } | |
| warn() { echo -e " ${YELLOW}⚠${RESET} $*"; } | |
| fail() { echo -e " ${RED}✗${RESET} $*"; } | |
| section() { echo -e "\n${BOLD}── $* ──${RESET}"; } | |
| # ── 1. Parse all thumbnailers ──────────────────────────────────────────────── | |
| section "Thumbnailer files ($THUMBNAILER_DIR)" | |
| declare -A mime_to_thumbnailers # mime → "file1:file2:..." (in readdir order) | |
| declare -A thumbnailer_exec # file → Exec line | |
| declare -A thumbnailer_tryexec # file → TryExec binary | |
| # Read files in inode (readdir) order — this matches how gnome-desktop loads them | |
| # Last entry for a given MIME type wins (g_hash_table_replace behaviour) | |
| mapfile -t TFILES < <(ls -i "$THUMBNAILER_DIR"/*.thumbnailer 2>/dev/null \ | |
| | sort -n | awk '{print $2}') | |
| if [[ ${#TFILES[@]} -eq 0 ]]; then | |
| fail "No thumbnailer files found in $THUMBNAILER_DIR" | |
| exit 1 | |
| fi | |
| for tfile in "${TFILES[@]}"; do | |
| name=$(basename "$tfile") | |
| exec_line=$(grep -m1 '^Exec=' "$tfile" | cut -d= -f2- || true) | |
| tryexec=$(grep -m1 '^TryExec=' "$tfile" | cut -d= -f2- || true) | |
| mimetypes=$(grep -m1 '^MimeType=' "$tfile" | cut -d= -f2- | tr ';' '\n' | grep -v '^$' || true) | |
| thumbnailer_exec["$name"]="$exec_line" | |
| thumbnailer_tryexec["$name"]="${tryexec:-}" | |
| echo " $name" | |
| while IFS= read -r mime; do | |
| [[ -z "$mime" ]] && continue | |
| if [[ -v mime_to_thumbnailers["$mime"] ]]; then | |
| mime_to_thumbnailers["$mime"]+=" $name" | |
| else | |
| mime_to_thumbnailers["$mime"]="$name" | |
| fi | |
| done <<< "$mimetypes" | |
| done | |
| # ── 2. MIME type conflicts ─────────────────────────────────────────────────── | |
| section "MIME type conflicts (last loaded wins)" | |
| conflicts=0 | |
| for mime in $(echo "${!mime_to_thumbnailers[@]}" | tr ' ' '\n' | sort); do | |
| entries="${mime_to_thumbnailers[$mime]}" | |
| count=$(echo "$entries" | wc -w) | |
| if [[ $count -gt 1 ]]; then | |
| winner=$(echo "$entries" | awk '{print $NF}') | |
| losers=$(echo "$entries" | awk 'NF>1{for(i=1;i<NF;i++) printf $i" "; print ""}' | sed 's/ $//') | |
| warn "$mime" | |
| echo " winner : $winner" | |
| echo " ignored: $losers" | |
| conflicts=$((conflicts + 1)) | |
| fi | |
| done | |
| [[ $conflicts -eq 0 ]] && ok "No conflicts" | |
| # ── 3. Binary checks ───────────────────────────────────────────────────────── | |
| section "Thumbnailer binary checks" | |
| for name in "${!thumbnailer_tryexec[@]}"; do | |
| tryexec="${thumbnailer_tryexec[$name]}" | |
| if [[ -z "$tryexec" ]]; then | |
| warn "$name: no TryExec defined" | |
| continue | |
| fi | |
| binary=$(command -v "$tryexec" 2>/dev/null || echo "") | |
| if [[ -z "$binary" ]]; then | |
| fail "$name: binary not found: $tryexec" | |
| continue | |
| fi | |
| # Check for missing shared libraries | |
| missing=$(ldd "$binary" 2>/dev/null | grep "not found" || true) | |
| if [[ -n "$missing" ]]; then | |
| fail "$name ($binary): missing libraries:" | |
| echo "$missing" | sed 's/^/ /' | |
| else | |
| ok "$name: $binary (libs ok)" | |
| fi | |
| done | |
| # ── 4. Effective thumbnailer per MIME (what Nautilus/Nemo actually uses) ───── | |
| section "Effective thumbnailer per MIME type" | |
| declare -A winner_for_mime | |
| for mime in $(echo "${!mime_to_thumbnailers[@]}" | tr ' ' '\n' | sort); do | |
| entries="${mime_to_thumbnailers[$mime]}" | |
| winner=$(echo "$entries" | awk '{print $NF}') | |
| winner_for_mime["$mime"]="$winner" | |
| done | |
| # Group by winner for compact output | |
| declare -A winner_mimes | |
| for mime in "${!winner_for_mime[@]}"; do | |
| w="${winner_for_mime[$mime]}" | |
| winner_mimes["$w"]+="$mime " | |
| done | |
| for w in $(echo "${!winner_mimes[@]}" | tr ' ' '\n' | sort); do | |
| tryexec="${thumbnailer_tryexec[$w]:-unknown}" | |
| binary=$(command -v "$tryexec" 2>/dev/null || echo "MISSING") | |
| missing_libs=$(ldd "$binary" 2>/dev/null | grep -c "not found" || true) | |
| missing_libs="${missing_libs:-0}" | |
| if [[ "$binary" == "MISSING" ]]; then | |
| status="${RED}BROKEN (binary missing)${RESET}" | |
| elif [[ "$missing_libs" -gt 0 ]]; then | |
| status="${RED}BROKEN ($missing_libs missing libs)${RESET}" | |
| else | |
| status="${GREEN}ok${RESET}" | |
| fi | |
| echo -e " ${BOLD}$w${RESET} [${status}]" | |
| echo "${winner_mimes[$w]}" | tr ' ' '\n' | grep -v '^$' | sort | sed 's/^/ /' | |
| done | |
| # ── 5. Thumbnail cache state ────────────────────────────────────────────────── | |
| section "Thumbnail cache ($THUMB_CACHE)" | |
| total_normal=$(find "$THUMB_CACHE" -name "*.png" ! -path "*/fail/*" 2>/dev/null | wc -l) | |
| ok "$total_normal normal thumbnails cached" | |
| for fail_dir in "$THUMB_CACHE"/fail/*/; do | |
| [[ -d "$fail_dir" ]] || continue | |
| count=$(find "$fail_dir" -name "*.png" 2>/dev/null | wc -l) | |
| dname=$(basename "$fail_dir") | |
| if [[ $count -gt 0 ]]; then | |
| warn "$count fail entries in $dname" | |
| # Sample a few URIs | |
| mapfile -t samples < <(find "$fail_dir" -name "*.png" 2>/dev/null | head -3) | |
| for f in "${samples[@]}"; do | |
| uri=$(exiftool -s3 -ThumbURI "$f" 2>/dev/null || true) | |
| [[ -n "$uri" ]] && echo " e.g. $uri" | |
| done | |
| else | |
| ok "fail/$dname is empty" | |
| fi | |
| done | |
| # ── 6. Stale temp dirs ──────────────────────────────────────────────────────── | |
| section "Stale thumbnailer temp dirs (/tmp)" | |
| stale=0 | |
| for pattern in gnome-desktop-thumbnailer nemo-thumbnail; do | |
| dirs=$(find /tmp -maxdepth 1 -name "${pattern}*" -type d 2>/dev/null | wc -l) | |
| if [[ $dirs -gt 0 ]]; then | |
| warn "$dirs stale /tmp/${pattern}* dirs (thumbnailer left files behind)" | |
| mapfile -t sample < <(find /tmp -maxdepth 1 -name "${pattern}*" -type d 2>/dev/null | head -3) | |
| for d in "${sample[@]}"; do | |
| contents=$(ls "$d" 2>/dev/null | tr '\n' ' ') | |
| echo " $d: $contents" | |
| done | |
| stale=$((stale + dirs)) | |
| fi | |
| done | |
| [[ $stale -eq 0 ]] && ok "No stale temp dirs" | |
| # ── 7. Environment variables ────────────────────────────────────────────────── | |
| section "Environment" | |
| check_env() { | |
| local var="$1" | |
| local src="$2" | |
| local val | |
| val=$(eval "echo \${$var:-}") | |
| if [[ -n "$val" ]]; then | |
| ok "$var=$val (in $src)" | |
| else | |
| echo " - $var: not set (in $src)" | |
| fi | |
| } | |
| # Current shell | |
| check_env GLYCIN_DISABLE_SANDBOX "shell" | |
| # Systemd user session | |
| sd_val=$(systemctl --user show-environment 2>/dev/null | grep "^GLYCIN_DISABLE_SANDBOX=" | cut -d= -f2- || true) | |
| if [[ -n "$sd_val" ]]; then | |
| ok "GLYCIN_DISABLE_SANDBOX=$sd_val (systemd user session)" | |
| else | |
| echo " - GLYCIN_DISABLE_SANDBOX: not in systemd user session" | |
| fi | |
| # environment.d files | |
| env_d_dirs=("$HOME/.config/environment.d" "/etc/environment.d") | |
| for d in "${env_d_dirs[@]}"; do | |
| if [[ -d "$d" ]]; then | |
| found=$(grep -r "GLYCIN_DISABLE_SANDBOX" "$d" 2>/dev/null || true) | |
| if [[ -n "$found" ]]; then | |
| ok "GLYCIN_DISABLE_SANDBOX found in $d" | |
| fi | |
| fi | |
| done | |
| # Running file manager processes | |
| for fm in nautilus nemo thunar; do | |
| pid=$(pgrep -x "$fm" 2>/dev/null | head -1 || true) | |
| if [[ -n "$pid" ]]; then | |
| val=$(tr '\0' '\n' < "/proc/$pid/environ" 2>/dev/null | grep "^GLYCIN_DISABLE_SANDBOX=" | cut -d= -f2- || true) | |
| if [[ -n "$val" ]]; then | |
| ok "$fm (pid $pid): GLYCIN_DISABLE_SANDBOX=$val" | |
| else | |
| warn "$fm (pid $pid): GLYCIN_DISABLE_SANDBOX not in process environment" | |
| fi | |
| fi | |
| done | |
| # ── 8. Sandbox / bwrap ─────────────────────────────────────────────────────── | |
| section "Sandbox (bwrap)" | |
| if command -v bwrap &>/dev/null; then | |
| bwrap_out=$(bwrap --unshare-user --ro-bind /usr /usr \ | |
| --symlink /usr/lib /lib --symlink /usr/lib /lib64 \ | |
| /usr/bin/true 2>&1); bwrap_ok=$? | |
| if [[ $bwrap_ok -eq 0 ]]; then | |
| ok "bwrap user namespaces work" | |
| else | |
| warn "bwrap user namespaces fail: $bwrap_out" | |
| echo " → glycin sandbox will fall back (expect 'WARNING: Glycin running without sandbox')" | |
| fi | |
| unprivileged=$(cat /proc/sys/kernel/unprivileged_userns_clone 2>/dev/null \ | |
| || cat /proc/sys/user/max_user_namespaces 2>/dev/null || echo "unknown") | |
| echo " unprivileged_userns_clone / max_user_namespaces: $unprivileged" | |
| else | |
| fail "bwrap not found — glycin sandbox unavailable" | |
| fi | |
| # ── 9. Quick thumbnailer smoke test ────────────────────────────────────────── | |
| if [[ -n "${1:-}" && -f "$1" ]]; then | |
| section "Smoke test: $1" | |
| mime=$(file --mime-type -b "$1") | |
| echo " MIME: $mime" | |
| effective="${winner_for_mime[$mime]:-none}" | |
| echo " Effective thumbnailer: $effective" | |
| if [[ "$effective" != "none" ]]; then | |
| tryexec="${thumbnailer_tryexec[$effective]:-}" | |
| exec_tmpl="${thumbnailer_exec[$effective]:-}" | |
| outfile=$(mktemp /tmp/thumb-test-XXXXXX.png) | |
| cmd="${exec_tmpl//%u/file:\/\/$(realpath "$1")}" | |
| cmd="${cmd//%i/file:\/\/$(realpath "$1")}" | |
| cmd="${cmd//%o/$outfile}" | |
| cmd="${cmd//%s/256}" | |
| echo " Running: $cmd" | |
| if eval "$cmd" 2>&1; then | |
| if [[ -s "$outfile" ]]; then | |
| ok "Thumbnail created: $outfile ($(du -h "$outfile" | cut -f1))" | |
| else | |
| fail "Thumbnailer exited 0 but produced no output" | |
| rm -f "$outfile" | |
| fi | |
| else | |
| fail "Thumbnailer exited non-zero" | |
| rm -f "$outfile" | |
| fi | |
| fi | |
| fi | |
| echo -e "\n${BOLD}Done.${RESET}" | |
| echo "Tip: to clear fail cache: rm -rf $THUMB_CACHE/fail/gnome-thumbnail-factory/" | |
| echo " to clear stale temp: rm -rf /tmp/gnome-desktop-thumbnailer-*" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment