#!/usr/bin/env bash
set -Eeuo pipefail

VERSION="1.1.0-public"
MODE="${1:---check}"

# Configuration locale facultative. Les valeurs propres au NAS ne sont jamais
# codées en dur dans la version publique.
CONFIG_FILE="${JARVIS_CONFIG:-$HOME/.config/jarvis/jarvis.env}"
if [[ -r "$CONFIG_FILE" ]]; then
    set -a
    # shellcheck disable=SC1090
    . "$CONFIG_FILE"
    set +a
fi

MODEL="${JARVIS_MODEL:-qwen3.6:35b-a3b}"
HERMES_PORT="${JARVIS_HERMES_PORT:-8642}"
WEBUI_PORT="${JARVIS_WEBUI_PORT:-3000}"
CAMOFOX_PORT="${JARVIS_CAMOFOX_PORT:-9377}"
NFS_MOUNT="${JARVIS_NFS_MOUNT:-/mnt/jarvis-backup}"
NFS_SOURCE="${JARVIS_NFS_SOURCE:-}"
NFS_GROUP="${JARVIS_NFS_GROUP:-nas-backup}"
NFS_GID="${JARVIS_NFS_GID:-3000}"

OK=0
WARN=0
FAIL=0

section() { printf '\n=== %s ===\n' "$1"; }
pass()    { printf 'OK   %s\n' "$1"; OK=$((OK+1)); }
warning() { printf 'WARN %s\n' "$1"; WARN=$((WARN+1)); }
failure() { printf 'FAIL %s\n' "$1"; FAIL=$((FAIL+1)); }

have() {
    command -v "$1" >/dev/null 2>&1
}

check_cmd() {
    if have "$1"; then
        pass "$2"
    else
        warning "$2 absent"
    fi
}

check_file() {
    if [[ -e "$1" ]]; then
        pass "$2"
    else
        warning "$2 absent"
    fi
}

listener() {
    ss -ltn 2>/dev/null |
        awk -v p=":$1" '$4 ~ p"$" {print $4; exit}'
}

plan() {
cat <<'PLAN'

JARVIS GOLDEN BUILD — PLAN

01. Vérifier Ubuntu, CPU, RAM, réseau et stockage.
02. Installer les prérequis Linux.
03. Installer Ollama.
04. Configurer Ollama :
      OLLAMA_CONTEXT_LENGTH=65536
      OLLAMA_FLASH_ATTENTION=true
      OLLAMA_KV_CACHE_TYPE=q8_0
      OLLAMA_KEEP_ALIVE=-1
05. Installer uniquement qwen3.6:35b-a3b.
06. Installer Hermes Agent.
07. Restaurer depuis le backup :
      - config.yaml
      - .env
      - skills
      - mémoire / SOUL / USER
      - hooks et allowlist
      - références NeuTTS
      - services systemd utilisateur
08. Réinstaller dans le venv Hermes :
      - faster-whisper
      - piper-tts (fallback)
      - neutts
      - neucodec
      - torchao==0.17.0
09. Restaurer Whisper small FR.
10. Restaurer NeuTTS French Q4 / speed 1.0.
11. Installer Camofox persistant sur 127.0.0.1:9377.
12. Installer Docker + Open WebUI.
13. Restaurer NFS / backup / watchdog.
14. Restaurer Telegram et son allowlist.
15. Valider le hook de sécurité.
16. Valider research-watch.
17. Effectuer les tests finaux.

EXCLUS :

- qwen3.5:4b
- qwen3.5:4b-aux
- router 4B
- router-test
- Chatbox
- anciennes API privées UniFi
- fichiers temporaires de tests

PLAN
}

check() {

section "IDENTITE"
echo "Bootstrap : $VERSION"
echo "User      : $USER"
echo "HOME      : $HOME"
echo "Hostname  : $(hostname)"

section "SYSTEME"

if [[ -r /etc/os-release ]]; then
    . /etc/os-release
    echo "OS        : ${PRETTY_NAME:-inconnu}"

    if [[ "${ID:-}" == "ubuntu" ]]; then
        pass "Ubuntu détecté"
    else
        warning "Distribution différente d'Ubuntu"
    fi
else
    failure "/etc/os-release inaccessible"
fi

ARCH="$(uname -m)"
[[ "$ARCH" == "x86_64" ]] &&
    pass "Architecture x86_64" ||
    failure "Architecture inattendue : $ARCH"

CPU="$(
    awk -F: '/model name/ {
        gsub(/^ +/,"",$2)
        print $2
        exit
    }' /proc/cpuinfo
)"

echo "CPU       : $CPU"

[[ "$CPU" == *"AMD Ryzen AI Max"* ]] &&
    pass "Ryzen AI Max détecté" ||
    warning "CPU différent du Golden Build"

MEM_KB="$(
    awk '/MemTotal:/ {print $2}' /proc/meminfo
)"

MEM_GB=$((MEM_KB / 1024 / 1024))

echo "RAM Linux : ~${MEM_GB} GiB"

(( MEM_GB >= 28 )) &&
    pass "RAM cohérente avec UMA" ||
    warning "RAM disponible faible"

FREE_GB="$(
    df -BG / |
    awk 'NR==2 {gsub(/G/,"",$4); print $4}'
)"

echo "Libre /   : ${FREE_GB} GiB"

(( FREE_GB >= 80 )) &&
    pass "Espace disque suffisant" ||
    warning "Moins de 80 GiB libres"

section "RESEAU"

ip -4 -o addr show scope global |
    grep -v docker0 || true

ip -4 route show default || true

ip -4 route show default |
    grep -q . &&
    pass "Route par défaut présente" ||
    failure "Pas de route par défaut"

section "OUTILS"

check_cmd curl    "curl"
check_cmd git     "git"
check_cmd xz      "xz"
check_cmd ffmpeg  "ffmpeg"
check_cmd docker  "Docker"
check_cmd node    "Node.js"
check_cmd npm     "npm"

section "OLLAMA"

if have ollama; then
    pass "Ollama installé"
    ollama --version || true
else
    warning "Ollama absent"
fi

[[ "$(systemctl is-active ollama.service 2>/dev/null || true)" == "active" ]] &&
    pass "ollama.service actif" ||
    warning "ollama.service inactif"

if have ollama &&
   ollama list 2>/dev/null |
   awk 'NR>1 {print $1}' |
   grep -Fxq "$MODEL"
then
    pass "$MODEL présent"
else
    warning "$MODEL absent"
fi

ENVIRONMENT="$(
    systemctl show ollama.service \
        -p Environment \
        --value 2>/dev/null || true
)"

for ITEM in \
    "OLLAMA_CONTEXT_LENGTH=65536" \
    "OLLAMA_FLASH_ATTENTION=true" \
    "OLLAMA_KV_CACHE_TYPE=q8_0" \
    "OLLAMA_KEEP_ALIVE=-1"
do
    if [[ "$ENVIRONMENT" == *"$ITEM"* ]]; then
        pass "$ITEM"
    else
        warning "$ITEM non détecté"
    fi
done

section "HERMES"

check_cmd hermes "Hermes CLI"

check_file \
    "$HOME/.hermes/config.yaml" \
    "Hermes config.yaml"

check_file \
    "$HOME/.hermes/.env" \
    "Hermes .env"

[[ "$(
    systemctl --user is-active \
        hermes-gateway.service 2>/dev/null || true
)" == "active" ]] &&
    pass "Hermes gateway actif" ||
    warning "Hermes gateway inactif"

L="$(listener "$HERMES_PORT")"

[[ -n "$L" ]] &&
    pass "Hermes écoute sur $L" ||
    warning "Port Hermes $HERMES_PORT absent"

section "VOIX"

PY="$HOME/.hermes/hermes-agent/venv/bin/python"

if [[ -x "$PY" ]]; then

    pass "Python Hermes présent"

    "$PY" - <<'PY'
mods = [
    "faster_whisper",
    "piper",
    "neutts",
    "neucodec",
    "torchao",
]

for name in mods:
    try:
        module = __import__(name)
        version = getattr(module, "__version__", "")
        print("OK  ", name, version)
    except Exception as exc:
        print(
            "WARN",
            name,
            type(exc).__name__,
            exc
        )
PY

else
    warning "Venv Hermes absent"
fi

check_file \
    "$HOME/.hermes/tts/neutts/juliette.wav" \
    "NeuTTS juliette.wav"

check_file \
    "$HOME/.hermes/tts/neutts/juliette.txt" \
    "NeuTTS juliette.txt"

section "SKILLS"

SKILLS=(
"$HOME/.hermes/skills/local-ai/strix-halo-local-ai/SKILL.md"
"$HOME/.hermes/skills/home-automation/home-assistant-jarvis/SKILL.md"
"$HOME/.hermes/skills/infrastructure/infrastructure-jarvis/SKILL.md"
"$HOME/.hermes/skills/coding/coding-automation/SKILL.md"
"$HOME/.hermes/skills/maintenance/jarvis-maintenance/SKILL.md"
"$HOME/.hermes/skills/research/research-watch/SKILL.md"
)

for FILE in "${SKILLS[@]}"
do
    check_file \
        "$FILE" \
        "Skill $(basename "$(dirname "$FILE")")"
done

section "SECURITE"

check_file \
    "$HOME/.hermes/agent-hooks/protect-system-actions.py" \
    "Hook protect-system-actions"

check_file \
    "$HOME/.hermes/shell-hooks-allowlist.json" \
    "Allowlist hooks"

if [[ -f "$HOME/.hermes/.env" ]] &&
   grep -q '^TELEGRAM_ALLOWED_USERS=' \
       "$HOME/.hermes/.env"
then
    pass "Telegram allowlist configurée"
else
    warning "Telegram allowlist absente"
fi

section "CAMOFOX"

check_file \
    "$HOME/.local/share/camofox/node_modules/.bin/camofox-browser" \
    "Camofox"

[[ "$(
    systemctl --user is-active \
        camofox.service 2>/dev/null || true
)" == "active" ]] &&
    pass "camofox.service actif" ||
    warning "camofox.service inactif"

L="$(listener "$CAMOFOX_PORT")"

if [[ "$L" == "127.0.0.1:$CAMOFOX_PORT" ]]
then
    pass "Camofox limité à localhost"
else
    warning "Camofox listener : ${L:-absent}"
fi

section "OPEN WEBUI"

if have docker &&
   docker inspect open-webui \
       >/dev/null 2>&1
then
    pass "Conteneur open-webui présent"

    RUNNING="$(
        docker inspect \
            -f '{{.State.Running}}' \
            open-webui
    )"

    [[ "$RUNNING" == "true" ]] &&
        pass "Open WebUI actif" ||
        warning "Open WebUI arrêté"
else
    warning "Open WebUI absent"
fi

L="$(listener "$WEBUI_PORT")"

[[ -n "$L" ]] &&
    pass "Open WebUI écoute sur $L" ||
    warning "Port $WEBUI_PORT absent"

section "BACKUP / NFS"

if mountpoint -q "$NFS_MOUNT" 2>/dev/null
then
    pass "NFS monté"
else
    warning "NFS non monté"
fi

for FILE in \
    jarvis-backup \
    jarvis-health \
    jarvis-system-info \
    jarvis-watchdog
do
    check_file \
        "$HOME/.local/bin/$FILE" \
        "$FILE"
done

for TIMER in \
    jarvis-backup.timer \
    jarvis-watchdog.timer
do
    STATE="$(
        systemctl --user \
            is-enabled "$TIMER" \
            2>/dev/null || true
    )"

    [[ "$STATE" == "enabled" ]] &&
        pass "$TIMER activé" ||
        warning "$TIMER : ${STATE:-absent}"
