Aller au contenu

Le script de diagnostic Mac, ligne par ligne.

Le bilan Mac est un seul fichier texte. Le voici en entier, tel qu'il s'exécute sur votre ordinateur. Les commentaires expliquent chaque partie.

Il lit

Système, protections, démarrage, extensions, applications, disque, sauvegarde, batterie.

Il n'ouvre jamais

Vos documents, mails, photos, historique de navigation ou mots de passe.

Il ne touche à rien

Aucune installation, modification ou suppression. Aucune autorisation demandée à macOS.

#!/bin/bash
# =============================================================================
# verifmonordi : bilan de santé d'un Mac, en LECTURE SEULE.
#
# Ce que fait ce script :
#   1. Il lit l'état du Mac (système, protections, démarrage automatique,
#      extensions, applications, disque, sauvegarde, performances).
#   2. Il retire du rapport ce qui vous identifie (nom, nom de l'ordinateur,
#      numéro de série, adresses matérielles).
#   3. Il vous montre ce qu'il a trouvé et vous demande l'accord.
#   4. Il chiffre le rapport : seul verifmonordi peut l'ouvrir.
#
# Ce qu'il ne fait jamais :
#   - installer, modifier ou supprimer quoi que ce soit sur votre Mac ;
#   - ouvrir vos documents, mails, photos, historique ou mots de passe ;
#   - demander une autorisation système (aucune trace dans Réglages Système).
#
# Usage :
#   bash diagnostic.sh                  # rapport chiffré dans votre dossier personnel
#   bash diagnostic.sh --dossiers-perso # mesure aussi Bureau, Documents, Téléchargements
#                                       # (macOS vous demandera l'accès : il restera accordé)
#   bash diagnostic.sh --admin          # quelques vérifications de plus (mot de passe admin)
#   bash diagnostic.sh -o <dossier>     # dossier où écrire le fichier chiffré
#
# Compatible bash 3.2 (celui de macOS). N'utilise que des outils fournis avec
# macOS : pas de Python, pas de Homebrew, rien à installer.
# =============================================================================

VERSION="0.1.0-test"
KEY_ID="test-2026-11"

umask 077
export LC_ALL=C   # sorties de commandes stables, quelle que soit la langue du Mac

# Clé publique de verifmonordi. Le rapport est chiffré avec elle ;
# seule la clé privée correspondante, chez verifmonordi, peut le déchiffrer.
PUBLIC_KEY='-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwZlVsSq3vfNeugR1OydE
5Xa55pvZjgMAA2MF+acbpuGrRv9ioDJCwUtx4R8E6l9KGmlRner7waIHf9nZdThd
LhnyqeZ1Y3/4I35XS0FtLTXQrxtkK/YVvExDLAIyU5iLDLfanhtB7A+QW8g0SJ9T
uHHMTUxROM77uwQ9MR3jD97E9T9K2GjN6b3X2mhLo5lMlCSJfMU8VO/iC1wCkcm0
M6PBcOu1XU6X3fAxKK2h3HT6EP9nvyRpy8e363ll+eBVGYO1HshTdTUF55wYthKC
WVoBYWVNDhK8cZ/djLzS/CBI4YZvoNRBgcyZ0HQfLM0iWmaFMb6C4mIu7JE+JzcA
D0NXsyUIef43nTaBu2qoY2x9ncq2z44VYtdKmbAHbgoJy8kD7far9U0mPI3JNLOo
4NROKytg59/mTaNQqs+zzNonGcWWfNtmQavSY6Aw3LZ+uXEXkTeohbdpYNbhgRzs
Rs8B1ax/TZ3F48FesiNveIL0HmYw+UpYuE0yYdYVTBFqUCErr1Rwp/H9CW5ijDWN
aBguzdstyZZXYEUa7NYt8EKU+vP10P5kK0KgXGZtMip4XB/K7Kw4TBGGmR9tKVM2
+vUtYELGhtwW0t1k4OGkKN3TMv9Bo0YSFJ4OHHQKpJqd4GwvxOHTGZ5Lcq78aG16
ogle4oshkWRF+ASH+RnMGvUCAwEAAQ==
-----END PUBLIC KEY-----'

OPENSSL=/usr/bin/openssl   # LibreSSL fourni avec macOS (pas celui de Homebrew)

