preflight.sh
Rama main · réplica de lectura del repositorio original.
#!/bin/bash
# Verify every host-side runtime dependency is installed and minimally functional; run at first
# setup and after host upgrades. Usage: preflight.sh [--verbose | --strict] (--verbose prints
# version strings; --strict treats optional misses as failure).
# Exit: 0 all required present and probes OK, 1 required missing/broken, 2 only optional missing.
set -uo pipefail
verbose=0
strict=0
for arg in "$@"; do
case "$arg" in
--verbose) verbose=1 ;;
--strict) strict=1 ;;
*) printf 'preflight: unknown arg %q\n' "$arg" >&2; exit 2 ;;
esac
done
REPO_ROOT="$(cd "$(dirname "$0")" && pwd)"
PATH="$REPO_ROOT/.venv/bin:$PATH" # the stack runs from the repo venv
fail=0
optfail=0
_probe_cmd() {
# _probe_cmd <counter-var> <missing-tag> <missing-suffix> <broken-tag> <bin> <description>
# [-- <probe argv...>]: the probe, if given, must exit 0 (a broken binary must not pass).
# Argv-style, not an eval'd string.
local counter="$1" miss_tag="$2" miss_sfx="$3" broken_tag="$4" cmd="$5" desc="$6"
shift 6
[[ "${1:-}" == "--" ]] && shift
if ! command -v "$cmd" >/dev/null 2>&1; then
printf '%s%s — %s%s\n' "$miss_tag" "$cmd" "$desc" "$miss_sfx"
printf -v "$counter" '%d' $(( ${!counter} + 1 ))
return
fi
if (( $# > 0 )); then
local out rc
out=$("$@" 2>&1 | head -1)
rc=$?
if [[ $rc -ne 0 ]]; then
printf '%s%s — probe exit %d: %s\n' "$broken_tag" "$cmd" "$rc" "$out"
printf -v "$counter" '%d' $(( ${!counter} + 1 ))
return
fi
if [[ $verbose -eq 1 ]]; then
printf 'OK: %s — %s\n' "$cmd" "$out"
return
fi
fi
printf 'OK: %s\n' "$cmd"
}
# require_cmd/optional_cmd <bin> <description> [-- <probe argv...>]: a miss is FAIL or OPT.
require_cmd() { _probe_cmd fail 'MISSING: ' '' 'BROKEN: ' "$@"; }
optional_cmd() { _probe_cmd optfail 'OPT: ' ' (not installed)' 'OPT: ' "$@"; }
require_cmd ffmpeg "two-pass loudnorm + remux" -- ffmpeg -version
require_cmd ffprobe "metadata probe used everywhere" -- ffprobe -version
require_cmd mkvmerge "v2 pipeline final mux (matroska cues)" -- mkvmerge --version
require_cmd mkvpropedit "global-tag stamping (NORMALIZED_AUDIO, CONSOLIDATED_SUBS)" -- mkvpropedit --version
require_cmd mkvextract "tag XML extraction in media_stack/tags.py" -- mkvextract --version
require_cmd python3 "all host-side scripts" -- python3 --version
require_cmd docker "container runtime" -- docker --version
# inotifywait --help exits 1, so probe via dpkg-query (argv-style: no pipes).
require_cmd inotifywait "consolidate-watch.service file event source" \
-- dpkg-query -W -f='${Version}' inotify-tools
require_cmd curl "ad-hoc API probes in shell scripts" -- curl --version
require_cmd flock "cron-driver mutual exclusion"
# Python deps live in ONE uv-managed venv (<repo>/.venv, pinned by uv.lock); PATH above puts it first.
# uv sync --check exits 1 when the venv is missing, stale or has extra packages.
require_cmd uv "manages the Python venv (install: https://docs.astral.sh/uv/)" -- uv --version
if command -v uv >/dev/null 2>&1; then
if [[ ! -x "$REPO_ROOT/.venv/bin/python" ]]; then
printf 'MISSING: .venv/bin/python (run: uv sync)\n'
fail=$((fail + 1))
elif sync_out=$(uv sync --check --locked --offline --project "$REPO_ROOT" 2>&1); then
[[ $verbose -eq 1 ]] && printf 'OK py: .venv matches uv.lock\n'
else
printf 'BROKEN: .venv does not match uv.lock (run: uv sync): %s\n' "$(tail -n 1 <<<"$sync_out")"
fail=$((fail + 1))
fi
fi
require_cmd subliminal "sub fallback chain in media_stack/subtitles.py" -- subliminal --version
require_cmd ffsubsync "consolidate-subs.py timing sync" -- ffsubsync --version
optional_cmd nvidia-smi "NVIDIA driver + GPU presence (used by Jellyfin HW transcode)" \
-- nvidia-smi --query-gpu=name --format=csv,noheader
if command -v docker >/dev/null 2>&1; then
if docker compose -f "$REPO_ROOT/docker-compose.yml" config >/dev/null 2>&1; then
printf 'OK: docker-compose.yml parses\n'
else
printf 'MISSING: docker-compose.yml fails to parse — fix before bringing the stack up\n'
fail=$((fail + 1))
fi
fi
for f in consolidate-subs.py normalize-audio.py; do
if [[ ! -r "$REPO_ROOT/$f" ]]; then
printf 'MISSING repo: %s (expected in %s)\n' "$f" "$REPO_ROOT"
fail=$((fail + 1))
fi
done
printf '\n'
if [[ $fail -gt 0 ]]; then
printf 'PREFLIGHT FAILED: %d required dependency miss(es). %d optional miss(es).\n' "$fail" "$optfail"
exit 1
fi
if [[ $optfail -gt 0 ]]; then
printf 'PREFLIGHT OK with %d optional miss(es).\n' "$optfail"
[[ $strict -eq 1 ]] && exit 1
exit 2
fi
printf 'PREFLIGHT OK: every required + optional dependency present.\n'
exit 0