done

section "RESUME"

echo "OK   : $OK"
echo "WARN : $WARN"
echo "FAIL : $FAIL"

echo
echo "Aucune modification n'a été effectuée."

(( FAIL == 0 ))
}


freeze() {

    section "GOLDEN MANIFEST"

    MANIFEST="$HOME/.hermes/jarvis-golden-manifest.env"
    CHECKSUMS="$HOME/.hermes/jarvis-golden-sha256.txt"

    mkdir -p "$HOME/.hermes"

    TMP="$(mktemp)"

    . /etc/os-release

    OLLAMA_VERSION_RAW=""
    HERMES_VERSION_RAW=""
    HERMES_GIT_COMMIT=""
    DOCKER_VERSION_RAW=""
    NODE_VERSION_RAW=""
    WEBUI_IMAGE=""
    WEBUI_IMAGE_ID=""
    MODEL_ID=""

    have ollama &&
        OLLAMA_VERSION_RAW="$(ollama --version 2>/dev/null | head -1 || true)"

    have hermes &&
        HERMES_VERSION_RAW="$(hermes --version 2>/dev/null | head -1 || true)"

    if [[ -d "$HOME/.hermes/hermes-agent/.git" ]]
    then
        HERMES_GIT_COMMIT="$(
            git -C "$HOME/.hermes/hermes-agent"                 rev-parse HEAD 2>/dev/null || true
        )"
    fi

    have docker &&
        DOCKER_VERSION_RAW="$(docker --version 2>/dev/null || true)"

    have node &&
        NODE_VERSION_RAW="$(node --version 2>/dev/null || true)"

    if have docker &&
       docker inspect open-webui >/dev/null 2>&1
    then
        WEBUI_IMAGE="$(
            docker inspect \
                -f '{{.Config.Image}}' \
                open-webui
        )"

        WEBUI_IMAGE_ID="$(
            docker inspect \
                -f '{{.Image}}' \
                open-webui
        )"
    fi

    if have ollama
    then
        MODEL_ID="$(
            ollama list 2>/dev/null |
            awk -v m="$MODEL" '
                NR > 1 && $1 == m {
                    print $2
                    exit
                }
            '
        )"
    fi

    {
        echo "# Jarvis Golden Build manifest"
        echo "# Generated automatically — no secrets"

        printf 'MANIFEST_VERSION=%q\n' "2"
        printf 'CREATED_AT=%q\n' "$(date --iso-8601=seconds)"

        printf 'HOSTNAME=%q\n' "$(hostname)"
        printf 'ARCH=%q\n' "$(uname -m)"
        printf 'KERNEL=%q\n' "$(uname -r)"

        printf 'OS_ID=%q\n' "${ID:-}"
        printf 'OS_VERSION_ID=%q\n' "${VERSION_ID:-}"
        printf 'OS_PRETTY_NAME=%q\n' "${PRETTY_NAME:-}"

        printf 'OLLAMA_VERSION_RAW=%q\n' "$OLLAMA_VERSION_RAW"
        printf 'HERMES_VERSION_RAW=%q\n' "$HERMES_VERSION_RAW"
        printf 'HERMES_GIT_COMMIT=%q\n' "$HERMES_GIT_COMMIT"
        printf 'DOCKER_VERSION_RAW=%q\n' "$DOCKER_VERSION_RAW"
        printf 'NODE_VERSION_RAW=%q\n' "$NODE_VERSION_RAW"

        printf 'OLLAMA_MODEL=%q\n' "$MODEL"
        printf 'OLLAMA_MODEL_ID=%q\n' "$MODEL_ID"

        printf 'OLLAMA_CONTEXT_LENGTH=%q\n' "65536"
        printf 'OLLAMA_FLASH_ATTENTION=%q\n' "true"
        printf 'OLLAMA_KV_CACHE_TYPE=%q\n' "q8_0"
        printf 'OLLAMA_KEEP_ALIVE=%q\n' "-1"

        printf 'OPEN_WEBUI_IMAGE=%q\n' "$WEBUI_IMAGE"
        printf 'OPEN_WEBUI_IMAGE_ID=%q\n' "$WEBUI_IMAGE_ID"

        printf 'HERMES_API_PORT=%q\n' "$HERMES_PORT"
        printf 'OPEN_WEBUI_PORT=%q\n' "$WEBUI_PORT"
        printf 'CAMOFOX_PORT=%q\n' "$CAMOFOX_PORT"
    } > "$TMP"

    PY="$HOME/.hermes/hermes-agent/venv/bin/python"

    if [[ -x "$PY" ]]
    then
        "$PY" - <<'PYVERS' >> "$TMP"
from importlib.metadata import version, PackageNotFoundError

packages = {
    "FASTER_WHISPER_VERSION": "faster-whisper",
    "PIPER_TTS_VERSION": "piper-tts",
    "NEUTTS_VERSION": "neutts",
    "NEUCODEC_VERSION": "neucodec",
    "TORCH_VERSION": "torch",
    "TORCHAUDIO_VERSION": "torchaudio",
    "TORCHAO_VERSION": "torchao",
    "TRANSFORMERS_VERSION": "transformers",
    "LLAMA_CPP_VERSION": "llama-cpp-python",
}

for key, package in packages.items():
    try:
        value = version(package)
    except PackageNotFoundError:
        value = ""

    print(f"{key}={value!r}")
PYVERS
    fi

    chmod 600 "$TMP"
    mv "$TMP" "$MANIFEST"

    : > "$CHECKSUMS"

    HASH_PATHS=(
        "$HOME/.hermes/config.yaml"
        "$HOME/.hermes/agent-hooks"
        "$HOME/.hermes/skills"
        "$HOME/.hermes/tts"
        "$HOME/.config/systemd/user"
        "$HOME/.local/bin/jarvis-health"
        "$HOME/.local/bin/jarvis-system-info"
        "$HOME/.local/bin/jarvis-backup"
        "$HOME/.local/bin/jarvis-watchdog"
        "$HOME/.local/bin/jarvis-bootstrap"
    )

    for ITEM in "${HASH_PATHS[@]}"
    do
        if [[ -f "$ITEM" ]]
        then
            sha256sum "$ITEM" >> "$CHECKSUMS"

        elif [[ -d "$ITEM" ]]
        then
            find "$ITEM" \
                -type f \
                -print0 2>/dev/null |
                sort -z |
                xargs -0 -r sha256sum \
                >> "$CHECKSUMS"
        fi
    done

    chmod 600 "$CHECKSUMS"

    pass "Manifest créé : $MANIFEST"
    pass "Checksums créés : $CHECKSUMS"

    echo
    echo "Aucun secret de ~/.hermes/.env n'est enregistré."
}


ollama_dropin_content() {

cat <<'EOF'
[Service]
Environment="OLLAMA_CONTEXT_LENGTH=65536"
Environment="OLLAMA_FLASH_ATTENTION=true"
Environment="OLLAMA_KV_CACHE_TYPE=q8_0"
Environment="OLLAMA_KEEP_ALIVE=-1"
EOF

}


simulate_base() {

    section "SIMULATION BASE"

    echo "Aucune modification ne sera effectuée."
    echo

    PACKAGES=(
        ca-certificates
        curl
        git
        jq
        zstd
        ffmpeg
        pciutils
        rsync
    )

    echo "Paquets Ubuntu :"

    for PKG in "${PACKAGES[@]}"
    do
        if dpkg-query \
            -W \
            -f='${Status}' \
            "$PKG" 2>/dev/null |
            grep -q 'install ok installed'
        then
            echo "  OK   $PKG"
        else
            echo "  ADD  $PKG"
        fi
    done

    echo
    echo "Ollama :"

    if have ollama
    then
        echo "  OK   déjà installé"
        ollama --version 2>/dev/null || true
    else
        echo "  ADD  installation officielle Ollama"
    fi

    echo
    echo "Configuration systemd Ollama :"

    DROPIN="/etc/systemd/system/ollama.service.d/20-jarvis.conf"

    EXPECTED="$(ollama_dropin_content)"

    if [[ -f "$DROPIN" ]] &&
       [[ "$(cat "$DROPIN")" == "$EXPECTED" ]]
    then
        echo "  OK   $DROPIN"
    else
        echo "  ADD/UPDATE $DROPIN"
    fi

    echo
    echo "Le modèle $MODEL n'est PAS téléchargé par --apply-base."
    echo "Il sera traité dans l'étape suivante du bootstrap."
}


apply_base() {

    section "APPLY BASE"

    if [[ "$(id -u)" -eq 0 ]]
    then
        failure "Ne pas exécuter jarvis-bootstrap en root."
        return 1
    fi

    if [[ ! -r /etc/os-release ]]
    then
        failure "/etc/os-release inaccessible"
        return 1
    fi

    . /etc/os-release

    if [[ "${ID:-}" != "ubuntu" ]]
    then
        failure "Cette étape est prévue pour Ubuntu."
        return 1
    fi

    if [[ "$(uname -m)" != "x86_64" ]]
    then
        failure "Architecture x86_64 requise."
        return 1
    fi

    if ! have sudo
    then
        failure "sudo absent"
        return 1
    fi

    echo "Validation sudo..."
    sudo -v

    PACKAGES=(
        ca-certificates
        curl
        git
        jq
        zstd
        ffmpeg
        pciutils
        rsync
    )

    echo
    echo "Installation des prérequis Ubuntu..."

    sudo apt-get update

    sudo apt-get install -y \
        "${PACKAGES[@]}"

    if ! have ollama
    then
        echo
        echo "Installation Ollama..."

        INSTALLER="$(mktemp)"

        curl -fsSL \
            https://ollama.com/install.sh \
            -o "$INSTALLER"

        sh -n "$INSTALLER"

        OLLAMA_INSTALL_VERSION=""

        MANIFEST="$HOME/.hermes/jarvis-golden-manifest.env"

        if [[ -f "$MANIFEST" ]]
        then
            set +u
            source "$MANIFEST"
            set -u

            OLLAMA_INSTALL_VERSION="$(
                printf '%s\n' "${OLLAMA_VERSION_RAW:-}" |
                grep -Eo '[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?' |
                head -1 || true
            )"
        fi

        if [[ -n "$OLLAMA_INSTALL_VERSION" ]]
        then
            echo "Installation Ollama version figée : $OLLAMA_INSTALL_VERSION"

            OLLAMA_VERSION="$OLLAMA_INSTALL_VERSION"                 sh "$INSTALLER"
        else
            echo "Version Ollama non figée : installation stable actuelle."
            sh "$INSTALLER"
        fi

        rm -f "$INSTALLER"
    else
        echo
        echo "Ollama déjà installé — aucune réinstallation."
    fi

    if ! systemctl cat ollama.service \
        >/dev/null 2>&1
    then
        failure "ollama.service introuvable après installation"
        return 1
    fi

    DROPIN_DIR="/etc/systemd/system/ollama.service.d"
    DROPIN="$DROPIN_DIR/20-jarvis.conf"

    TMP="$(mktemp)"

    ollama_dropin_content > "$TMP"

    CHANGED=0

    if ! sudo cmp -s "$TMP" "$DROPIN" 2>/dev/null
    then
        echo "Installation de la configuration Golden Build Ollama..."

        sudo install \
            -d \
            -m 755 \
            "$DROPIN_DIR"

        sudo install \
            -m 644 \
            "$TMP" \
            "$DROPIN"

        CHANGED=1
    else
        echo "Configuration Ollama déjà conforme."
    fi

    rm -f "$TMP"

    sudo systemctl daemon-reload
    sudo systemctl enable ollama.service

    if [[ "$CHANGED" -eq 1 ]]
    then
        sudo systemctl restart ollama.service

    elif [[ "$(
        systemctl is-active \
            ollama.service 2>/dev/null || true
    )" != "active" ]]
    then
        sudo systemctl start ollama.service
    fi

    sleep 2

    if [[ "$(
        systemctl is-active \
            ollama.service 2>/dev/null || true
    )" != "active" ]]
    then
        failure "ollama.service n'est pas actif"
        return 1
    fi

    ENVIRONMENT="$(
        systemctl show \
            ollama.service \
            -p Environment \
            --value
    )"

    for ITEM in \
        "OLLAMA_CONTEXT_LENGTH=65536" \
        "OLLAMA_FLASH_ATTENTION=true" \
        "OLLAMA_KV_CACHE_TYPE=q8_0" \
        "OLLAMA_KEEP_ALIVE=-1"
    do
        if [[ "$ENVIRONMENT" == *"$ITEM"* ]]
        then
            pass "$ITEM"
        else
            failure "$ITEM absent"
        fi
    done

    echo
    echo "BASE : OK"
    echo
    echo "Le modèle $MODEL n'a pas encore été téléchargé."
}