# --- Options -----------------------------------------------------------------
ADMIN=0
PERSO=0
OUTDIR="$HOME"
while [ $# -gt 0 ]; do
  case "$1" in
    --admin) ADMIN=1 ;;
    --dossiers-perso) PERSO=1 ;;
    -o) shift; OUTDIR="${1:-$HOME}" ;;
    -h|--help) sed -n '2,30p' "$0"; exit 0 ;;
    *) echo "Option inconnue : $1 (voir --help)"; exit 2 ;;
  esac
  shift
done

# --- Espace de travail temporaire, effacé quoi qu'il arrive -------------------
WORK=$(mktemp -d "${TMPDIR:-/tmp}/vmo.XXXXXX") || { echo "Impossible de créer un dossier temporaire."; exit 1; }
cleanup() { rm -rf "$WORK"; }
on_interrupt() { echo; echo "Interrompu : rien n'a été envoyé ni conservé."; exit 130; }
trap cleanup EXIT
trap on_interrupt INT TERM
RAW="$WORK/rapport.txt"
CLEAN="$WORK/rapport-anonyme.txt"

# Dossiers que macOS protège : les lire déclencherait une demande d'autorisation
# qui resterait accordée. Sans --dossiers-perso, on ne les ouvre pas.
PROTECTED="Desktop Documents Downloads Pictures Movies Music"

# --- Petits outils -----------------------------------------------------------
say()     { echo "$@" >&3; }
section() { say "  • $1"; printf '\n\n==================== %s ====================\n' "$1"; }
sub()     { printf '\n--- %s ---\n' "$1"; }

# Lance une commande avec un délai maximal (macOS n'a pas `timeout`).
# Le processus est arrêté proprement par perl, sans message parasite.
limit() {
  local s=$1; shift
  perl -e 'my $t = shift; my $pid = fork();
           if (!$pid) { exec @ARGV or exit 127 }
           local $SIG{ALRM} = sub { kill 9, $pid; print "(délai dépassé)\n"; exit 124 };
           alarm $t; waitpid($pid, 0); exit($? >> 8);' "$s" "$@"
}

is_protected() {
  local name; name=$(basename "$1")
  for p in $PROTECTED; do [ "$name" = "$p" ] && return 0; done
  return 1
}

# Signature d'un exécutable ou d'une application.
sig() {
  local p="$1" info auth team
  [ -e "$p" ] || { echo "INTROUVABLE"; return; }
  info=$(codesign -dv --verbose=2 "$p" 2>&1)
  if echo "$info" | grep -q "not signed"; then echo "NON SIGNÉ"; return; fi
  auth=$(echo "$info" | grep -m1 '^Authority=' | cut -d= -f2-)
  team=$(echo "$info" | grep -m1 '^TeamIdentifier=' | cut -d= -f2-)
  [ -z "$auth" ] && auth="signature ad hoc (éditeur non identifié)"
  echo "$auth | team=$team"
}

# Programme lancé par un fichier launchd.
plist_prog() {
  local f="$1" p
  p=$(plutil -extract Program raw -o - "$f" 2>/dev/null)
  [ -z "$p" ] && p=$(plutil -extract ProgramArguments.0 raw -o - "$f" 2>/dev/null)
  [ -z "$p" ] && p=$(plutil -extract BundleProgram raw -o - "$f" 2>/dev/null)
  echo "$p"
}

# Nom lisible d'une extension Chrome/Edge/Brave.
chrome_ext_name() {
  local m="$1/manifest.json" n key loc k v
  n=$(plutil -extract name raw -o - "$m" 2>/dev/null)
  case "$n" in
    __MSG_*__)
      key=${n#__MSG_}; key=${key%__}
      for loc in fr en en_US; do
        for k in "$key" "$(echo "$key" | tr 'A-Z' 'a-z')"; do
          v=$(plutil -extract "$k.message" raw -o - "$1/_locales/$loc/messages.json" 2>/dev/null)
          [ -n "$v" ] && { echo "$v"; return; }
        done
      done ;;
  esac
  echo "${n:-?}"
}

