Ctrl K
ComunidadGuías…

preflight.sh

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/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