nfs-mount-heal: read a small chunk, not the whole file, in health checks

Root cause of nas_audiobooks still flapping after the automount fix: the
health check finds the first file in each share via `find -quit` and
`cat`s the whole thing within a 10s timeout. That's fine for immich's
13-byte marker and owncloud's 0-byte one, but audiobooks has no such
marker - find picked a real 2.9GB audiobook file, which can never fully
transfer in 10s regardless of mount health. Switched to `head -c 64k`,
which only needs to prove the file handle/NFS path is alive. Confirmed:
the mount was never actually unhealthy post the automount fix, this test
methodology was generating the "stale" verdicts (cat exit=124) itself.

Also captures and logs/alerts the actual find/head error text now instead
of a generic "stale/unreachable" guess, so any future real failure is
diagnosable without SSH archaeology.
This commit is contained in:
2026-09-18 03:26:09 +00:00
parent a0921dd387
commit c95fdef0e5
+41 -8
View File
@@ -28,18 +28,46 @@ alert() {
-d chat_id="${TELEGRAM_CHAT_ID}" -d text="$1" > /dev/null 2>&1 -d chat_id="${TELEGRAM_CHAT_ID}" -d text="$1" > /dev/null 2>&1
} }
LAST_ERROR=""
healthy() { healthy() {
local testdir="$1" local testdir="$1"
local f local f rc errfile
errfile=$(mktemp)
# No -maxdepth: -quit stops at the first match via depth-first search, # No -maxdepth: -quit stops at the first match via depth-first search,
# so this is fast regardless of tree depth. A maxdepth that's too # so this is fast regardless of tree depth. A maxdepth that's too
# shallow for the actual file depth (e.g. immich's thumbs/<uuid>/XX/YY/ # shallow for the actual file depth (e.g. immich's thumbs/<uuid>/XX/YY/
# file is 4 levels deep) makes find exhaust a huge fan-out with no # file is 4 levels deep) makes find exhaust a huge fan-out with no
# match and time out -- a false positive, not real staleness (hit in # match and time out -- a false positive, not real staleness (hit in
# production 2026-08-25, 3 spurious remounts in 15 min). # production 2026-08-25, 3 spurious remounts in 15 min).
f=$(timeout 10 find "$testdir" -type f -print -quit 2>/dev/null) || return 1 f=$(timeout 10 find "$testdir" -type f -print -quit 2>"$errfile")
[ -n "$f" ] || return 1 rc=$?
timeout 10 cat "$f" > /dev/null 2>&1 if [ $rc -ne 0 ] || [ -z "$f" ]; then
LAST_ERROR="find exit=${rc}: $(cat "$errfile")"
rm -f "$errfile"
return 1
fi
# head -c, not cat: this only needs to prove the file handle/NFS path is
# alive, not fully transfer the file. Reading the whole thing was fine for
# small marker files (immich's 13-byte .immich, owncloud's 0-byte
# .migrations.lock) but nas_audiobooks has no such marker, so `find`
# legitimately picks a real audiobook - discovered 2026-09-18 to be 2.9GB,
# which a full `cat` can never finish inside a 10s timeout regardless of
# mount health. That was generating a "stale" verdict (cat exit=124,
# timeout) on a mount that was actually fine post the automount fix -
# this bug, not a real NFS issue, was the second flapping source.
timeout 10 head -c 65536 "$f" > /dev/null 2>"$errfile"
rc=$?
if [ $rc -ne 0 ]; then
LAST_ERROR="head exit=${rc} on '${f}': $(cat "$errfile")"
rm -f "$errfile"
return 1
fi
rm -f "$errfile"
return 0
} }
for entry in "${CHECKS[@]}"; do for entry in "${CHECKS[@]}"; do
@@ -47,16 +75,21 @@ for entry in "${CHECKS[@]}"; do
healthy "$testdir" && continue healthy "$testdir" && continue
logger -t nfs-mount-heal "${mount} unhealthy (stale/unreachable), remounting ${unit}" # Captured so we log/alert the actual syscall error (stale handle vs
# timeout vs something else) instead of guessing - added 2026-09-18
# after nas_audiobooks kept flapping with no corroborating error on
# the NAS side even after fixing its automount-vs-permanent-mount bug.
first_error="$LAST_ERROR"
logger -t nfs-mount-heal "${mount} unhealthy, remounting ${unit}: ${first_error}"
if timeout 30 systemctl restart "$unit"; then if timeout 30 systemctl restart "$unit"; then
sleep 3 sleep 3
if healthy "$testdir"; then if healthy "$testdir"; then
alert "🔧 NFS self-heal: ${mount} went stale, remounted ${unit} and restarted ${container} automatically. All clear." alert "🔧 NFS self-heal: ${mount} went stale (${first_error}), remounted ${unit} and restarted ${container} automatically. All clear."
else else
alert "⚠️ NFS self-heal: ${mount} was stale, remounted ${unit} and restarted ${container}, but it's still failing the health check. Needs a look." alert "⚠️ NFS self-heal: ${mount} was stale (${first_error}), remounted ${unit} and restarted ${container}, but it's still failing (${LAST_ERROR}). Needs a look."
fi fi
else else
alert "❗ NFS self-heal FAILED: ${mount} is unhealthy and systemctl restart ${unit} failed. Manual intervention needed." alert "❗ NFS self-heal FAILED: ${mount} is unhealthy (${first_error}) and systemctl restart ${unit} failed. Manual intervention needed."
fi fi
done done