# =============================================================================
# Collecte
# =============================================================================
collect() {
  echo "VERIFMONORDI : BILAN MAC (lecture seule)"
  echo "Version script : $VERSION"
  echo "Date           : $(date '+%Y-%m-%d %H:%M')"
  echo "Mode admin     : $([ $ADMIN = 1 ] && echo oui || echo non)"
  echo "Dossiers perso : $([ $PERSO = 1 ] && echo lus || echo 'non lus (protégés par macOS)')"

  # ---------------------------------------------------------------------------
  section "1. SYSTÈME"
  sw_vers
  sub "Matériel"
  system_profiler SPHardwareDataType 2>/dev/null | grep -v -i -E 'serial|uuid|provisioning|^$'
  sub "Allumé depuis"
  uptime
  sub "Mises à jour disponibles (Apple)"
  limit 90 softwareupdate -l 2>&1

  # ---------------------------------------------------------------------------
  section "2. PROTECTIONS"
  sub "SIP (protection de l'intégrité du système)"; csrutil status
  sub "Gatekeeper"; spctl --status
  sub "FileVault (chiffrement du disque)"; fdesetup status
  sub "Pare-feu"; /usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate
  sub "XProtect (antivirus intégré)"
  if command -v xprotect >/dev/null 2>&1; then limit 20 xprotect version; fi
  plutil -extract CFBundleShortVersionString raw -o - \
    /Library/Apple/System/Library/CoreServices/XProtect.bundle/Contents/Info.plist 2>/dev/null \
    | sed 's/^/version bundle : /'

  # ---------------------------------------------------------------------------
  section "3. DÉMARRAGE AUTOMATIQUE"
  local d f prog found
  for d in "$HOME/Library/LaunchAgents" /Library/LaunchAgents /Library/LaunchDaemons; do
    sub "$d"
    found=0
    for f in "$d"/*.plist; do
      [ -e "$f" ] || continue
      found=1
      prog=$(plist_prog "$f")
      echo "$(basename "$f")"
      echo "    programme : ${prog:-(aucun programme déclaré : fichier vide ou service à la demande)}"
      [ -n "$prog" ] && echo "    signature : $(sig "$prog")"
      echo "    modifié   : $(stat -f '%Sm' -t '%Y-%m-%d' "$f")"
    done
    [ $found = 0 ] && echo "(vide)"
  done

  sub "Outils privilégiés (/Library/PrivilegedHelperTools)"
  for f in /Library/PrivilegedHelperTools/*; do
    [ -e "$f" ] || continue
    echo "$(basename "$f")  →  $(sig "$f")"
  done

  if [ $ADMIN = 1 ]; then
    sub "Éléments de connexion et en arrière-plan (admin)"
    sudo -n sfltool dumpbtm 2>&1 \
      | grep -E '^\s*(Name|Developer Name|Team Identifier|Type|Disposition|URL|Executable Path):' \
      | sed 's/^ *//'
  else
    sub "Éléments de connexion"
    echo "(non lus sans --admin : les lire autrement laisserait une autorisation permanente)"
  fi

  # ---------------------------------------------------------------------------
  section "4. PROFILS DE CONFIGURATION / GESTION À DISTANCE"
  sub "Inscription MDM"; profiles status -type enrollment 2>&1
  sub "Profils utilisateur"; profiles list 2>&1
  if [ $ADMIN = 1 ]; then
    sub "Profils système (admin)"
    sudo -n profiles show -type configuration 2>&1 \
      | grep -E 'profileIdentifier|ProfileDisplayName|ProfileOrganization|ProfileInstallDate|attribute: name'
  fi

  # ---------------------------------------------------------------------------
  section "5. EXTENSIONS DE NAVIGATEURS"
  sub "Safari"
  pluginkit -mAv -p com.apple.Safari.web-extension 2>/dev/null | sed 's/^ *//'
  pluginkit -mAv -p com.apple.Safari.extension 2>/dev/null | sed 's/^ *//'

  local b base prof e v
  for b in "Google/Chrome" "BraveSoftware/Brave-Browser" "Microsoft Edge"; do
    base="$HOME/Library/Application Support/$b"
    [ -d "$base" ] || continue
    for prof in "$base"/*/Extensions; do
      [ -d "$prof" ] || continue
      sub "$b ($(basename "$(dirname "$prof")"))"
      for e in "$prof"/*; do
        [ -d "$e" ] || continue
        [ "$(basename "$e")" = "Temp" ] && continue
        v=$(ls -1 "$e" | tail -1)
        echo "$(chrome_ext_name "$e/$v")  [$(basename "$e")]"
      done
    done
  done
  for prof in "$HOME/Library/Application Support/Firefox/Profiles"/*/extensions; do
    [ -d "$prof" ] || continue
    sub "Firefox ($(basename "$(dirname "$prof")"))"
    ls -1 "$prof"
  done

  # ---------------------------------------------------------------------------
  section "6. RÉSEAU"
  sub "Serveurs DNS"; scutil --dns | grep 'nameserver\[' | awk '{print $3}' | sort -u
  sub "Proxy"; scutil --proxy
  sub "Fichier hosts (lignes actives)"; grep -v -E '^\s*(#|$)' /etc/hosts

  # ---------------------------------------------------------------------------
  section "7. APPLICATIONS"
  local KNOWN_BAD='MacKeeper|Advanced Mac Cleaner|Mac Auto Fixer|Mac Cleanup Pro|Smart Mac Booster|Mac Tonic|Mac Mechanic|Search Baron|SearchMine|Mac Booster|PC Keeper|Similar Photo Cleaner|Disk Cleanup Pro|Genieo|InstallMac|VSearch|Spigot|Shlayer'
  local app name ver used src s flag
  printf '%-40s %-14s %-11s %-10s %s\n' "APPLICATION" "VERSION" "DERN. USAGE" "SOURCE" "SIGNATURE"
  for app in /Applications/*.app "$HOME/Applications"/*.app; do
    [ -d "$app" ] || continue
    name=$(basename "$app" .app)
    ver=$(plutil -extract CFBundleShortVersionString raw -o - "$app/Contents/Info.plist" 2>/dev/null)
    used=$(mdls -raw -name kMDItemLastUsedDate "$app" 2>/dev/null | cut -c1-10)
    if [ "$used" = "(null)" ]; then used="jamais"
    elif ! echo "$used" | grep -q -E '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'; then used="inconnu"; fi
    if [ -e "$app/Contents/_MASReceipt" ]; then src="App Store"; else src="web"; fi
    s=$(sig "$app" | cut -d'|' -f1 | sed 's/Developer ID Application: //')
    flag=""; echo "$name" | grep -q -i -E "$KNOWN_BAD" && flag="  <<< LOGICIEL DOUTEUX CONNU"
    printf '%-40.40s %-14.14s %-11s %-10s %s%s\n' "$name" "${ver:-?}" "${used:-?}" "$src" "$s" "$flag"
  done

  sub "Dossiers dans /Library/Application Support (traces de logiciels installés)"
  ls -1 "/Library/Application Support"

  # ---------------------------------------------------------------------------
  section "8. DISQUE"
  sub "Espace"; df -h / | sed 's/^/  /'
  sub "Taille des dossiers de l'utilisateur"
  for d in "$HOME"/* "$HOME/.Trash" "$HOME/Library/Caches" "$HOME/Library/Application Support"; do
    [ -e "$d" ] || continue
    [ "$d" = "$HOME/Library" ] && continue
    if [ $PERSO = 0 ] && is_protected "$d"; then
      echo "-	$d (protégé par macOS, non lu)"
      continue
    fi
    limit 120 du -sh "$d" 2>/dev/null
  done | sort -hr

  sub "Fichiers de plus de 1 Go (taille réelle sur disque | taille apparente | nom)"
  local prune="-path $HOME/Library"
  if [ $PERSO = 0 ]; then
    for p in $PROTECTED; do prune="$prune -o -path $HOME/$p"; done
  fi
  # shellcheck disable=SC2086  # $prune doit être découpé en arguments
  limit 180 find "$HOME" -xdev \( $prune \) -prune -o -type f -size +1G -print 2>/dev/null \
    | while IFS= read -r f; do
        printf '%s\t%s\t%s\n' "$(du -h "$f" | cut -f1)" \
          "$(stat -f %z "$f" | awk '{printf "%.1fG", $1/1073741824}')" "$f"
      done | sort -hr | head -40

  # ---------------------------------------------------------------------------
  section "9. SAUVEGARDE TIME MACHINE"
  tmutil destinationinfo 2>&1
  sub "Dernière sauvegarde"; tmutil latestbackup 2>&1
  sub "Sauvegarde automatique activée"
  defaults read /Library/Preferences/com.apple.TimeMachine AutoBackup 2>/dev/null || echo "(inconnu)"

  # ---------------------------------------------------------------------------
  section "10. PERFORMANCES"
  sub "Pression mémoire"; memory_pressure 2>/dev/null | tail -1
  sub "Top 15 processeur"
  top -l 2 -o cpu -n 15 -stats pid,command,cpu,mem 2>/dev/null | awk '/^PID/{n++} n==2'
  sub "Processus hors système (ni /System, ni /usr, ni /Applications)"
  ps -axo pid=,%cpu=,%mem=,comm= \
    | awk '$4 !~ /^\/(System|usr|sbin|bin|Applications|Library\/Apple)\// && $4 ~ /^\//'
  sub "Batterie"
  pmset -g batt
  system_profiler SPPowerDataType 2>/dev/null | grep -i -E 'cycle count|condition|maximum capacity'

  # ---------------------------------------------------------------------------
  section "11. PLANTAGES RÉCENTS (30 derniers jours)"
  for d in "$HOME/Library/Logs/DiagnosticReports" /Library/Logs/DiagnosticReports; do
    [ -d "$d" ] || continue
    find "$d" -type f -mtime -30 \( -name '*.ips' -o -name '*.crash' -o -name '*.panic' \) 2>/dev/null
  done | xargs -n1 basename 2>/dev/null | sed -E 's/[-_][0-9]{4}-[0-9]{2}-[0-9]{2}.*//' \
    | sort | uniq -c | sort -rn
}

