Aller au contenu principal

Préparation à la restauration

Libre WebUI fournit un inventaire de restauration en lecture seule comme première barrière de sécurité pour la sauvegarde et la restauration. Il indique les états connus qui existent et les conditions détectées qui bloquent un instantané. Il n'acquiert aucun verrou de maintenance et ne copie, ne chiffre, ne téléverse, ne supprime, ne répare ni ne restaure aucune donnée.

libre-webui recovery-check --json > recovery-inventory.json

Depuis une copie des sources, exécutez une fois npm run build:backend, puis remplacez libre-webui recovery-check par npm run recovery:check --. Les installations fournies par npx et Homebrew inspectent ~/.libre-webui par défaut ; DATA_DIR et les options de chemin explicites remplacent cet emplacement.

La commande se termine avec l'état 0 lorsqu'aucun blocage n'est détecté, 1 lorsque le rapport est complet mais que des obstacles à la restauration existent, et 2 en cas d'arguments non valides ou d'échec inattendu de la collecte. Utilisez --data-dir PATH ou --database PATH pour inspecter un emplacement non standard. L'inventaire d'un volume par défaut ou spécifié avec --data-dir n'accepte que le fichier canonique DATA_DIR/data.sqlite et rejette les entrées de base de données, WAL ou SHM liées physiquement, symboliques ou non ordinaires. Un chemin explicite fourni à --database peut se trouver hors de DATA_DIR, mais la base sélectionnée et ses éventuels fichiers associés doivent toujours être des fichiers ordinaires et ne peuvent pas être des liens symboliques. Lorsque --database est utilisé sans --data-dir, la restauration considère le parent de la base comme racine des données afin que la clé correspondante, les objets binaires et les définitions d'extensions soient inventoriés ensemble.

L'environnement d'exécution lit également les anciennes définitions d'extensions depuis le répertoire plugins déterministe du paquet serveur et, pour un PLUGINS_DIR relatif, depuis son ancien emplacement relatif au serveur. La restauration inventorie ces anciens chemins actifs et bloque un instantané limité au volume lorsqu'ils contiennent des définitions personnalisées. Les déploiements empaquetés peuvent passer --legacy-plugins-dir PATH plusieurs fois lorsque la disposition de leur image déplace ces répertoires de compatibilité.

Pour le déploiement Compose privé, exécutez la commande dans le conteneur déployé afin que le rapport décrive le volume monté, le code et les secrets de ce conteneur :

docker exec libre-webui \
libre-webui recovery-check --json --data-dir /app/backend/data

Éléments vérifiés par l'inventaire

Le rapport JSON versionné consigne :

  • les versions de l'application, de Node.js, du système d'exploitation et de l'architecture ;
  • les tailles du fichier SQLite et des fichiers WAL/SHM, quick_check, la validation des clés étrangères, l'empreinte du schéma, la version utilisateur, les tables requises manquantes et la validation sans suivi du fichier source avant la création d'un instantané d'inspection privé ;
  • la lisibilité et l'écriture du répertoire de données, ainsi que le nombre de fichiers et d'octets ;
  • la source de clé de chiffrement sélectionnée et une empreinte unidirectionnelle de 16 caractères ;
  • la validation sans suivi et à lien unique du fichier persistant .encryption_key ;
  • la présence, le nombre, la taille et l'inclusion dans le répertoire de données des définitions d'extensions personnalisées, ainsi que la racine chiffrée des objets binaires locaux, les médias intégrés, les références vocales, le texte des documents, les anciens vecteurs de documents et les vecteurs de plateforme avec leurs lignes de liste de contrôle d'accès et de filtre ;
  • l'authentification bornée et en lecture seule de chaque objet binaire local canonique et de chaque enveloppe intégrée de vecteurs de plateforme, notamment la vérification complète des segments et sommes de contrôle des objets et la disponibilité de la clé configurée ;
  • l'authentification bornée et en lecture seule de chaque enveloppe texte AES-GCM ancienne reconnaissable dans les chats, notes, documents, préférences, secrets d'extensions, état de galerie et de médias et adresse e-mail du compte, ainsi que de chaque enveloppe liée à des données authentifiées supplémentaires pour les noms de voix enregistrés, enregistrements et transcriptions ;
  • le nombre de tâches, exécutions et aperçus Work, ainsi que les volumes Docker, PVC Kubernetes ou identités de chemins de l'hôte hachées attendus ; les volumes Docker doivent porter à la fois le libellé géré et l'identifiant exact de la tâche propriétaire ;
  • les états des anciennes tâches de génération de médias, ainsi que les tâches durables par état, les tentatives par résultat, le nombre de flux et d'événements et le dernier curseur global d'événement ;
  • l'authentification bornée et en lecture seule de chaque charge utile chiffrée de tâche durable et d'événement, ainsi qu'une validation syntaxique bornée de chaque charge utile de référence opaque ; et
  • les blocages et avertissements explicites, ainsi que les données qui résident hors du répertoire de données de l'application.

