Skip to content

Instantly share code, notes, and snippets.

@rockerBOO
Created April 14, 2026 00:13
Show Gist options
  • Select an option

  • Save rockerBOO/6ab64b91f73273b70f6fc81258ad9522 to your computer and use it in GitHub Desktop.

Select an option

Save rockerBOO/6ab64b91f73273b70f6fc81258ad9522 to your computer and use it in GitHub Desktop.
Checks thumbnailers for conflicting or incorrect setup
#!/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