load_golden_ai() {

    GOLDEN_MODEL="$MODEL"
    GOLDEN_MODEL_ID=""
    GOLDEN_HERMES_COMMIT=""
    GOLDEN_HERMES_VERSION=""

    MANIFEST="$HOME/.hermes/jarvis-golden-manifest.env"

    if [[ -f "$MANIFEST" ]]
    then
        set +u
        source "$MANIFEST"
        set -u

        GOLDEN_MODEL="${OLLAMA_MODEL:-$MODEL}"
        GOLDEN_MODEL_ID="${OLLAMA_MODEL_ID:-}"
        GOLDEN_HERMES_COMMIT="${HERMES_GIT_COMMIT:-}"
        GOLDEN_HERMES_VERSION="${HERMES_VERSION_RAW:-}"
    fi
}


simulate_ai() {

    section "SIMULATION IA"

    load_golden_ai

    echo "Aucune modification ne sera effectuée."
    echo

    echo "Modèle attendu : $GOLDEN_MODEL"

    if have ollama
    then
        CURRENT_MODEL_ID="$(
            ollama list 2>/dev/null |
            awk -v m="$GOLDEN_MODEL" '
                NR > 1 && $1 == m {
                    print $2
                    exit
                }
            '
        )"

        if [[ -n "$CURRENT_MODEL_ID" ]]
        then
            echo "  OK   modèle présent"
            echo "       ID actuel : $CURRENT_MODEL_ID"

            if [[ -n "$GOLDEN_MODEL_ID" ]]
            then
                echo "       ID golden : $GOLDEN_MODEL_ID"

                if [[ "$CURRENT_MODEL_ID" == "$GOLDEN_MODEL_ID" ]]
                then
                    echo "  OK   modèle identique au Golden Build"
                else
                    echo "  WARN modèle différent du Golden Build"
                fi
            fi
        else
            echo "  ADD  ollama pull $GOLDEN_MODEL"
        fi
    else
        echo "  FAIL Ollama absent"
    fi

    echo
    echo "Hermes :"

    HERMES_BIN=""

    if have hermes
    then
        HERMES_BIN="$(command -v hermes)"

    elif [[ -x "$HOME/.local/bin/hermes" ]]
    then
        HERMES_BIN="$HOME/.local/bin/hermes"
    fi

    if [[ -n "$HERMES_BIN" ]]
    then
        echo "  OK   $HERMES_BIN"
        "$HERMES_BIN" --version 2>/dev/null || true

        if [[ -d "$HOME/.hermes/hermes-agent/.git" ]]
        then
            CURRENT_COMMIT="$(
                git -C "$HOME/.hermes/hermes-agent" \
                    rev-parse HEAD 2>/dev/null || true
            )"

            echo "       commit actuel : ${CURRENT_COMMIT:-inconnu}"

            if [[ -n "$GOLDEN_HERMES_COMMIT" ]]
            then
                echo "       commit golden : $GOLDEN_HERMES_COMMIT"

                if [[ "$CURRENT_COMMIT" == "$GOLDEN_HERMES_COMMIT" ]]
                then
                    echo "  OK   Hermes identique au Golden Build"
                else
                    echo "  WARN commit Hermes différent"
                fi
            fi
        fi
    else
        echo "  ADD  installation Hermes"

        if [[ -n "$GOLDEN_HERMES_COMMIT" ]]
        then
            echo "       commit : $GOLDEN_HERMES_COMMIT"
        else
            echo "       commit : dernière version disponible"
        fi
    fi

    echo
    echo "Cette étape ne restaure PAS :"
    echo "  - config.yaml"
    echo "  - .env"
    echo "  - Telegram"
    echo "  - Home Assistant"
    echo "  - UniFi"
    echo "  - skills utilisateur"
    echo "  - voix"
    echo
    echo "Ces éléments seront traités par l'étape restore."
}


apply_ai() {

    section "APPLY IA"

    load_golden_ai

    # --------------------------------------------------------------
    # Vérification Ollama
    # --------------------------------------------------------------

    if ! have ollama
    then
        failure "Ollama absent. Exécuter d'abord --apply-base."
        return 1
    fi

    if [[ "$(
        systemctl is-active ollama.service 2>/dev/null || true
    )" != "active" ]]
    then
        failure "ollama.service n'est pas actif"
        return 1
    fi

    if ! curl -fsS \
        http://127.0.0.1:11434/api/tags \
        >/dev/null
    then
        failure "API Ollama inaccessible sur 127.0.0.1:11434"
        return 1
    fi

    pass "API Ollama accessible"

    # --------------------------------------------------------------
    # Modèle
    # --------------------------------------------------------------

    SEED_DIR="$(
        model_seed_dir "$GOLDEN_MODEL"
    )"

    MODEL_PRESENT="$(
        ollama list 2>/dev/null |
        awk -v m="$GOLDEN_MODEL" '
            NR > 1 && $1 == m {
                print $2
                exit
            }
        '
    )"

    if [[ -z "$MODEL_PRESENT" &&
          -d "$SEED_DIR" ]]
    then
        echo
        echo "Tentative de restauration hors ligne..."

        restore_model_seed "$GOLDEN_MODEL" ||
            return 1
    fi

    CURRENT_MODEL_ID="$(
        ollama list 2>/dev/null |
        awk -v m="$GOLDEN_MODEL" '
            NR > 1 && $1 == m {
                print $2
                exit
            }
        '
    )"

    if [[ -z "$CURRENT_MODEL_ID" ]]
    then
        echo
        echo "Téléchargement du modèle : $GOLDEN_MODEL"

        ollama pull "$GOLDEN_MODEL"

        CURRENT_MODEL_ID="$(
            ollama list 2>/dev/null |
            awk -v m="$GOLDEN_MODEL" '
                NR > 1 && $1 == m {
                    print $2
                    exit
                }
            '
        )"
    else
        echo
        echo "Modèle déjà présent : $GOLDEN_MODEL"
    fi

    if [[ -z "$CURRENT_MODEL_ID" ]]
    then
        failure "$GOLDEN_MODEL introuvable après pull"
        return 1
    fi

    pass "$GOLDEN_MODEL présent"
    echo "ID modèle : $CURRENT_MODEL_ID"

    if [[ -n "$GOLDEN_MODEL_ID" ]]
    then
        if [[ "$CURRENT_MODEL_ID" == "$GOLDEN_MODEL_ID" ]]
        then
            pass "ID modèle identique au Golden Build"
        else
            failure "Le tag Ollama ne correspond plus au modèle Golden Build."

            echo
            echo "Attendu : $GOLDEN_MODEL_ID"
            echo "Obtenu  : $CURRENT_MODEL_ID"
            echo
            echo "Le bootstrap refuse de déclarer cette reconstruction identique."

            return 1
        fi
    else
        warning "Aucun ID modèle Golden figé pour comparaison"
    fi

    # --------------------------------------------------------------
    # Hermes
    # --------------------------------------------------------------

    HERMES_BIN=""

    if have hermes
    then
        HERMES_BIN="$(command -v hermes)"

    elif [[ -x "$HOME/.local/bin/hermes" ]]
    then
        HERMES_BIN="$HOME/.local/bin/hermes"
    fi

    if [[ -z "$HERMES_BIN" ]]
    then

        echo
        echo "Installation Hermes..."

        INSTALLER="$(mktemp)"

        curl -fsSL \
            https://hermes-agent.nousresearch.com/install.sh \
            -o "$INSTALLER"

        bash -n "$INSTALLER"

        ARGS=(
            --skip-setup
            --skip-browser
            --skip-computer-use
            --non-interactive
            --hermes-home
            "$HOME/.hermes"
        )

        if [[ -n "$GOLDEN_HERMES_COMMIT" ]]
        then
            echo "Commit Hermes Golden : $GOLDEN_HERMES_COMMIT"

            ARGS+=(
                --commit
                "$GOLDEN_HERMES_COMMIT"
            )
        fi

        bash "$INSTALLER" "${ARGS[@]}"

        rm -f "$INSTALLER"

        if [[ -x "$HOME/.local/bin/hermes" ]]
        then
            HERMES_BIN="$HOME/.local/bin/hermes"
        elif have hermes
        then
            HERMES_BIN="$(command -v hermes)"
        fi
    else
        echo
        echo "Hermes déjà installé — aucune réinstallation."
    fi

    if [[ -z "$HERMES_BIN" || ! -x "$HERMES_BIN" ]]
    then
        failure "Hermes introuvable après installation"
        return 1
    fi

    pass "Hermes CLI présent"

    "$HERMES_BIN" --version || {
        failure "Hermes --version a échoué"
        return 1
    }

    # --------------------------------------------------------------
    # Validation commit Hermes
    # --------------------------------------------------------------

    CURRENT_COMMIT=""

    if [[ -d "$HOME/.hermes/hermes-agent/.git" ]]
    then
        CURRENT_COMMIT="$(
            git -C "$HOME/.hermes/hermes-agent" \
                rev-parse HEAD 2>/dev/null || true
        )"
    fi

    if [[ -n "$GOLDEN_HERMES_COMMIT" ]]
    then
        if [[ "$CURRENT_COMMIT" == "$GOLDEN_HERMES_COMMIT" ]]
        then
            pass "Commit Hermes identique au Golden Build"
        else
            failure "Commit Hermes différent du Golden Build"

            echo "Attendu : $GOLDEN_HERMES_COMMIT"
            echo "Obtenu  : ${CURRENT_COMMIT:-inconnu}"

            return 1
        fi
    else
        warning "Commit Hermes Golden non disponible"
    fi

    echo
    echo "IA : OK"
    echo
    echo "Runtime validé :"
    echo "  Ollama"
    echo "  $GOLDEN_MODEL"
    echo "  Hermes"
    echo
    echo "La configuration utilisateur n'a pas encore été restaurée."
}


find_latest_backup() {

    if ! mountpoint -q "$NFS_MOUNT" 2>/dev/null
    then
        return 1
    fi

    find "$NFS_MOUNT" \
        -maxdepth 1 \
        -type f \
        -name '*.tar.gz' \
        -printf '%T@ %p\n' 2>/dev/null |
        sort -nr |
        head -1 |
        cut -d' ' -f2-
}


find_backup_checksum() {

    local archive="$1"

    local candidates=(
        "${archive}.sha256"
        "${archive%.tar.gz}.sha256"
    )

    local candidate

    for candidate in "${candidates[@]}"
    do
        if [[ -f "$candidate" ]]
        then
            printf '%s\n' "$candidate"
            return 0
        fi
    done

    return 1
}