Le rapport n'inclut jamais les clés de chiffrement, les secrets JWT ou de session, les identifiants des fournisseurs, le contenu des extensions, le contenu des utilisateurs ni les chemins littéraux des espaces de travail de l'hôte. Seuls les booléens indiquant la présence de secrets et l'empreinte non réversible de la clé de chiffrement sont émis.

Un montage de données en lecture seule est valide pour l'inspection de restauration et génère un avertissement, pas un blocage. Le fonctionnement de l'application exige toujours un stockage accessible en écriture ; ne démarrez jamais Libre WebUI sur l'instantané en lecture seule utilisé par l'assistant de sauvegarde.

Blocages

Considérez tout blocage comme un échec de la barrière de restauration. Parmi les blocages courants figurent une base de données absente ou corrompue, un schéma incomplet, une clé absente ou contradictoire, un texte chiffré ancien ou de plateforme corrompu ou non authentifié, le dépassement des bornes de vérification, un répertoire de données illisible, une source SQLite liée ou non ordinaire, des exécutions ou aperçus Work actifs, des tâches multimédias ou des tâches durables actives, un espace de travail Work absent ou mal étiqueté, une incohérence de tête d'événements durables ou des lacunes de séquence, des définitions d'extensions personnalisées hors du répertoire de données ou un plan de contrôle d'environnement d'exécution incapable de vérifier les espaces de travail externes. Mettez le travail actif au repos et résolvez les dépendances manquantes avant de prendre l'instantané ; ne modifiez pas le rapport pour masquer un blocage.

Les charges utiles durables chiffrées sont authentifiées par rapport à l'identité de leur tâche ou événement et validées comme JSON canonique borné. Les charges utiles de références opaques sont seulement bornées et soumises à un contrôle syntaxique : l'infrastructure actuelle ne possède aucun dépôt de références d'objets faisant autorité qui permettrait à la restauration de prouver l'existence ou l'accessibilité de la cible. Le rapport marque referenceTargetsVerified comme faux et émet un avertissement chaque fois que de telles références sont présentes ; il n'expose jamais les valeurs des charges utiles ni des références.

Les champs texte anciens sont antérieurs à l'obligation d'un marqueur d'enveloppe ; les véritables lignes en clair issues d'anciennes générations du schéma restent donc lisibles et ne sont pas signalées comme texte chiffré authentifié. Les enveloppes canoniques sont toujours authentifiées ; les valeurs en trois parties dont l'IV ou la balise d'authentification a la largeur d'une enveloppe échouent de manière fermée lorsqu'elles sont mal formées. Les champs de voix enregistrées disposent d'une enveloppe binaire sans ambiguïté et doivent toujours s'authentifier par rapport à leur profil, propriétaire et identité de champ. La section JSON encryption.legacyCiphertext indique le total des enregistrements texte ou binaires authentifiés et des octets sans exposer le texte en clair.

