#!/usr/bin/env bash # # Voxforge installer # -------------------------------------------------------------------------- # Installe Voxforge — dictée et comptes rendus médicaux 100 % en local — # sur Mac Apple Silicon (M1+). L'application est ensuite mise à jour # automatiquement à chaque lancement. # # Usage : # curl -fsSL https://voxforge.io/install.sh | bash # # Variables d'environnement (optionnelles) : # VOXFORGE_BASE_URL URL de base des téléchargements (def: https://voxforge.io) # VOXFORGE_HOME Dossier d'installation (def: $HOME/.voxforge) # VOXFORGE_BIN_DIR Dossier du raccourci CLI (def: $HOME/.local/bin) # -------------------------------------------------------------------------- set -euo pipefail BASE_URL="${VOXFORGE_BASE_URL:-https://voxforge.io}" VOXFORGE_HOME="${VOXFORGE_HOME:-$HOME/.voxforge}" BIN_DIR="${VOXFORGE_BIN_DIR:-$HOME/.local/bin}" # ---- jolis logs -------------------------------------------------------------- if [ -t 1 ]; then BOLD=$'\033[1m'; DIM=$'\033[2m'; RED=$'\033[31m'; GREEN=$'\033[32m' BLUE=$'\033[34m'; CYAN=$'\033[36m'; RESET=$'\033[0m' else BOLD=""; DIM=""; RED=""; GREEN=""; BLUE=""; CYAN=""; RESET="" fi say() { printf '%s==>%s %s\n' "$BLUE$BOLD" "$RESET" "$*"; } ok() { printf '%s ✓ %s%s\n' "$GREEN" "$*" "$RESET"; } warn() { printf '%s ! %s%s\n' "$RED" "$*" "$RESET" >&2; } die() { printf '\n%sErreur :%s %s\n' "$RED$BOLD" "$RESET" "$*" >&2; exit 1; } printf '\n%s' "$CYAN$BOLD" cat <<'BANNER' __ __ __ \ \ / /____ __/ _| ___ _ __ __ _ ___ \ \ / / _ \ \/ / |_ / _ \| '__/ _` |/ _ \ \ V / (_) > <| _| (_) | | | (_| | __/ \_/ \___/_/\_\_| \___/|_| \__, |\___| |___/ Dictée & comptes rendus médicaux — 100 % en local BANNER printf '%s\n' "$RESET" # ---- 1. Vérifications système ------------------------------------------------ say "Vérification de l'environnement..." [ "$(uname -s)" = "Darwin" ] || die "Voxforge fonctionne uniquement sur macOS." ARCH="$(uname -m)" if [ "$ARCH" != "arm64" ]; then die "Voxforge nécessite un Mac à puce Apple (M1+). Architecture détectée : $ARCH." fi ok "macOS sur puce Apple ($ARCH)" command -v python3 >/dev/null 2>&1 || die "Python 3 est introuvable. Installez les outils en ligne de commande Xcode : xcode-select --install" PY_VER="$(python3 -c 'import sys; print("%d.%d" % sys.version_info[:2])')" PY_OK="$(python3 -c 'import sys; print(1 if sys.version_info[:2] >= (3, 9) else 0)')" [ "$PY_OK" = "1" ] || die "Python 3.9+ est requis (version détectée : $PY_VER)." ok "Python $PY_VER" command -v curl >/dev/null 2>&1 || die "curl est requis." # Outil de checksum (shasum sur macOS) if command -v shasum >/dev/null 2>&1; then SHA_CMD="shasum -a 256" elif command -v sha256sum >/dev/null 2>&1; then SHA_CMD="sha256sum" else die "Aucun outil de vérification SHA-256 trouvé (shasum/sha256sum)." fi # ---- 2. Récupération du manifeste de version -------------------------------- say "Récupération de la dernière version..." TMP_DIR="$(mktemp -d)" trap 'rm -rf "$TMP_DIR"' EXIT curl -fsSL "$BASE_URL/download/version.json" -o "$TMP_DIR/version.json" \ || die "Impossible de récupérer le manifeste depuis $BASE_URL/download/version.json" read_json() { # read_json : extrait une valeur string d'un JSON plat (sans dépendance). python3 - "$1" "$TMP_DIR/version.json" <<'PY' import json, sys key, path = sys.argv[1], sys.argv[2] with open(path) as f: data = json.load(f) print(data.get(key, "")) PY } VERSION="$(read_json version)" PKG_PATH="$(read_json package)" SHA256="$(read_json sha256)" [ -n "$VERSION" ] || die "Manifeste invalide : champ 'version' manquant." [ -n "$PKG_PATH" ] || PKG_PATH="download/voxforge-latest.tar.gz" ok "Version disponible : $VERSION" # ---- 3. Téléchargement du paquet -------------------------------------------- say "Téléchargement de Voxforge ${VERSION}..." PKG_URL="$BASE_URL/$PKG_PATH" curl -fSL --progress-bar "$PKG_URL" -o "$TMP_DIR/voxforge.tar.gz" \ || die "Échec du téléchargement depuis $PKG_URL" if [ -n "$SHA256" ]; then say "Vérification de l'intégrité (SHA-256)..." ACTUAL="$($SHA_CMD "$TMP_DIR/voxforge.tar.gz" | awk '{print $1}')" [ "$ACTUAL" = "$SHA256" ] || die "Empreinte SHA-256 invalide. Téléchargement corrompu ou altéré." ok "Intégrité vérifiée" fi # ---- 4. Installation des fichiers ------------------------------------------- say "Installation dans ${VOXFORGE_HOME}..." mkdir -p "$VOXFORGE_HOME/releases" RELEASE_DIR="$VOXFORGE_HOME/releases/$VERSION" rm -rf "$RELEASE_DIR" mkdir -p "$RELEASE_DIR" tar -xzf "$TMP_DIR/voxforge.tar.gz" -C "$RELEASE_DIR" --strip-components=1 \ || die "Échec de l'extraction de l'archive." # Lien symbolique 'current' -> release active ln -sfn "$RELEASE_DIR" "$VOXFORGE_HOME/current" cp "$TMP_DIR/version.json" "$VOXFORGE_HOME/version.json" ok "Fichiers installés" # ---- 5. Environnement Python (venv + dépendances) --------------------------- say "Préparation de l'environnement Python (peut prendre une minute)..." VENV="$VOXFORGE_HOME/venv" if [ ! -d "$VENV" ]; then python3 -m venv "$VENV" fi # shellcheck disable=SC1091 "$VENV/bin/python" -m pip install --quiet --upgrade pip "$VENV/bin/python" -m pip install --quiet -r "$VOXFORGE_HOME/current/requirements.txt" \ || die "Échec de l'installation des dépendances Python." ok "Environnement prêt" # ---- 6. Lanceur + auto-update ----------------------------------------------- say "Installation du lanceur 'voxforge'..." mkdir -p "$BIN_DIR" LAUNCHER="$BIN_DIR/voxforge" cat > "$LAUNCHER" </dev/null 2>&1; then SHA_CMD="shasum -a 256"; else SHA_CMD="sha256sum"; fi current_version() { [ -f "\$VOXFORGE_HOME/version.json" ] || { echo ""; return; } python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("version",""))' "\$VOXFORGE_HOME/version.json" 2>/dev/null || echo "" } self_update() { local tmp manifest remote pkg sha url rel command -v curl >/dev/null 2>&1 || return 0 tmp="\$(mktemp -d)" || return 0 trap 'rm -rf "\$tmp"' RETURN curl -fsSL --max-time 8 "\$BASE_URL/download/version.json" -o "\$tmp/version.json" 2>/dev/null || { echo " (hors-ligne — mise à jour ignorée)"; return 0; } remote="\$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("version",""))' "\$tmp/version.json" 2>/dev/null || echo "")" [ -n "\$remote" ] || return 0 if [ "\$remote" = "\$(current_version)" ]; then return 0; fi echo "==> Mise à jour disponible : \$remote - installation..." pkg="\$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("package","download/voxforge-latest.tar.gz"))' "\$tmp/version.json")" sha="\$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("sha256",""))' "\$tmp/version.json")" url="\$BASE_URL/\$pkg" curl -fSL --progress-bar "\$url" -o "\$tmp/voxforge.tar.gz" 2>/dev/null || { echo " (téléchargement échoué — version actuelle conservée)"; return 0; } if [ -n "\$sha" ]; then actual="\$(\$SHA_CMD "\$tmp/voxforge.tar.gz" | awk '{print \$1}')" [ "\$actual" = "\$sha" ] || { echo " (intégrité invalide — mise à jour annulée)"; return 0; } fi rel="\$VOXFORGE_HOME/releases/\$remote" rm -rf "\$rel"; mkdir -p "\$rel" tar -xzf "\$tmp/voxforge.tar.gz" -C "\$rel" --strip-components=1 || return 0 ln -sfn "\$rel" "\$VOXFORGE_HOME/current" cp "\$tmp/version.json" "\$VOXFORGE_HOME/version.json" "\$VENV/bin/python" -m pip install --quiet -r "\$VOXFORGE_HOME/current/requirements.txt" || true echo "==> Mise à jour vers \$remote terminée." } echo "Voxforge \$(current_version)" if [ "\${VOXFORGE_NO_UPDATE:-0}" != "1" ]; then self_update; fi # Voxforge ouvre sa propre fenêtre native (pywebview). En mode navigateur # (VOXFORGE_NO_GUI=1 ou pywebview absent), on ouvre l'URL automatiquement. if [ "\${VOXFORGE_NO_GUI:-0}" = "1" ]; then URL="http://127.0.0.1:5001" ( sleep 2; command -v open >/dev/null 2>&1 && open "\$URL" >/dev/null 2>&1 || true ) & fi cd "\$VOXFORGE_HOME/current" exec "\$VENV/bin/python" app.py LAUNCHER_EOF chmod +x "$LAUNCHER" ok "Lanceur installé : $LAUNCHER" # ---- 7. PATH ---------------------------------------------------------------- case ":$PATH:" in *":$BIN_DIR:"*) PATH_OK=1 ;; *) PATH_OK=0 ;; esac printf '\n%s' "$GREEN$BOLD" cat <<'DONE' Voxforge est installé. 🎉 DONE printf '%s\n' "$RESET" if [ "$PATH_OK" = "1" ]; then printf ' Lancez l'\''application avec : %svoxforge%s\n\n' "$BOLD" "$RESET" else warn "Le dossier $BIN_DIR n'est pas dans votre PATH." SHELL_RC="$HOME/.zshrc" case "${SHELL:-}" in */bash) SHELL_RC="$HOME/.bash_profile" ;; esac printf ' Ajoutez-le en exécutant :\n' printf ' %secho '\''export PATH="%s:$PATH"'\'' >> %s%s\n' "$DIM" "$BIN_DIR" "$SHELL_RC" "$RESET" printf ' %ssource %s%s\n\n' "$DIM" "$SHELL_RC" "$RESET" printf ' Puis lancez : %svoxforge%s\n' "$BOLD" "$RESET" printf ' (ou directement : %s%s/voxforge%s)\n\n' "$BOLD" "$BIN_DIR" "$RESET" fi printf ' %sL'\''interface s'\''ouvrira sur http://127.0.0.1:5001%s\n' "$DIM" "$RESET" printf ' %sAu premier lancement, les modèles d'\''IA (~5 Go) seront téléchargés.%s\n\n' "$DIM" "$RESET"