verify_backup_archive() {

    local archive="$1"
    local checksum
    local expected
    local actual

    if [[ ! -r "$archive" ]]
    then
        failure "Archive inaccessible : $archive"
        return 1
    fi

    checksum="$(
        find_backup_checksum "$archive" || true
    )"

    if [[ -z "$checksum" ]]
    then
        failure "Checksum SHA256 absent"
        return 1
    fi

    expected="$(
        awk 'NR == 1 {print $1}' "$checksum"
    )"

    actual="$(
        sha256sum "$archive" |
        awk '{print $1}'
    )"

    if [[ -z "$expected" ]]
    then
        failure "Checksum vide : $checksum"
        return 1
    fi

    if [[ "$expected" != "$actual" ]]
    then
        failure "SHA256 incorrect"

        echo "Attendu : $expected"
        echo "Obtenu  : $actual"

        return 1
    fi

    pass "SHA256 valide"

    if ! tar -tzf "$archive" >/dev/null
    then
        failure "Archive tar.gz invalide"
        return 1
    fi

    pass "Archive tar.gz lisible"

    UNSAFE="$(
        tar -tzf "$archive" |
        awk '
            /^\// {
                print
                exit
            }

            /(^|\/)\.\.(\/|$)/ {
                print
                exit
            }
        '
    )"

    if [[ -n "$UNSAFE" ]]
    then
        failure "Chemin dangereux détecté dans l'archive"
        echo "$UNSAFE"
        return 1
    fi

    pass "Aucun chemin dangereux détecté"

    return 0
}


simulate_restore() {

    section "SIMULATION RESTORE"

    echo "Aucune modification ne sera effectuée."
    echo

    if ! mountpoint -q "$NFS_MOUNT" 2>/dev/null
    then
        failure "NFS non monté : $NFS_MOUNT"
        return 1
    fi

    pass "NFS monté"

    ARCHIVE="$(
        find_latest_backup || true
    )"

    if [[ -z "$ARCHIVE" ]]
    then
        failure "Aucun backup .tar.gz trouvé"
        return 1
    fi

    echo
    echo "Dernier backup :"
    echo "  $ARCHIVE"
    echo

    verify_backup_archive "$ARCHIVE" || return 1

    CHECKSUM="$(
        find_backup_checksum "$ARCHIVE" || true
    )"

    echo
    echo "Checksum :"
    echo "  $CHECKSUM"

    echo
    echo "Taille :"
    du -h "$ARCHIVE" |
        awk '{print "  " $1}'

    echo
    echo "40 premières entrées :"

    tar -tzf "$ARCHIVE" |
        head -40 |
        sed 's/^/  /'

    echo
    echo "Éléments Jarvis détectés :"

    MEMBERS="$(
        tar -tzf "$ARCHIVE"
    )"

    for ITEM in \
        ".hermes/config.yaml" \
        ".hermes/.env" \
        ".hermes/skills" \
        ".hermes/agent-hooks" \
        ".hermes/tts" \
        ".config/systemd/user" \
        ".local/bin/jarvis-health" \
        ".local/bin/jarvis-system-info" \
        ".local/bin/jarvis-backup" \
        ".local/bin/jarvis-watchdog"
    do
        if printf '%s\n' "$MEMBERS" |
           grep -Fq "$ITEM"
        then
            echo "  OK   $ITEM"
        else
            echo "  INFO absent : $ITEM"
        fi
    done

    echo
    echo "Simulation terminée."
}


create_restore_rollback() {

    local rollback_dir
    local rollback
    local paths=()

    rollback_dir="$HOME/jarvis-restore-rollbacks"

    mkdir -p "$rollback_dir"
    chmod 700 "$rollback_dir"

    rollback="$rollback_dir/rollback-$(date '+%Y%m%d-%H%M%S').tar.gz"

    [[ -e "$HOME/.hermes" ]] &&
        paths+=(".hermes")

    [[ -e "$HOME/.config/systemd/user" ]] &&
        paths+=(".config/systemd/user")

    [[ -e "$HOME/.local/bin" ]] &&
        paths+=(".local/bin")

    if (( ${#paths[@]} == 0 ))
    then
        warning "Rien à sauvegarder avant restore"
        printf '%s\n' ""
        return 0
    fi

    tar \
        -C "$HOME" \
        --exclude='.hermes/hermes-agent' \
        --exclude='.hermes/cache' \
        --exclude='.hermes/tmp' \
        -czf "$rollback" \
        "${paths[@]}"

    chmod 600 "$rollback"

    printf '%s\n' "$rollback"
}


restore_latest() {

    section "RESTORE LATEST"

    if [[ "$(id -u)" -eq 0 ]]
    then
        failure "Ne pas exécuter le restore en root."
        return 1
    fi

    if ! have rsync
    then
        failure "rsync absent. Exécuter --apply-base."
        return 1
    fi

    if ! have hermes
    then
        failure "Hermes absent. Exécuter --apply-ai."
        return 1
    fi

    if ! mountpoint -q "$NFS_MOUNT" 2>/dev/null
    then
        failure "NFS non monté : $NFS_MOUNT"
        return 1
    fi

    if systemctl --user is-active \
        --quiet hermes-gateway.service 2>/dev/null
    then
        failure "Hermes gateway est actif."

        echo
        echo "Le restore est prévu pour une reconstruction."
        echo "Arrêter d'abord le gateway :"
        echo
        echo "  hermes gateway stop"

        return 1
    fi

    if systemctl --user is-active \
        --quiet camofox.service 2>/dev/null
    then
        failure "Camofox est actif."

        echo
        echo "Arrêter d'abord Camofox :"
        echo
        echo "  systemctl --user stop camofox.service"

        return 1
    fi

    ARCHIVE="$(
        find_latest_backup || true
    )"

    if [[ -z "$ARCHIVE" ]]
    then
        failure "Aucun backup .tar.gz trouvé"
        return 1
    fi

    echo "Backup sélectionné :"
    echo "  $ARCHIVE"
    echo

    verify_backup_archive "$ARCHIVE" || return 1

    echo
    echo "ATTENTION"
    echo "Cette opération va restaurer la configuration Jarvis."
    echo "Un snapshot de rollback sera créé AVANT toute copie."
    echo

    read -r -p \
        "Tape RESTORE pour continuer : " \
        CONFIRM

    if [[ "$CONFIRM" != "RESTORE" ]]
    then
        echo "Restore annulé."
        return 0
    fi

    echo
    echo "Création du rollback..."

    ROLLBACK="$(
        create_restore_rollback
    )"

    if [[ -n "$ROLLBACK" ]]
    then
        pass "Rollback créé"
        echo "  $ROLLBACK"
    fi

    STAGE="$(
        mktemp -d \
            "$HOME/.cache/jarvis-restore.XXXXXX"
    )"

    chmod 700 "$STAGE"

    echo
    echo "Extraction dans :"
    echo "  $STAGE"

    tar \
        --no-same-owner \
        -xzf "$ARCHIVE" \
        -C "$STAGE"

    HERMES_DIR="$(
        find "$STAGE" \
            -maxdepth 4 \
            -type d \
            -name '.hermes' \
            -print \
            -quit
    )"

    if [[ -z "$HERMES_DIR" ]]
    then
        rm -rf "$STAGE"
        failure ".hermes absent du backup"
        return 1
    fi

    ROOT="$(
        dirname "$HERMES_DIR"
    )"

    echo
    echo "Racine détectée :"
    echo "  $ROOT"

    mkdir -p \
        "$HOME/.hermes" \
        "$HOME/.local/bin" \
        "$HOME/.config/systemd/user"

    echo
    echo "Restauration ~/.hermes..."

    rsync \
        -a \
        --delete \
        --exclude='hermes-agent/' \
        --exclude='cache/' \
        --exclude='tmp/' \
        --exclude='jarvis-golden-manifest.env' \
        --exclude='jarvis-golden-sha256.txt' \
        "$ROOT/.hermes/" \
        "$HOME/.hermes/"

    if [[ -d "$ROOT/.local/bin" ]]
    then
        echo "Restauration scripts Jarvis..."

        rsync \
            -a \
            --include='jarvis-*' \
            --exclude='*' \
            "$ROOT/.local/bin/" \
            "$HOME/.local/bin/"
    fi

    if [[ -d "$ROOT/.config/systemd/user" ]]
    then
        echo "Restauration services systemd utilisateur..."

        rsync \
            -a \
            "$ROOT/.config/systemd/user/" \
            "$HOME/.config/systemd/user/"
    fi

    [[ -f "$HOME/.hermes/.env" ]] &&
        chmod 600 "$HOME/.hermes/.env"

    [[ -f "$HOME/.hermes/shell-hooks-allowlist.json" ]] &&
        chmod 600 \
            "$HOME/.hermes/shell-hooks-allowlist.json"

    if [[ -d "$HOME/.hermes/agent-hooks" ]]
    then
        find "$HOME/.hermes/agent-hooks" \
            -type f \
            -name '*.py' \
            -exec chmod 700 {} +
    fi

    if [[ -d "$HOME/.local/bin" ]]
    then
        find "$HOME/.local/bin" \
            -maxdepth 1 \
            -type f \
            -name 'jarvis-*' \
            -exec chmod 700 {} +
    fi

    systemctl --user daemon-reload

    rm -rf "$STAGE"

    echo
    echo "Validation fichiers essentiels..."

    ESSENTIALS=(
        "$HOME/.hermes/config.yaml"
        "$HOME/.hermes/.env"
        "$HOME/.hermes/skills"
    )

    RESTORE_FAIL=0

    for ITEM in "${ESSENTIALS[@]}"
    do
        if [[ -e "$ITEM" ]]
        then
            pass "$ITEM"
        else
            failure "$ITEM absent"
            RESTORE_FAIL=1
        fi
    done

    if [[ "$RESTORE_FAIL" -ne 0 ]]
    then
        failure "Restore incomplet"

        echo
        echo "Rollback disponible :"
        echo "  ${ROLLBACK:-aucun}"

        return 1
    fi

    if have hermes
    then
        echo
        echo "Validation Hermes..."

        hermes config check || {
            warning "hermes config check signale un problème"
        }

        if [[ -d "$HOME/.hermes/agent-hooks" ]]
        then
            hermes hooks doctor || {
                warning "hermes hooks doctor signale un problème"
            }
        fi
    fi

    echo
    echo "RESTORE : OK"

    if [[ -n "${ROLLBACK:-}" ]]
    then
        echo
        echo "Rollback local :"
        echo "  $ROLLBACK"
    fi

    echo
    echo "Aucun service n'a été démarré automatiquement."
    echo "La prochaine étape est la restauration des dépendances"
    echo "voix, Camofox, Open WebUI et la validation finale."
}