Lorsque la colonne users.email_lookup du schéma v4 est présente, la restauration authentifie aussi chaque adresse e-mail non nulle et recalcule son jeton de recherche à clé séparé par domaine. Un jeton absent ou différent, ou un jeton associé à une adresse e-mail nulle, bloque l'instantané. Les bases antérieures à la v4 restent compatibles, car elles ne possèdent pas cette colonne de recherche dérivée.

Limites actuelles de la sauvegarde

L'assistant de déploiement privé arrête l'application lorsqu'elle est en cours d'exécution et utilise l'image immuable de ce conteneur, le volume de données monté et l'environnement pour créer une archive solo intégrée. Le manifeste est signé avec Ed25519 et la charge utile complète est chiffrée au moyen d'une clé de sauvegarde AES-256-GCM détenue par la personne qui exploite le système. Elle contient SQLite, les objets binaires locaux et vecteurs intégrés, les sélecteurs d'environnement d'exécution et la configuration protégée nécessaire au déchiffrement de l'état restauré. L'assistant vérifie la signature, la somme de contrôle du texte chiffré et la charge utile déchiffrée avant de publier l'archive et le rapport de métadonnées. libre-webui-restore n'accepte qu'un nouveau volume Docker, vérifie l'inventaire de restauration déchiffré avant de copier toute donnée et publie la configuration récupérée sous forme de fichiers privés dans un nouveau répertoire cible.

La configuration protégée de l'environnement d'exécution comprend les délais d'expiration du pool, de la connexion, de l'inactivité, des instructions et du verrou de migration PostgreSQL ; le délai de connexion Redis ; les deux paramètres de quota des objets binaires durables ; les sélecteurs de plateforme ; ainsi que le préfixe S3 et le mode d'adressage. Ces valeurs se trouvent dans la charge utile signée et chiffrée, et non dans le manifeste en clair ; elles sont republiées comme configuration de mode 0600 lors d'une restauration appliquée.

L'archive solo n'inclut pas les volumes Docker Work, les PVC Kubernetes, les dossiers d'espaces de travail liés à l'hôte, les modèles Ollama ni l'état des fournisseurs externes. Gardez ces exclusions du manifeste signé bien visibles et prenez séparément un instantané du stockage Work externe. Le profil équipe utilise le workflow hors ligne distinct pour équipe : un instantané PostgreSQL exporté, les objets de texte chiffré S3 exactement versionnés, l'inventaire PGVector, la configuration de l'environnement d'exécution et l'identité de la clé sont scellés dans le même format d'archive signé et chiffré, puis vérifiés par rapport à une cible PostgreSQL/S3 propre lors de la restauration. Le cache Redis, les présences, les réveils et les baux sont reconstruits à partir de l'état SQL canonique.

La sauvegarde d'équipe authentifie également chaque charge utile chiffrée bornée de tâche durable et d'événement dans l'instantané PostgreSQL exporté exact. Son inventaire signé protégé consigne les totaux de tâches, événements, flux, curseurs, enveloppes, références et textes en clair authentifiés. Chaque flux d'événements doit contenir exactement la séquence contiguë 1..last_sequence, et la séquence globale de curseurs de PostgreSQL ne doit pas être en retard sur le plus grand curseur enregistré. La restauration répète ces contrôles sur la cible propre et exige que le résultat complet corresponde à l'inventaire source signé avant d'annoncer la réussite. Les intervalles entre des valeurs distinctes de curseur global sont valides, car l'allocation des identités PostgreSQL n'est pas transactionnelle ; les séquences par flux constituent le contrat d'ordonnancement contigu.

Lorsque PLUGINS_DIR pointe hors de DATA_DIR, la restauration inventorie précisément ce répertoire et le signale comme exclu de l'archive du volume de l'application. Toute définition qui s'y trouve bloque l'instantané limité au volume jusqu'à ce que la personne qui exploite le système organise un instantané correspondant du répertoire d'extensions. La même règle s'applique aux anciens répertoires d'extensions actifs. Les définitions JSON liées symboliquement, non ordinaires ou illisibles constituent toujours des blocages et ne sont jamais suivies ni omises silencieusement.