# =============================================================================
# Anonymisation : retire du rapport ce qui identifie la personne ou la machine.
# =============================================================================
anonymize() {
  VMO_HOME="$HOME" \
  VMO_USER="$(id -un)" \
  VMO_FULL="$(id -F 2>/dev/null)" \
  VMO_CN="$(scutil --get ComputerName 2>/dev/null)" \
  VMO_LHN="$(scutil --get LocalHostName 2>/dev/null)" \
  VMO_HN="$(hostname 2>/dev/null)" \
  VMO_SERIAL="$(ioreg -rd1 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformSerialNumber/{print $4}')" \
  VMO_UUID="$(ioreg -rd1 -c IOPlatformExpertDevice | awk -F'"' '/IOPlatformUUID/{print $4}')" \
  perl -pe '
    BEGIN {
      %label = (VMO_FULL => "<nom>", VMO_CN => "<nom-ordinateur>", VMO_HN => "<nom-ordinateur>",
                VMO_LHN => "<nom-ordinateur>", VMO_SERIAL => "<numero-de-serie>", VMO_UUID => "<uuid>");
      @r = ();
      push @r, [$ENV{VMO_HOME}, "~"];
      # Noms génériques (« Mac », « MacBook-Pro »...) : ils n identifient personne
      # et les remplacer abîmerait le rapport (« MacOS », « Mac17,9 »).
      $generic = qr/^(i?Mac|MacBook|Mac[- ]?(mini|Pro|Studio)|MacBook[- ]?(Air|Pro)|localhost)(\.local)?$/i;
      for my $k (keys %label) {
        my $v = $ENV{$k} // "";
        push @r, [$v, $label{$k}] if length($v) >= 3 && $v !~ $generic;
      }
      push @r, [$ENV{VMO_USER}, "<utilisateur>"] if length($ENV{VMO_USER}) >= 3;
      @r = sort { length($b->[0]) <=> length($a->[0]) } @r;   # plus long d abord
    }
    # Remplacement en mot entier seulement (pas au milieu d un autre mot).
    for my $p (@r) {
      my $s = quotemeta($p->[0]);
      if ($p->[1] eq "~") { s/$s/~/g; }
      else { s/(?<![A-Za-z0-9])$s(?![A-Za-z0-9])/$p->[1]/g; }
    }
    s/\b(?:[0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}\b/<adresse-mac>/g;
  ' "$1"
}

# =============================================================================
# Chiffrement : AES-256-CBC + HMAC-SHA256, clé protégée par RSA-OAEP (SHA-256).
# Format du fichier .vmo (texte) :
#   VMO1 / kid / key (base64) / iv (hex) / mac (hex) / data (base64)
# Le HMAC couvre "VMO1|kid|key|iv|data" : toute modification est détectée.
# =============================================================================
encrypt() {
  local in="$1" out="$2" keys ek mk iv keyb64 data mac
  printf '%s\n' "$PUBLIC_KEY" > "$WORK/pub.pem"
  keys=$($OPENSSL rand -hex 64) || return 1
  ek=$(printf '%s' "$keys" | cut -c1-64)       # clé de chiffrement (32 octets)
  mk=$(printf '%s' "$keys" | cut -c65-128)     # clé d'authentification (32 octets)
  iv=$($OPENSSL rand -hex 16) || return 1
  keyb64=$(printf '%s' "$keys" | $OPENSSL pkeyutl -encrypt -pubin -inkey "$WORK/pub.pem" \
           -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 | $OPENSSL base64 -A) || return 1
  data=$($OPENSSL enc -aes-256-cbc -K "$ek" -iv "$iv" -in "$in" | $OPENSSL base64 -A) || return 1
  mac=$(printf '%s' "VMO1|$KEY_ID|$keyb64|$iv|$data" | $OPENSSL dgst -sha256 -hmac "$mk" -r | cut -d' ' -f1)
  [ -n "$keyb64" ] && [ -n "$data" ] && [ -n "$mac" ] || return 1
  {
    echo "VMO1"
    echo "kid: $KEY_ID"
    echo "key: $keyb64"
    echo "iv: $iv"
    echo "mac: $mac"
    echo "data: $data"
  } > "$out"
}

# =============================================================================
# Déroulé
# =============================================================================
exec 3>&1
say ""
say "verifmonordi : bilan de votre Mac (lecture seule, rien n'est installé)"
say "Durée : 2 à 5 minutes. Vous pouvez arrêter à tout moment avec Ctrl+C."
say ""

if [ $ADMIN = 1 ]; then
  say "Mot de passe administrateur (pour quelques vérifications en plus) :"
  sudo -v </dev/tty || { say "  Mot de passe refusé : on continue sans le mode admin."; ADMIN=0; }
fi

START=$(date +%s)
collect > "$RAW" 2>&1
anonymize "$RAW" > "$CLEAN"
rm -f "$RAW"
SECS=$(( $(date +%s) - START ))

LINES=$(wc -l < "$CLEAN" | tr -d ' ')
SIZE=$(( $(wc -c < "$CLEAN") / 1024 ))
say ""
say "Bilan terminé en ${SECS} s : ${LINES} lignes, ${SIZE} Ko."
say ""
say "Le rapport contient : version de macOS, réglages de sécurité, programmes"
say "lancés au démarrage, extensions de navigateurs, liste des applications,"
say "tailles de dossiers, état de la sauvegarde et de la batterie."
say "Il ne contient pas : vos documents, mails, photos, historique, mots de passe."
say "Votre nom, le nom de l'ordinateur et son numéro de série ont été retirés."
say ""

while :; do
  printf "Tapez  v  pour lire le rapport,  o  pour le chiffrer,  n  pour tout annuler : " >&3
  if [ -n "${VMO_TEST_ANSWER:-}" ]; then answer="$VMO_TEST_ANSWER"; echo "$answer" >&3   # tests automatiques uniquement
  else read -r answer </dev/tty; fi
  case "$answer" in
    v|V) less "$CLEAN" </dev/tty >/dev/tty ;;
    o|O|oui|Oui) break ;;
    n|N|non|Non) say "Annulé : rien n'a été conservé ni envoyé."; exit 0 ;;
  esac