simulate_final() {

    section "SIMULATION FINAL"

    echo "Aucune modification ne sera effectuée."
    echo

    # ----------------------------------------------------------
    # VOIX
    # ----------------------------------------------------------

    section "VOIX"

    PY="$HOME/.hermes/hermes-agent/venv/bin/python"

    if [[ -x "$PY" ]]
    then
        pass "Python Hermes présent"

        "$PY" - <<'PYVOICE'
from importlib.metadata import version, PackageNotFoundError

packages = [
    "faster-whisper",
    "piper-tts",
    "neutts",
    "neucodec",
    "torch",
    "torchaudio",
    "torchao",
    "transformers",
    "llama-cpp-python",
]

for package in packages:
    try:
        print(f"OK   {package}: {version(package)}")
    except PackageNotFoundError:
        print(f"WARN {package}: ABSENT")
PYVOICE

    else
        warning "Python Hermes absent"
    fi

    check_file \
        "$HOME/.hermes/tts/neutts/juliette.wav" \
        "NeuTTS juliette.wav"

    check_file \
        "$HOME/.hermes/tts/neutts/juliette.txt" \
        "NeuTTS juliette.txt"

    echo
    echo "Configuration STT/TTS :"

    if [[ -x "$PY" &&
          -f "$HOME/.hermes/config.yaml" ]]
    then
        "$PY" <<'PYCFG'
from pathlib import Path
import yaml

cfg = yaml.safe_load(
    (Path.home() / ".hermes/config.yaml").read_text()
) or {}

def value(path):
    cur = cfg
    for part in path.split("."):
        if not isinstance(cur, dict):
            return None
        cur = cur.get(part)
    return cur

checks = {
    "stt.provider": "local",
    "stt.language": "fr",
    "stt.local.model": "small",
    "stt.local.language": "fr",
    "tts.provider": "neutts",
    "tts.speed": 1.0,
    "tts.neutts.model":
        "neuphonic/neutts-nano-french-q4-gguf",
    "tts.neutts.device": "cpu",
}

for key, expected in checks.items():
    current = value(key)

    if current == expected:
        print(f"OK   {key} = {current}")
    else:
        print(
            f"WARN {key}: actuel={current!r} "
            f"attendu={expected!r}"
        )
PYCFG
    fi

    # ----------------------------------------------------------
    # CAMOFOX
    # ----------------------------------------------------------

    section "CAMOFOX"

    CAMOFOX_PACKAGE="$HOME/.local/share/camofox/node_modules/@askjo/camofox-browser/package.json"

    if [[ -f "$CAMOFOX_PACKAGE" ]]
    then
        CAMOFOX_VERSION="$(
            python3 - "$CAMOFOX_PACKAGE" <<'PYCAMO'
import json
import sys

with open(sys.argv[1], encoding="utf-8") as f:
    print(json.load(f).get("version", "inconnue"))
PYCAMO
        )"

        pass "Camofox $CAMOFOX_VERSION"
    else
        warning "Camofox absent"
    fi

    if [[ "$(
        systemctl --user is-active \
            camofox.service 2>/dev/null || true
    )" == "active" ]]
    then
        pass "camofox.service actif"
    else
        warning "camofox.service inactif"
    fi

    if curl -fsS \
        "http://127.0.0.1:$CAMOFOX_PORT/health" \
        >/dev/null 2>&1
    then
        pass "Camofox health OK"
    else
        warning "Camofox health inaccessible"
    fi

    L="$(listener "$CAMOFOX_PORT")"

    if [[ "$L" == "127.0.0.1:$CAMOFOX_PORT" ]]
    then
        pass "Camofox limité à localhost"
    else
        warning "Camofox listener : ${L:-absent}"
    fi

    if [[ -d "$HOME/.camofox/profiles" ]]
    then
        pass "Profils persistants Camofox présents"
    else
        warning "Profils persistants Camofox absents"
    fi

    # ----------------------------------------------------------
    # OPEN WEBUI
    # ----------------------------------------------------------

    section "OPEN WEBUI"

    if have docker &&
       docker inspect open-webui >/dev/null 2>&1
    then
        pass "Conteneur open-webui présent"

        RUNNING="$(
            docker inspect \
                -f '{{.State.Running}}' \
                open-webui
        )"

        [[ "$RUNNING" == "true" ]] &&
            pass "Open WebUI actif" ||
            warning "Open WebUI arrêté"

        echo
        echo "Image :"

        docker inspect \
            -f '  {{.Config.Image}}' \
            open-webui

        echo
        echo "Volumes :"

        docker inspect open-webui \
            --format '{{range .Mounts}}{{println "  " .Name "->" .Destination}}{{end}}'

        WEBUI_DATA="$(
            docker inspect open-webui \
                --format '{{range .Mounts}}{{if eq .Destination "/app/backend/data"}}{{.Name}}{{end}}{{end}}'
        )"

        if [[ -n "$WEBUI_DATA" ]]
        then
            pass "Volume persistant /app/backend/data : $WEBUI_DATA"
        else
            warning "Volume persistant Open WebUI non détecté"
        fi

    else
        warning "Open WebUI absent"
    fi

    # ----------------------------------------------------------
    # HERMES
    # ----------------------------------------------------------

    section "HERMES"

    if have hermes
    then
        if hermes config check >/dev/null
        then
            pass "Hermes config check"
        else
            warning "Hermes config check"
        fi

        if hermes hooks doctor >/dev/null
        then
            pass "Hermes hooks doctor"
        else
            warning "Hermes hooks doctor"
        fi
    else
        warning "Hermes absent"
    fi

    check_file \
        "$HOME/.hermes/skills/research/research-watch/SKILL.md" \
        "research-watch"

    check_file \
        "$HOME/.hermes/agent-hooks/protect-system-actions.py" \
        "Hook protect-system-actions"

    if [[ -f "$HOME/.hermes/.env" ]] &&
       grep -q '^TELEGRAM_ALLOWED_USERS=' \
           "$HOME/.hermes/.env"
    then
        pass "Telegram allowlist configurée"
    else
        warning "Telegram allowlist absente"
    fi

    # ----------------------------------------------------------
    # TIMERS
    # ----------------------------------------------------------

    section "TIMERS"

    for TIMER in \
        jarvis-backup.timer \
        jarvis-watchdog.timer
    do
        STATE="$(
            systemctl --user \
                is-enabled "$TIMER" \
                2>/dev/null || true
        )"

        if [[ "$STATE" == "enabled" ]]
        then
            pass "$TIMER activé"
        else
            warning "$TIMER : ${STATE:-absent}"
        fi
    done

    # ----------------------------------------------------------
    # BACKUP
    # ----------------------------------------------------------

    section "COUVERTURE BACKUP"

    ARCHIVE="$(
        find_latest_backup 2>/dev/null || true
    )"

    if [[ -z "$ARCHIVE" ]]
    then
        warning "Dernier backup introuvable"
    else
        echo "Backup analysé :"
        echo "  $ARCHIVE"
        echo

        CONTENTS="$(
            tar -tzf "$ARCHIVE" 2>/dev/null || true
        )"

        if printf '%s\n' "$CONTENTS" |
           grep -qE '(^|/)\.camofox/'
        then
            pass "Profils Camofox couverts"
        else
            warning "Profils Camofox NON couverts"
        fi

        if printf '%s\n' "$CONTENTS" |
           grep -qiE 'open-webui|webui.*data'
        then
            pass "État Open WebUI semble couvert"
        else
            warning "État Open WebUI NON couvert"
        fi
    fi

    echo
    echo "SIMULATION FINAL : terminée"
    echo "Aucune modification n'a été effectuée."
}


golden_get() {

    local key="$1"
    local manifest="$HOME/.hermes/jarvis-golden-manifest.env"

    [[ -f "$manifest" ]] || return 0

    (
        set +u
        source "$manifest"
        eval "printf '%s\n' \"\${$key:-}\""
    )
}


docker_do() {

    if docker info >/dev/null 2>&1
    then
        docker "$@"
    else
        sudo docker "$@"
    fi
}


restore_voice_layer() {

    local extra="$HOME/.hermes/backup-extra"
    local py="$HOME/.hermes/hermes-agent/venv/bin/python"
    local requirements="$extra/voice/requirements.txt"

    section "VOIX"

    if [[ ! -x "$py" ]]
    then
        failure "Python Hermes absent"
        return 1
    fi

    if ! "$py" -m pip --version >/dev/null 2>&1
    then
        echo "Activation de pip..."
        "$py" -m ensurepip --upgrade
    fi

    if [[ -f "$requirements" ]]
    then
        echo "Installation des versions Golden de la voix..."

        "$py" -m pip install \
            -r "$requirements"
    else
        warning "requirements voix absent du backup"

        echo "Installation minimale connue..."

        "$py" -m pip install \
            "faster-whisper==1.2.1" \
            "neutts==1.4.1" \
            "neucodec==0.0.6" \
            "torchao==0.17.0"
    fi

    echo
    echo "Vérification pip..."

    "$py" -m pip check

    if [[ -d "$extra/huggingface/hub" ]]
    then
        echo
        echo "Restauration cache NeuTTS / NeuCodec..."

        mkdir -p "$HOME/.cache/huggingface/hub"

        rsync -a \
            "$extra/huggingface/hub/" \
            "$HOME/.cache/huggingface/hub/"
    else
        warning "Cache Hugging Face absent du backup"
    fi

    check_file \
        "$HOME/.hermes/tts/neutts/juliette.wav" \
        "juliette.wav"

    check_file \
        "$HOME/.hermes/tts/neutts/juliette.txt" \
        "juliette.txt"

    pass "Couche voix restaurée"
}


restore_camofox_layer() {

    local extra="$HOME/.hermes/backup-extra"
    local version="1.14.0"
    local current=""
    local node_major

    section "CAMOFOX"

    if ! have node
    then
        failure "Node.js absent"
        return 1
    fi

    node_major="$(
        node -p 'Number(process.versions.node.split(".")[0])'
    )"

    if (( node_major < 22 ))
    then
        failure "Node.js >=22 requis. Version actuelle : $(node --version)"
        return 1
    fi

    pass "Node $(node --version)"

    if [[ -s "$extra/camofox-version.txt" ]]
    then
        version="$(
            head -1 "$extra/camofox-version.txt"
        )"
    fi

    CAMOFOX_PACKAGE="$HOME/.local/share/camofox/node_modules/@askjo/camofox-browser/package.json"

    if [[ -f "$CAMOFOX_PACKAGE" ]]
    then
        current="$(
            python3 - "$CAMOFOX_PACKAGE" <<'PYCAMO'
import json
import sys

with open(sys.argv[1], encoding="utf-8") as f:
    print(json.load(f).get("version", ""))
PYCAMO
        )"
    fi

    if [[ "$current" != "$version" ]]
    then
        echo "Installation Camofox $version..."

        mkdir -p "$HOME/.local/share/camofox"

        npm install \
            --prefix "$HOME/.local/share/camofox" \
            "@askjo/camofox-browser@$version"
    else
        pass "Camofox $current déjà installé"
    fi

    if systemctl --user is-active \
        --quiet camofox.service 2>/dev/null
    then
        systemctl --user stop camofox.service
    fi

    if [[ -d "$extra/.camofox/profiles" ]]
    then
        echo "Restauration profils Camofox..."

        mkdir -p "$HOME/.camofox/profiles"

        rsync \
            -a \
            --delete \
            "$extra/.camofox/profiles/" \
            "$HOME/.camofox/profiles/"
    else
        warning "Aucun profil Camofox dans le backup"
    fi

    if [[ ! -f "$HOME/.config/systemd/user/camofox.service" ]]
    then
        failure "camofox.service absent. --restore-latest doit être exécuté avant."
        return 1
    fi

    systemctl --user daemon-reload
    systemctl --user enable camofox.service >/dev/null
    systemctl --user start camofox.service

    sleep 2

    if curl -fsS \
        "http://127.0.0.1:$CAMOFOX_PORT/health" \
        >/dev/null
    then
        pass "Camofox health OK"
    else
        failure "Camofox health inaccessible"
        return 1
    fi
}