Les tâches durables et événements ordonnés sont actifs dans les deux profils. La restauration bloque tant qu'une tentative de tâche ou une exécution Work est active, valide les charges utiles des tâches et événements ainsi que les têtes de flux contiguës, puis préserve leur état SQL canonique. Le profil solo exécute le worker intégré borné ; le profil équipe exécute les mêmes gestionnaires enregistrés dans un worker externe et n'utilise Redis que pour le réveil et la diffusion.

En production, stockez les secrets de chiffrement et JWT dans un gestionnaire de secrets protégé, conservez les archives de sauvegarde chiffrées hors de l'hôte et testez les restaurations dans un environnement compatible propre. L'inventaire constitue un contrôle préalable de l'état connu, et non un verrou de maintenance ni une preuve indépendante que chaque ressource externe peut être restaurée.

Commandes de sauvegarde signée et chiffrée

Les exemples ci-dessous utilisent la commande libre-webui installée globalement par npm ou Homebrew. Sans installation globale, remplacez libre-webui par npx --yes libre-webui@latest. Depuis une copie des sources, compilez une fois le serveur et remplacez libre-webui backup par npm run recovery:backup --. L'image Docker de production expose la même commande dans /usr/local/bin/libre-webui. La sauvegarde et la restauration d'équipe nécessitent aussi pg_dump et pg_restore de PostgreSQL 16 ; ils sont inclus dans l'image de production et dans le chemin des commandes de la formule Homebrew. Installez explicitement un client PostgreSQL compatible avant d'utiliser ces commandes depuis npm ou npx seuls.

Générez la clé d'archive AES-256-GCM détenue par la personne qui exploite le système et la paire de clés de signature Ed25519 dans un répertoire privé, puis déplacez les clés privées vers un stockage hors hôte protégé :

install -d -m 0700 /absolute/private/libre-backup-keys
libre-webui backup keygen \
--directory /absolute/private/libre-backup-keys

Pour un répertoire de données solo au repos, créez puis vérifiez indépendamment une archive :

libre-webui backup create \
--offline \
--data-dir /absolute/path/to/libre-data \
--output /absolute/backups/libre-solo.lwbackup \
--encryption-key /absolute/private/libre-backup-keys/backup-encryption.key \
--signing-private-key /absolute/private/libre-backup-keys/backup-signing-private.pem

libre-webui backup verify \
--archive /absolute/backups/libre-solo.lwbackup \
--encryption-key /absolute/private/libre-backup-keys/backup-encryption.key \
--signing-public-key /absolute/private/libre-backup-keys/backup-signing-public.pem

Effectuez d'abord un contrôle préalable de restauration, puis ne l'appliquez qu'à un nouveau répertoire cible vide :

libre-webui backup restore-preflight \
--archive /absolute/backups/libre-solo.lwbackup \
--target /absolute/restore/libre-data \
--encryption-key /absolute/private/libre-backup-keys/backup-encryption.key \
--signing-public-key /absolute/private/libre-backup-keys/backup-signing-public.pem

libre-webui backup restore-apply \
--archive /absolute/backups/libre-solo.lwbackup \
--target /absolute/restore/libre-data \
--encryption-key /absolute/private/libre-backup-keys/backup-encryption.key \
--signing-public-key /absolute/private/libre-backup-keys/backup-signing-public.pem

libre-webui backup restore-verify \
--target /absolute/restore/libre-data

Pour le mode équipe, arrêtez toutes les répliques de l'application et tous les workers, conservez l'environnement PostgreSQL/S3/trousseau de clés source chargé, puis créez l'archive coordonnée :

libre-webui backup create-team \
--offline \
--output /absolute/backups/libre-team.lwbackup \
--encryption-key /absolute/private/libre-backup-keys/backup-encryption.key \
--signing-private-key /absolute/private/libre-backup-keys/backup-signing-private.pem