done

mkdir -p "$OUTDIR" 2>/dev/null
OUT="$OUTDIR/verifmonordi-$(date +%Y%m%d-%H%M).vmo"
if ! encrypt "$CLEAN" "$OUT"; then
  rm -f "$OUT"
  say "Le chiffrement a échoué : rien n'a été conservé. Contactez verifmonordi."
  exit 1
fi

# Envoi automatique (activé seulement quand le service en ligne fournit l'adresse).
if [ -n "${VMO_UPLOAD_URL:-}" ] && [ -n "${VMO_TOKEN:-}" ]; then
  say "Envoi du rapport chiffré…"
  if curl -fsS --retry 3 --max-time 60 -H "Authorization: Bearer $VMO_TOKEN" \
       -H "Content-Type: text/plain" --data-binary @"$OUT" "$VMO_UPLOAD_URL" >/dev/null; then
    rm -f "$OUT"
    say "Envoyé. Vous pouvez retourner sur la page verifmonordi."
    exit 0
  fi
  say "L'envoi a échoué. Le fichier chiffré reste ici, vous pourrez l'envoyer depuis la page :"
fi

say ""
say "Rapport chiffré créé : $OUT"
say "Lui seul quitte votre Mac ; personne d'autre que verifmonordi ne peut l'ouvrir."