ensure_docker_layer() {

    section "DOCKER"

    if ! have docker
    then
        echo "Installation Docker Ubuntu..."

        sudo apt-get update
        sudo apt-get install -y docker.io

        sudo systemctl enable --now docker.service

        sudo usermod \
            -aG docker \
            "$USER"

        warning \
            "Groupe docker ajouté. Le bootstrap utilise sudo docker pendant cette session."
    else
        pass "Docker CLI présent"
    fi

    if ! sudo systemctl is-active \
        --quiet docker.service
    then
        sudo systemctl start docker.service
    fi

    if ! docker_do info >/dev/null
    then
        failure "Docker daemon inaccessible"
        return 1
    fi

    pass "Docker daemon actif"
}


restore_openwebui_layer() {

    local extra="$HOME/.hermes/backup-extra/open-webui"
    local inspect="$extra/container-inspect.json"
    local image_inspect="$extra/image-inspect.json"
    local data="$extra/data"
    local meta
    local image_ref
    local volume_name
    local host_ip
    local host_port
    local restart_policy
    local network_mode
    local label
    local temp_container

    section "OPEN WEBUI"

    if docker_do inspect open-webui >/dev/null 2>&1
    then
        pass "Conteneur open-webui déjà présent"

        if [[ "$(
            docker_do inspect \
                -f '{{.State.Running}}' \
                open-webui
        )" != "true" ]]
        then
            docker_do start open-webui >/dev/null
        fi

        pass "Open WebUI actif"
        return 0
    fi

    if [[ ! -f "$inspect" ||
          ! -f "$image_inspect" ||
          ! -f "$data/webui.db" ]]
    then
        failure "Snapshot Open WebUI incomplet"
        return 1
    fi

    META_FILE="$(mktemp)"

    python3 \
        - "$inspect" "$image_inspect" \
        > "$META_FILE" <<'PYMETA'
import json
import shlex
import sys

with open(sys.argv[1], encoding="utf-8") as f:
    c = json.load(f)[0]

with open(sys.argv[2], encoding="utf-8") as f:
    i = json.load(f)[0]

mount = next(
    (
        m for m in c.get("Mounts", [])
        if m.get("Destination") == "/app/backend/data"
    ),
    None,
)

if not mount:
    raise SystemExit(
        "Mount /app/backend/data absent du snapshot"
    )

if mount.get("Type") != "volume":
    raise SystemExit(
        "Le bootstrap v0.6 attend un volume Docker nommé"
    )

digests = i.get("RepoDigests") or []

image_ref = (
    digests[0]
    if digests
    else c["Config"]["Image"]
)

pb = (
    c.get("HostConfig", {})
     .get("PortBindings", {})
     .get("8080/tcp", [])
)

host_ip = ""
host_port = "3000"

if pb:
    host_ip = pb[0].get("HostIp") or ""
    host_port = pb[0].get("HostPort") or "3000"

restart = (
    c.get("HostConfig", {})
     .get("RestartPolicy", {})
     .get("Name")
    or "always"
)

network = (
    c.get("HostConfig", {})
     .get("NetworkMode")
    or "bridge"
)

values = {
    "IMAGE_REF": image_ref,
    "VOLUME_NAME": mount["Name"],
    "HOST_IP": host_ip,
    "HOST_PORT": host_port,
    "RESTART_POLICY": restart,
    "NETWORK_MODE": network,
}

for key, value in values.items():
    print(f"{key}={shlex.quote(str(value))}")
PYMETA

    source "$META_FILE"
    rm -f "$META_FILE"

    echo "Image Golden : $IMAGE_REF"
    echo "Volume       : $VOLUME_NAME"
    echo "Port         : ${HOST_IP:-0.0.0.0}:$HOST_PORT"

    if [[ -n "$HOST_IP" &&
          "$HOST_IP" != "0.0.0.0" &&
          "$HOST_IP" != "::" ]]
    then
        if ! ip -o addr show |
             grep -Fq " $HOST_IP/"
        then
            failure \
                "L'adresse Golden $HOST_IP n'existe pas sur cette machine."
            return 1
        fi
    fi

    echo "Téléchargement image Open WebUI Golden..."
    docker_do pull "$IMAGE_REF"

    if docker_do volume inspect \
        "$VOLUME_NAME" >/dev/null 2>&1
    then
        label="$(
            docker_do volume inspect \
                -f '{{index .Labels "jarvis.bootstrap"}}' \
                "$VOLUME_NAME" 2>/dev/null || true
        )"

        if [[ "$label" != "1" ]]
        then
            failure \
                "Volume $VOLUME_NAME préexistant non créé par le bootstrap."
            echo "Refus de l'écraser automatiquement."
            return 1
        fi
    else
        docker_do volume create \
            --label jarvis.bootstrap=1 \
            "$VOLUME_NAME" \
            >/dev/null
    fi

    echo "Nettoyage du volume de reconstruction..."

    docker_do run \
        --rm \
        -v "$VOLUME_NAME:/app/backend/data" \
        --entrypoint /bin/sh \
        "$IMAGE_REF" \
        -c \
        'find /app/backend/data -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +' \
        >/dev/null

    temp_container="jarvis-openwebui-restore-$$"

    docker_do create \
        --name "$temp_container" \
        -v "$VOLUME_NAME:/app/backend/data" \
        "$IMAGE_REF" \
        >/dev/null

    echo "Restauration de /app/backend/data..."

    if ! docker_do cp \
        "$data/." \
        "$temp_container:/app/backend/data/"
    then
        docker_do rm -f \
            "$temp_container" >/dev/null 2>&1 || true
        failure "docker cp Open WebUI a échoué"
        return 1
    fi

    docker_do rm \
        "$temp_container" \
        >/dev/null

    RUN_ARGS=(
        -d
        --name open-webui
        --restart "$RESTART_POLICY"
        -v "$VOLUME_NAME:/app/backend/data"
    )

    if [[ "$NETWORK_MODE" != "default" &&
          -n "$NETWORK_MODE" ]]
    then
        RUN_ARGS+=(
            --network "$NETWORK_MODE"
        )
    fi

    if [[ -n "$HOST_IP" &&
          "$HOST_IP" != "0.0.0.0" ]]
    then
        RUN_ARGS+=(
            -p "$HOST_IP:$HOST_PORT:8080"
        )
    else
        RUN_ARGS+=(
            -p "$HOST_PORT:8080"
        )
    fi

    mapfile -d '' -t SAVED_ENV < <(
        python3 - "$inspect" <<'PYENV'
import json
import sys

with open(sys.argv[1], encoding="utf-8") as f:
    c = json.load(f)[0]

for value in c.get("Config", {}).get("Env", []):
    sys.stdout.buffer.write(
        value.encode() + b"\0"
    )
PYENV
    )

    for item in "${SAVED_ENV[@]}"
    do
        RUN_ARGS+=(
            -e "$item"
        )
    done

    mapfile -d '' -t EXTRA_HOSTS < <(
        python3 - "$inspect" <<'PYHOST'
import json
import sys

with open(sys.argv[1], encoding="utf-8") as f:
    c = json.load(f)[0]

for value in (
    c.get("HostConfig", {}).get("ExtraHosts")
    or []
):
    sys.stdout.buffer.write(
        value.encode() + b"\0"
    )
PYHOST
    )

    for item in "${EXTRA_HOSTS[@]}"
    do
        RUN_ARGS+=(
            --add-host "$item"
        )
    done

    docker_do run \
        "${RUN_ARGS[@]}" \
        "$IMAGE_REF" \
        >/dev/null

    sleep 5

    if [[ "$(
        docker_do inspect \
            -f '{{.State.Running}}' \
            open-webui
    )" != "true" ]]
    then
        failure "Open WebUI ne démarre pas"
        return 1
    fi

    pass "Open WebUI restauré et actif"
}


apply_final() {

    section "APPLY FINAL"

    if [[ "$(id -u)" -eq 0 ]]
    then
        failure "Ne pas lancer le bootstrap en root."
        return 1
    fi

    if [[ ! -d "$HOME/.hermes/backup-extra" ]]
    then
        failure \
            "~/.hermes/backup-extra absent. Exécuter --restore-latest avant."
        return 1
    fi

    echo "Validation sudo..."
    sudo -v

    restore_voice_layer ||
        return 1

    restore_camofox_layer ||
        return 1

    ensure_docker_layer ||
        return 1

    restore_openwebui_layer ||
        return 1

    # ----------------------------------------------------------
    # SYSTEMD
    # ----------------------------------------------------------

    section "SYSTEMD"

    systemctl --user daemon-reload

    for TIMER in \
        jarvis-watchdog.timer \
        jarvis-backup.timer
    do
        if systemctl --user cat \
            "$TIMER" >/dev/null 2>&1
        then
            systemctl --user enable \
                "$TIMER" >/dev/null

            systemctl --user start \
                "$TIMER"

            pass "$TIMER activé"
        else
            warning "$TIMER absent"
        fi
    done

    # ----------------------------------------------------------
    # HERMES
    # ----------------------------------------------------------

    section "HERMES"

    if [[ -f "$HOME/.config/systemd/user/hermes-gateway.service" ]]
    then
        systemctl --user enable \
            hermes-gateway.service >/dev/null

        systemctl --user restart \
            hermes-gateway.service

        sleep 3

        if systemctl --user is-active \
            --quiet hermes-gateway.service
        then
            pass "Hermes gateway actif"
        else
            failure "Hermes gateway inactif"
            return 1
        fi
    else
        failure "hermes-gateway.service absent"
        return 1
    fi

    # ----------------------------------------------------------
    # VALIDATION FINALE
    # ----------------------------------------------------------

    section "VALIDATION FINALE"

    if curl -fsS \
        "http://127.0.0.1:$CAMOFOX_PORT/health" \
        >/dev/null
    then
        pass "Camofox"
    else
        failure "Camofox"
        return 1
    fi

    if docker_do inspect \
        open-webui >/dev/null 2>&1 &&
       [[ "$(
           docker_do inspect \
               -f '{{.State.Running}}' \
               open-webui
       )" == "true" ]]
    then
        pass "Open WebUI"
    else
        failure "Open WebUI"
        return 1
    fi

    if have hermes
    then
        hermes config check || {
            failure "Hermes config check"
            return 1
        }

        hermes hooks doctor || {
            failure "Hermes hooks doctor"
            return 1
        }

        pass "Hermes config + hooks"
    fi

    if [[ -x "$HOME/.local/bin/jarvis-health" ]]
    then
        echo
        echo "Exécution jarvis-health..."
        "$HOME/.local/bin/jarvis-health"
    fi

    echo
    echo "=================================================="
    echo "  JARVIS RECONSTRUCTION TERMINEE"
    echo "=================================================="
    echo
    echo "Chaîne Golden Build :"
    echo "  Ubuntu"
    echo "  Ollama"
    echo "  Qwen3.6"
    echo "  Hermes"
    echo "  Whisper"
    echo "  NeuTTS"
    echo "  Camofox"
    echo "  Open WebUI"
    echo "  Home Assistant / UniFi config"
    echo "  Telegram"
    echo "  watchdog"
    echo "  backup local + NFS"
    echo
}


ensure_nas_group() {

    section "GROUPE NAS"

    EXPECTED_GID="$NFS_GID"

    if getent group "$NFS_GROUP" >/dev/null
    then
        CURRENT_GID="$(
            getent group "$NFS_GROUP" |
            cut -d: -f3
        )"

        if [[ "$CURRENT_GID" != "$EXPECTED_GID" ]]
        then
            failure \
                "$NFS_GROUP existe avec GID $CURRENT_GID, attendu $EXPECTED_GID"
            return 1
        fi

        pass "Groupe $NFS_GROUP GID $EXPECTED_GID présent"

    else
        echo "Création groupe $NFS_GROUP GID $EXPECTED_GID..."

        sudo groupadd \
            -g "$EXPECTED_GID" \
            "$NFS_GROUP"
    fi

    if id -nG "$USER" |
       tr ' ' '\n' |
       grep -qx "$NFS_GROUP"
    then
        pass "$USER appartient à $NFS_GROUP"
    else
        echo "Ajout de $USER au groupe $NFS_GROUP..."

        sudo usermod \
            -aG "$NFS_GROUP" \
            "$USER"

        pass "$USER ajouté à $NFS_GROUP"
    fi
}


ensure_nfs_mount() {

    section "NFS GOLDEN BUILD"

    if [[ -z "$NFS_SOURCE" || "$NFS_SOURCE" == *"METTRE_VOTRE_"* ]]; then
        failure "JARVIS_NFS_SOURCE non configuré dans $CONFIG_FILE"
        return 1
    fi

    if [[ -z "$NFS_MOUNT" || "$NFS_MOUNT" == *"METTRE_VOTRE_"* ]]; then
        failure "JARVIS_NFS_MOUNT non configuré dans $CONFIG_FILE"
        return 1
    fi

    if ! command -v mount.nfs >/dev/null 2>&1
    then
        failure "nfs-common absent"
        return 1
    fi

    sudo install \
        -d \
        -m 755 \
        "$NFS_MOUNT"

    if mountpoint -q "$NFS_MOUNT"
    then
        if ls -la "$NFS_MOUNT" >/dev/null 2>&1
        then
            pass "NFS déjà monté et lisible"
            return 0
        fi
    fi

    FSTAB_LINE="$NFS_SOURCE $NFS_MOUNT nfs defaults,_netdev,nofail,x-systemd.automount 0 0"

    if ! grep -Fqx \
        "$FSTAB_LINE" \
        /etc/fstab
    then
        FSTAB_BACKUP="/etc/fstab.jarvis-bootstrap.$(date '+%Y%m%d-%H%M%S')"

        echo "Sauvegarde de /etc/fstab :"
        echo "  $FSTAB_BACKUP"

        sudo cp -a \
            /etc/fstab \
            "$FSTAB_BACKUP"

        printf '%s\n' "$FSTAB_LINE" |
            sudo tee -a /etc/fstab \
            >/dev/null
    else
        pass "Entrée NFS déjà présente dans /etc/fstab"
    fi

    sudo systemctl daemon-reload

    sudo mount "$NFS_MOUNT" \
        >/dev/null 2>&1 || true

    if ! ls -la "$NFS_MOUNT" >/dev/null 2>&1
    then
        failure "NFS inaccessible après montage"
        return 1
    fi

    pass "NFS Golden Build accessible"
}


prepare_golden_manifest() {

    section "MANIFEST GOLDEN"

    ARCHIVE="$(
        find_latest_backup || true
    )"

    if [[ -z "$ARCHIVE" ]]
    then
        failure "Aucun backup disponible sur le NAS"
        return 1
    fi

    echo "Backup source :"
    echo "  $ARCHIVE"
    echo

    verify_backup_archive "$ARCHIVE" ||
        return 1

    MEMBER="$(
        tar -tzf "$ARCHIVE" |
        grep -E '(^|/)\.hermes/jarvis-golden-manifest\.env$' |
        head -1 || true
    )"

    if [[ -z "$MEMBER" ]]
    then
        failure "Manifest Golden absent du backup"
        return 1
    fi

    mkdir -p "$HOME/.hermes"

    TMP="$(
        mktemp \
            "$HOME/.hermes/.golden-manifest.XXXXXX"
    )"

    if ! tar -xOf \
        "$ARCHIVE" \
        "$MEMBER" \
        > "$TMP"
    then
        rm -f "$TMP"
        failure "Extraction du manifest impossible"
        return 1
    fi

    if ! grep -q '^OLLAMA_MODEL=' "$TMP"
    then
        rm -f "$TMP"
        failure "Manifest Golden invalide"
        return 1
    fi

    chmod 600 "$TMP"

    mv \
        "$TMP" \
        "$HOME/.hermes/jarvis-golden-manifest.env"

    pass "Manifest Golden restauré avant installation"

    echo
    grep -E \
        '^(OLLAMA_VERSION_RAW|OLLAMA_MODEL|OLLAMA_MODEL_ID|HERMES_VERSION_RAW|HERMES_GIT_COMMIT)=' \
        "$HOME/.hermes/jarvis-golden-manifest.env" \
        || true
}