Avant la restauration, chargez les variables d'environnement d'une base PostgreSQL distincte et vide et d'un bucket S3 vide avec gestion des versions. Le contrôle préalable vérifie la signature et l'archive chiffrée, valide l'inventaire protégé et prouve que la base cible et le préfixe du bucket choisis sont vides sans publier de données. L'application restaure dans ces cibles propres, vérifie le schéma PostgreSQL obtenu, les objets S3 exacts et les enregistrements PGVector, puis écrit la configuration protégée de l'environnement d'exécution dans un nouveau répertoire privé :

libre-webui backup restore-team-preflight \
--archive /absolute/backups/libre-team.lwbackup \
--encryption-key /absolute/private/libre-backup-keys/backup-encryption.key \
--signing-public-key /absolute/private/libre-backup-keys/backup-signing-public.pem

libre-webui backup restore-team-apply \
--archive /absolute/backups/libre-team.lwbackup \
--configuration-output /absolute/restore/libre-team-config \
--encryption-key /absolute/private/libre-backup-keys/backup-encryption.key \
--signing-public-key /absolute/private/libre-backup-keys/backup-signing-public.pem

Ne dirigez jamais une restauration vers la base source, le bucket source, un répertoire de données existant ou un répertoire de configuration qui contient des fichiers. Conservez la clé de signature publique avec la procédure de restauration ; la possession de l'archive et de la clé publique ne suffit pas à déchiffrer la charge utile.

Si la restauration d'équipe signale une annulation incomplète, considérez les deux cibles choisies comme souillées et ne réessayez pas immédiatement. Inspectez et nettoyez la base PostgreSQL cible, puis répertoriez et supprimez chaque version d'objet et marqueur de suppression sous le préfixe S3 cible exact. Relancez restore-team-preflight ; l'application ne peut être retentée en toute sécurité qu'une fois ce contrôle de cible propre réussi.

Exercices planifiés et vérifiés de restauration

Des sauvegardes qui n'ont jamais été restaurées ne sont qu'un espoir, pas une stratégie de reprise. Un exercice prouve que l'instance est réellement récupérable en exécutant précisément le processus ci-dessus, de bout en bout, sans interruption de service ni intervention humaine :

  1. Un instantané au repos du répertoire de données est préparé : la base SQLite au moyen de l'API de sauvegarde en ligne, les objets binaires et fichiers par copie physique. L'exercice attend un moment calme : il refuse de s'exécuter pendant qu'une tâche durable est en cours, selon la même règle qu'applique recovery-check.
  2. La copie préparée devient une archive signée et chiffrée avec AES-256-GCM à l'aide de clés éphémères propres à l'exercice, tout en exécutant l'inventaire de restauration complet.
  3. L'archive est vérifiée, restaurée dans une cible temporaire isolée, puis l'environnement restauré est à nouveau vérifié.
  4. L'exercice consigne ses mesures : la durée de restauration est le RTO démontré, et l'intervalle entre les exercices réussis borne le RPO réalisable avec la planification actuelle. Il supprime ensuite chaque artefact. Les exercices servent à vérifier, pas à sauvegarder : aucune archive ni clé n'est conservée.

Activez la planification avec RECOVERY_DRILL_INTERVAL_HOURS (par exemple 24) ; les exercices s'exécutent alors sur le planificateur partagé sous un bail de coordination, ce qui empêche les répliques et les déclenchements simultanés de les exécuter en double. La page Système affiche l'historique des exercices avec un bouton « Exécuter l'exercice maintenant » pour les administrateurs, adossé à GET /api/recovery/drills et POST /api/recovery/drills/run. L'échec d'un exercice sans surveillance alerte chaque administrateur dans la boîte de notifications (ainsi que toute cible webhook abonnée) ; les exécutions manuelles signalent directement leur refus. RECOVERY_DRILL_HISTORY limite l'historique conservé (60 entrées par défaut).

Les exercices couvrent le profil solo (SQLite), pour lequel l'archive du système de fichiers constitue la méthode de sauvegarde faisant autorité. Le profil équipe conserve son flux coordonné backup create-team, dont la répétition de restauration reste pour l'instant une étape de la procédure réalisée par une personne qui exploite le système.