prepare_rebuild_source() {

    section "PREPARATION SOURCE REBUILD"

    echo "Installation du minimum nécessaire au NAS..."

    sudo apt-get update

    sudo apt-get install -y \
        ca-certificates \
        nfs-common \
        curl

    ensure_nas_group ||
        return 1

    ensure_nfs_mount ||
        return 1

    prepare_golden_manifest ||
        return 1

    sudo loginctl \
        enable-linger \
        "$USER"

    pass "Linger systemd utilisateur activé"
}


simulate_all() {

    section "SIMULATION COMPLETE"

    echo "Aucune modification ne sera effectuée."
    echo

    RESULT=0

    echo
    echo "========== CHECK =========="
    check || RESULT=1

    echo
    echo "========== BASE =========="
    simulate_base || RESULT=1

    echo
    echo "========== IA =========="
    simulate_ai || RESULT=1

    echo
    echo "========== RESTORE =========="
    simulate_restore || RESULT=1

    echo
    echo "========== FINAL =========="
    simulate_final || RESULT=1

    echo
    echo "=============================================="

    if [[ "$RESULT" -eq 0 ]]
    then
        echo "SIMULATION COMPLETE : OK"
    else
        echo "SIMULATION COMPLETE : avertissements détectés"
    fi

    echo "=============================================="

    return "$RESULT"
}


publish_seed() {

    section "PUBLICATION SEED"

    sudo -v

    ensure_nas_group ||
        return 1

    ensure_nfs_mount ||
        return 1

    echo "Actualisation du manifest Golden..."

    freeze

    SRC="$HOME/.local/bin/jarvis-bootstrap"
    DST="$NFS_MOUNT/jarvis-bootstrap.seed"

    if [[ ! -x "$SRC" ]]
    then
        failure "$SRC absent"
        return 1
    fi

    TMPDIR="$(
        mktemp -d \
            "$HOME/.cache/jarvis-seed.XXXXXX"
    )"

    cp \
        "$SRC" \
        "$TMPDIR/jarvis-bootstrap.seed"

    chmod 700 \
        "$TMPDIR/jarvis-bootstrap.seed"

    (
        cd "$TMPDIR"

        sha256sum \
            jarvis-bootstrap.seed \
            > jarvis-bootstrap.seed.sha256
    )

    cp \
        "$HOME/.hermes/jarvis-golden-manifest.env" \
        "$TMPDIR/jarvis-golden-manifest.env"

    chmod 600 \
        "$TMPDIR/jarvis-golden-manifest.env"

    cat > "$TMPDIR/README-REBUILD.txt" <<EOF
JARVIS GOLDEN BUILD - SEED

NFS source:
$NFS_SOURCE

NFS mount:
$NFS_MOUNT

Après installation Ubuntu :

1. Installer NFS :
   sudo apt update
   sudo apt install -y nfs-common

2. Monter le NAS :
   sudo mkdir -p "$NFS_MOUNT"
   sudo mount -t nfs "$NFS_SOURCE" "$NFS_MOUNT"

3. Installer le seed :
   mkdir -p ~/.local/bin
   cp "$NFS_MOUNT/jarvis-bootstrap.seed" ~/.local/bin/jarvis-bootstrap
   chmod 700 ~/.local/bin/jarvis-bootstrap

4. Vérifier :
   sha256sum ~/.local/bin/jarvis-bootstrap

5. Reconstruire :
   jarvis-bootstrap --rebuild-all
EOF

    chmod 600 \
        "$TMPDIR/README-REBUILD.txt"

    echo "Copie du seed vers le NAS..."

    for FILE in \
        jarvis-bootstrap.seed \
        jarvis-bootstrap.seed.sha256 \
        jarvis-golden-manifest.env \
        README-REBUILD.txt
    do
        MODE=600

        [[ "$FILE" == "jarvis-bootstrap.seed" ]] &&
            MODE=700

        CMD="install -m $MODE '$TMPDIR/$FILE' '$NFS_MOUNT/$FILE'"

        sg "$NFS_GROUP" \
            -c "$CMD"
    done

    rm -rf "$TMPDIR"

    echo
    echo "Vérification NAS :"

    ls -lh \
        "$NFS_MOUNT/jarvis-bootstrap.seed" \
        "$NFS_MOUNT/jarvis-bootstrap.seed.sha256" \
        "$NFS_MOUNT/jarvis-golden-manifest.env" \
        "$NFS_MOUNT/README-REBUILD.txt"

    pass "Seed Golden Build publié"
}


rebuild_all() {

    section "JARVIS REBUILD ALL"

    if [[ "$(id -u)" -eq 0 ]]
    then
        failure "Ne pas exécuter en root."
        return 1
    fi

    # Protection contre une exécution accidentelle
    # sur le Jarvis opérationnel.
    if [[ -f "$HOME/.hermes/config.yaml" ]] &&
       have docker &&
       docker inspect open-webui \
           >/dev/null 2>&1
    then
        failure \
            "Cette machine ressemble déjà à un Jarvis opérationnel."

        echo
        echo "Par sécurité --rebuild-all refuse de continuer."
        echo "Utiliser --simulate-all sur la machine actuelle."

        return 2
    fi

    echo
    echo "Cette commande est destinée à une Ubuntu neuve."
    echo
    echo "Elle va reconstruire :"
    echo "  - NFS Golden Build"
    echo "  - Ollama"
    echo "  - Qwen"
    echo "  - Hermes"
    echo "  - configuration/secrets"
    echo "  - voix"
    echo "  - Camofox"
    echo "  - Open WebUI"
    echo "  - timers/watchdog/backup"
    echo

    read -r -p \
        "Tape REBUILD-JARVIS pour continuer : " \
        CONFIRM

    if [[ "$CONFIRM" != "REBUILD-JARVIS" ]]
    then
        echo "Reconstruction annulée."
        return 0
    fi

    LOG_DIR="$HOME/jarvis-rebuild-logs"

    mkdir -p "$LOG_DIR"
    chmod 700 "$LOG_DIR"

    LOG="$LOG_DIR/rebuild-$(date '+%Y%m%d-%H%M%S').log"

    touch "$LOG"
    chmod 600 "$LOG"

    exec > >(
        tee -a "$LOG"
    ) 2>&1

    echo
    echo "=============================================="
    echo " JARVIS GOLDEN BUILD v1.0"
    echo "=============================================="
    echo "Date     : $(date --iso-8601=seconds)"
    echo "Host     : $(hostname)"
    echo "Kernel   : $(uname -r)"
    echo "Log      : $LOG"
    echo

    echo "[1/6] Préparation NAS + manifest..."

    sudo -v

    prepare_rebuild_source ||
        return 1

    echo
    echo "[2/6] Base Ubuntu + Ollama..."

    apply_base ||
        return 1

    echo
    echo "[3/6] Qwen + Hermes..."

    apply_ai ||
        return 1

    echo
    echo "[4/6] Restore Golden..."

    restore_latest ||
        return 1

    echo
    echo "[5/6] Couche finale..."

    apply_final ||
        return 1

    echo
    echo "[6/6] Validation..."

    if [[ -x "$HOME/.local/bin/jarvis-health" ]]
    then
        "$HOME/.local/bin/jarvis-health" ||
            return 1
    else
        warning "jarvis-health absent"
    fi

    echo
    echo "=============================================="
    echo " JARVIS REBUILD : SUCCES"
    echo "=============================================="
    echo
    echo "Journal complet :"
    echo "  $LOG"
}


ollama_models_root() {

    local root=""

    root="$(
        systemctl show \
            ollama.service \
            -p Environment \
            --value 2>/dev/null |
        grep -oE 'OLLAMA_MODELS=[^ "]*' |
        tail -1 |
        cut -d= -f2- || true
    )"

    if [[ -z "$root" ]]
    then
        root="/usr/share/ollama/.ollama/models"
    fi

    printf '%s\n' "$root"
}


model_seed_name() {

    printf '%s\n' "$1" |
        sed 's#[/:]#_#g'
}


find_model_manifest() {

    local model="$1"
    local root="$2"
    local name
    local tag

    if [[ "$model" == *:* ]]
    then
        name="${model%%:*}"
        tag="${model#*:}"
    else
        name="$model"
        tag="latest"
    fi

    find "$root/manifests" \
        -type f \
        -path "*/$name/$tag" \
        -print \
        -quit 2>/dev/null
}


model_seed_dir() {

    local model="$1"
    local seed_name

    seed_name="$(
        model_seed_name "$model"
    )"

    printf '%s/ollama-seed/%s\n' \
        "$NFS_MOUNT" \
        "$seed_name"
}


check_model_seed() {

    section "CHECK MODEL SEED"

    local model
    local seed

    model="$(
        golden_get OLLAMA_MODEL
    )"

    [[ -n "$model" ]] ||
        model="$MODEL"

    seed="$(
        model_seed_dir "$model"
    )"

    echo "Modèle : $model"
    echo "Seed   : $seed"
    echo

    if [[ ! -d "$seed" ]]
    then
        failure "Seed modèle absent"
        return 1
    fi

    if [[ ! -f "$seed/.complete" ]]
    then
        failure \
            "Seed présent mais incomplet (.complete absent)"
        return 1
    fi

    if [[ ! -f "$seed/SHA256SUMS" ]]
    then
        failure "SHA256SUMS absent"
        return 1
    fi

    echo "Vérification SHA256..."

    if (
        cd "$seed"
        sha256sum -c SHA256SUMS
    )
    then
        pass "Seed modèle intègre"
    else
        failure "Seed modèle corrompu"
        return 1
    fi

    echo
    du -sh "$seed"
}


publish_model_seed() {

    section "PUBLISH MODEL SEED"

    sudo -v

    ensure_nfs_mount ||
        return 1

    local model
    local root
    local manifest
    local manifest_rel
    local seed
    local list
    local checksums
    local metadata
    local current_id

    model="$(
        golden_get OLLAMA_MODEL
    )"

    [[ -n "$model" ]] ||
        model="$MODEL"

    root="$(
        ollama_models_root
    )"

    echo "Modèle      : $model"
    echo "Ollama root : $root"

    if [[ ! -d "$root" ]]
    then
        failure "Stockage Ollama absent : $root"
        return 1
    fi

    manifest="$(
        find_model_manifest \
            "$model" \
            "$root"
    )"

    if [[ -z "$manifest" ]]
    then
        failure "Manifest Ollama de $model introuvable"
        return 1
    fi

    manifest_rel="${manifest#"$root"/}"

    pass "Manifest trouvé : $manifest_rel"

    TMP="$(
        mktemp -d \
            "$HOME/.cache/jarvis-model-seed.XXXXXX"
    )"

    list="$TMP/files.txt"
    checksums="$TMP/SHA256SUMS"
    metadata="$TMP/model.env"

    printf '%s\n' \
        "$manifest_rel" \
        > "$list"

    python3 \
        - "$manifest" \
        "$list" <<'PYMODEL'
import json
import sys

manifest, output = sys.argv[1:3]

with open(manifest, encoding="utf-8") as f:
    data = json.load(f)

digests = []

config = data.get("config") or {}

if config.get("digest"):
    digests.append(config["digest"])

for layer in data.get("layers") or []:
    digest = layer.get("digest")
    if digest:
        digests.append(digest)

seen = set()

with open(output, "a", encoding="utf-8") as out:
    for digest in digests:
        if digest in seen:
            continue

        seen.add(digest)

        if not digest.startswith("sha256:"):
            raise SystemExit(
                f"Digest inattendu : {digest}"
            )

        out.write(
            "blobs/" +
            digest.replace(":", "-", 1) +
            "\n"
        )
PYMODEL

    echo
    echo "Fichiers nécessaires :"

    while IFS= read -r FILE
    do
        if [[ ! -f "$root/$FILE" ]]
        then
            rm -rf "$TMP"
            failure "Blob manquant : $FILE"
            return 1
        fi

        ls -lh "$root/$FILE"
    done < "$list"

    echo
    echo "Calcul des SHA256 source..."

    (
        cd "$root"

        while IFS= read -r FILE
        do
            sha256sum "$FILE"
        done < "$list"
    ) > "$checksums"

    current_id="$(
        ollama list 2>/dev/null |
        awk -v m="$model" '
            NR > 1 && $1 == m {
                print $2
                exit
            }
        '
    )"

    {
        printf 'MODEL=%q\n' "$model"
        printf 'MODEL_ID=%q\n' "$current_id"
        printf 'MANIFEST_REL=%q\n' "$manifest_rel"
        printf 'OLLAMA_MODELS_ROOT=%q\n' "$root"
        printf 'CREATED_AT=%q\n' \
            "$(date --iso-8601=seconds)"
    } > "$metadata"

    seed="$(
        model_seed_dir "$model"
    )"

    echo
    echo "Destination :"
    echo "  $seed"
    echo

    echo "Espace disponible NAS :"
    df -h "$NFS_MOUNT"

    CMD="mkdir -p '$seed' && rm -f '$seed/.complete'"

    sg "$NFS_GROUP" \
        -c "$CMD"

    printf -v CMD \
        'rsync -ah --relative --partial --append-verify --info=progress2 --files-from=%q %q/ %q/' \
        "$list" \
        "$root" \
        "$seed"

    echo
    echo "Copie des blobs vers le NAS..."

    sg "$NFS_GROUP" \
        -c "$CMD"

    printf -v CMD \
        'install -m 644 %q %q' \
        "$checksums" \
        "$seed/SHA256SUMS"

    sg "$NFS_GROUP" \
        -c "$CMD"

    printf -v CMD \
        'install -m 644 %q %q' \
        "$metadata" \
        "$seed/model.env"

    sg "$NFS_GROUP" \
        -c "$CMD"

    sg "$NFS_GROUP" -c \
        "find '$seed' -type d -exec chmod 755 {} + && find '$seed' -type f -exec chmod 644 {} +"

    echo
    echo "Vérification complète du NAS..."

    if (
        cd "$seed"
        sha256sum -c SHA256SUMS
    )
    then
        pass "Copie NAS vérifiée"
    else
        rm -rf "$TMP"
        failure "Checksum NAS incorrect"
        return 1
    fi

    sg "$NFS_GROUP" -c \
        "touch '$seed/.complete' && chmod 644 '$seed/.complete'"

    rm -rf "$TMP"

    echo
    du -sh "$seed"

    pass "Seed hors ligne $model publié"
}


restore_model_seed() {

    local model="$1"
    local seed
    local root

    seed="$(
        model_seed_dir "$model"
    )"

    if [[ ! -d "$seed" ]]
    then
        return 2
    fi

    if [[ ! -f "$seed/.complete" ]]
    then
        failure \
            "Seed $model présent mais incomplet"
        return 1
    fi

    if [[ ! -f "$seed/SHA256SUMS" ]]
    then
        failure "SHA256SUMS du seed absent"
        return 1
    fi

    echo
    echo "Seed hors ligne trouvé."
    echo "Validation SHA256..."

    if ! (
        cd "$seed"
        sha256sum -c SHA256SUMS
    )
    then
        failure \
            "Seed hors ligne corrompu. Abandon."
        return 1
    fi

    root="$(
        ollama_models_root
    )"

    echo
    echo "Restauration vers :"
    echo "  $root"

    sudo systemctl stop \
        ollama.service

    sudo install \
        -d \
        -o ollama \
        -g ollama \
        -m 755 \
        "$root/blobs" \
        "$root/manifests"

    if ! sudo rsync \
        -a \
        "$seed/blobs/" \
        "$root/blobs/"
    then
        sudo systemctl start \
            ollama.service

        failure "Restauration blobs impossible"
        return 1
    fi

    if ! sudo rsync \
        -a \
        "$seed/manifests/" \
        "$root/manifests/"
    then
        sudo systemctl start \
            ollama.service

        failure "Restauration manifests impossible"
        return 1
    fi

    sudo chown -R \
        ollama:ollama \
        "$root/blobs" \
        "$root/manifests"

    sudo systemctl start \
        ollama.service

    API_OK=0

    for _ in {1..30}
    do
        if curl -fsS \
            http://127.0.0.1:11434/api/tags \
            >/dev/null 2>&1
        then
            API_OK=1
            break
        fi

        sleep 1
    done

    if [[ "$API_OK" -ne 1 ]]
    then
        failure \
            "Ollama inaccessible après restauration"
        return 1
    fi

    CURRENT_MODEL_ID="$(
        ollama list 2>/dev/null |
        awk -v m="$model" '
            NR > 1 && $1 == m {
                print $2
                exit
            }
        '
    )"

    if [[ -z "$CURRENT_MODEL_ID" ]]
    then
        failure \
            "$model non reconnu après restauration"
        return 1
    fi

    pass \
        "$model restauré hors ligne ($CURRENT_MODEL_ID)"

    return 0
}

case "$MODE" in

    --check)
        check
        ;;

    --plan)
        plan
        ;;

    --freeze)
        freeze
        ;;

    --simulate-base)
        simulate_base
        ;;

    --apply-base)
        apply_base
        ;;

    --simulate-ai)
        simulate_ai
        ;;

    --apply-ai)
        apply_ai
        ;;

    --simulate-restore)
        simulate_restore
        ;;

    --restore-latest)
        restore_latest
        ;;

    --simulate-final)
        simulate_final
        ;;

    --apply-final)
        apply_final
        ;;

    --simulate-all)
        simulate_all
        ;;

    --publish-seed)
        publish_seed
        ;;

    --rebuild-all)
        rebuild_all
        ;;

    --publish-model-seed)
        publish_model_seed
        ;;

    --check-model-seed)
        check_model_seed
        ;;

    --help|-h)
        echo "jarvis-bootstrap $VERSION"
        echo
        echo "  --check          audit lecture seule"
        echo "  --plan           plan de reconstruction"
        echo "  --freeze         fige les versions du Golden Build"
        echo "  --simulate-base  simule l'installation de base"
        echo "  --apply-base     prérequis Ubuntu + Ollama"
        echo "  --simulate-ai    simule Qwen + Hermes"
        echo "  --apply-ai       installe/valide Qwen + Hermes"
        echo "  --simulate-restore  vérifie le dernier backup NFS"
        echo "  --restore-latest    restaure le dernier backup NFS"
        echo "  --simulate-final    audit final lecture seule"
        echo "  --apply-final       restaure et valide la couche finale"
        echo "  --simulate-all      audit complet lecture seule"
        echo "  --publish-seed      publie le bootstrap de secours sur le NAS"
        echo "  --rebuild-all       reconstruction complète Ubuntu vierge"
        echo "  --publish-model-seed  sauvegarde Qwen hors ligne sur le NAS"
        echo "  --check-model-seed    vérifie le seed Qwen du NAS"
        ;;

    *)
        echo "Option inconnue : $MODE"
        exit 2
        ;;
esac
