Aller au contenu

Sauvegarde, export et import

Tout ce que Control Center peut copier, transmettre ou adopter, et la forme exacte de chaque artefact. Pour effectuer réellement une sauvegarde, consultez Sauvegarder et restaurer ; pour savoir pourquoi la persistance est scindée de cette façon, consultez Espaces de travail et isolation.

Il existe trois artefacts de copie de base de données, aux portées et aux politiques de suppression différentes.

Artefact Écrit par Forme Couvre
Instantané de l’installation server.backupNow Un répertoire horodaté Chaque base de données de l’installation
Sauvegarde d’espace de travail pipeline Workspace backup Répertoire horodaté avec un seul .db La base de données d’un espace de travail
Export d’espace de travail workspace.export Un seul fichier .db La base de données d’un espace de travail

Tous sont écrits avec VACUUM INTO de SQLite, qui est sûr face à une base de données WAL active : il prend une transaction de lecture, produit une copie défragmentée et cohérente du point de vue transactionnel, et n’exige jamais d’arrêter le serveur.

<dataDir>/backups/2026-07-28T09-12-33-000Z/
manifest.json
global.db
<workspaceId>/workspace.db one directory per workspace

L’arborescence reproduit exactement le répertoire de données actif, et c’est ce qui fait de la restauration d’une installation une copie en retour plutôt qu’une transposition. Le nom du répertoire est l’horodatage UTC avec les : et les . remplacés par des - : il est donc sans danger pour les systèmes de fichiers Windows et se trie chronologiquement comme une simple chaîne.

Les sauvegardes d’espace de travail planifiées résident séparément sous <dataDir>/backups/workspace-backups/<workspaceId>/<timestamp>/workspace.db. Elles ne comportent ni manifeste ni global.db et ne sont pas des instantanés de l’installation. L’étape de rétention du pipeline hebdomadaire ne supprime que les sauvegardes de son propre espace de travail plus anciennes que l’âge configuré (30 jours par défaut) ; les instantanés de l’installation créés à la main ne sont jamais éliminés par cette étape. server.listBackups ne liste que les instantanés de l’installation, y compris les incomplets.

Champ Type Signification
version int Version du format du manifeste. Actuellement 1
created_at ISO-8601 UTC Date à laquelle l’instantané a été pris
global.file string Toujours global.db
global.schema_version int Version du schéma GlobalDatabase au moment de la capture
global.bytes int Taille du global.db écrit
workspace_schema_version int Version du schéma WorkspaceDatabase partagée par chaque fichier d’espace de travail
workspaces[] array Une entrée par espace de travail capturé
workspaces[].workspace_id string L’espace de travail que contient ce fichier
workspaces[].file string Chemin relatif à la racine de l’instantané, <workspaceId>/workspace.db
workspaces[].bytes int Taille de ce fichier
skipped_workspaces[] array of string Espaces de travail que la sauvegarde n’a pas pu capturer

Un espace de travail est ignoré lorsqu’il est enregistré mais qu’on n’y a jamais écrit (le fichier n’existe donc pas encore), ou lorsque son VACUUM INTO a échoué. Dans un cas comme dans l’autre, l’instantané est tout de même mené à son terme et le manifeste indique ce qui manque.

server.listBackups renvoie un indicateur complete par instantané. Il vaut faux lorsque le manifeste est absent ou illisible, lorsque global.db manque, ou lorsque le manifeste désigne un fichier d’espace de travail qui n’est pas sur le disque.

Un instantané incomplet est tout de même listé, car un instantané qu’un opérateur ne voit pas est un instantané qu’il croit posséder. Lorsque le manifeste est illisible, la liste se rabat sur l’arborescence elle-même et signale tous les fichiers <workspaceId>/workspace.db qu’elle trouve — ceux-ci restent adoptables individuellement via workspace.import, même si l’instantané dans son ensemble ne peut pas restaurer une installation.

Les deux artefacts sont uniquement des bases de données. Un répertoire d’espace de travail contient plus que sa base de données, et rien du reste ne voyage :

Chemin Dans un instantané ? Dans un export d’espace de travail ?
global.db Oui Non
<workspaceId>/workspace.db Oui Oui (c’est lui le fichier)
<workspaceId>/blobs/ Non Non
<workspaceId>/skills/ Non Non
<workspaceId>/agents/ Non Non
<workspaceId>/chat_credentials/ Non Non
<workspaceId>/spaces/<spaceId>/ Non Non
<workspaceId>/pr_clones/ Non Non
secrets.json Non Non
models/ Non Non
meetings/<meetingId>/ Non Non
rigs/ Non Non

Une partie de tout cela est jetable — spaces/ contient des arbres de travail copy-on-write reprovisionnés depuis les dépôts, pr_clones/ est un cache, models/ est retéléchargé, rigs/ est de l’état machine. Une autre ne l’est pas : blobs/ contient les images collées vers lesquelles pointent des lignes de la base de données, skills/ et agents/ contiennent les fichiers avec lesquels un agent s’exécute, et secrets.json contient chaque clé d’appairage, identifiant de fournisseur et paramètre SSO de l’installation. Voir Sauvegarder et restaurer pour couvrir le reste.

Les RPC de sauvegarde de l’installation et l’export/import d’espace de travail sont réservés au client complet (fullClient) : un téléphone associé ne peut pas les déclencher. Toutes sont entièrement absentes — non pas refusées, mais structurellement inexistantes — sur un serveur où aucun port de sauvegarde n’est câblé, comme le serveur de démonstration.

Op Type Portée Rôle minimal Arguments Renvoie
server.backupNow mutate Installation propriétaire de l’installation aucun {ok, path}
server.listBackups read Installation propriétaire de l’installation aucun {backups: [...]}
server.deleteBackup mutate Installation propriétaire de l’installation name {ok}
workspace.export read Espace de travail admin aucun {ok, path}
workspace.import mutate Espace de travail owner source_path {ok, workspace_id}
workspace.delete mutate Installation* owner id ligne de registre marquée comme supprimée
  • workspace.delete est sans portée, car la ligne qu’elle marque réside dans le registre global ; son gestionnaire vérifie l’appartenance de l’appelant à l’espace de travail nommé et exige le rôle owner.

Le path de chaque réponse est un chemin sur l’hôte du serveur.

Du plus récent au plus ancien, triés par nom de répertoire (qui est l’horodatage : l’ordre tient donc même pour un instantané dont le manifeste n’a pas pu être lu).

{
"backups": [
{
"path": "/…/backups/2026-07-28T09-12-33-000Z",
"name": "2026-07-28T09-12-33-000Z",
"created_at": "2026-07-28T09:12:33.000Z",
"bytes": 481203712,
"complete": true,
"workspaces": [
{
"workspace_id": "w_9f2c…",
"path": "/…/backups/2026-07-28T09-12-33-000Z/w_9f2c…/workspace.db",
"bytes": 190840832
}
],
"skipped_workspace_ids": []
}
]
}

created_at est omis lorsque le manifeste n’a pas pu être lu. bytes au premier niveau est la taille réelle sur disque du répertoire entier, et non la somme inscrite dans le manifeste — ce qu’un opérateur qui élague des instantanés cherche à savoir, c’est ce que coûte le répertoire, pas ce que la sauvegarde avait l’intention d’écrire.

Chaque workspaces[].path est exactement ce que workspace.import attend comme source_path. C’est délibéré : « restaurer cet espace de travail depuis cet instantané » est l’opération d’import, si bien que les deux ne peuvent pas diverger vers des traitements différents du même fichier.

Avant que quoi que ce soit ne soit remplacé, le fichier candidat est ouvert en lecture seule au moyen d’une connexion SQLite brute de courte durée, et sa ligne workspace_meta est lue.

Condition Résultat
Base SQLite illisible, ou absence de workspace_meta Refusé. Rien n’est touché
Identifiant d’espace de travail enregistré ≠ la cible Autorisé, journalisé. C’est ainsi qu’un espace de travail est dupliqué ou restauré sous un nouvel identifiant
Identifiant d’installation enregistré ≠ cette installation Autorisé, journalisé. Ses appareils associés et ses identifiants utilisateur appartiennent à cette installation et ne se résoudront pas ici
Identifiant d’espace de travail invalide, ou fichier inexistant Refusé

En cas d’acceptation, la base de données de l’espace de travail cible est fermée et retirée du gestionnaire avant que le fichier n’y soit copié par-dessus (une connexion Drift ouverte retient le fichier et son -wal), puis rouverte de sorte que beforeOpen réinstalle les déclencheurs FTS et de synchronisation, et les index FTS sont reconstruits — les tables de contenu d’un fichier importé sont remplies, mais son index peut être périmé.

Chaque opération ci-dessus s’exprime en chemins côté serveur, ce qui n’est une réponse complète que lorsque le serveur est votre propre machine. Ces trois routes transportent les octets, sur la même voie signée par PSK que celle qu’utilisent le proxy multimédia et le magasin de blobs. L’appelant signe une chaîne cible avec la clé pré-partagée de son appareil et transmet la signature sous le nom s.

Route Méthode Requête Cible signée Exige
/backup/workspace GET w, d, s backup-workspace:<workspaceId> admin de l’espace de travail
/backup/snapshot GET n, d, s backup-snapshot:<name> propriétaire de l’installation
/backup/restore POST w, d, s backup-restore:<workspaceId> propriétaire de l’espace de travail

w est un identifiant d’espace de travail, n un nom de répertoire d’instantané, d l’identifiant de l’appareil appelant, s la signature. n est résolu par rapport à la liste des sauvegardes plutôt que concaténé à la racine des sauvegardes : la route ne peut donc pas être pointée vers un répertoire que la liste n’aurait pas montré.

Statut Cause
400 Un paramètre de requête obligatoire manque, ou le corps d’un envoi est vide
403 Appareil inconnu ou révoqué, signature invalide, ou l’appelant n’a pas le rôle requis
404 Aucun port de sauvegarde sur cet hôte, ou le fichier d’instantané/d’espace de travail nommé n’existe pas
405 /backup/restore appelé avec autre chose que POST
413 Le corps de l’envoi dépasse 8 GiB, d’après la longueur de contenu déclarée ou les octets réellement lus
500 L’export ou l’archivage a échoué côté serveur

Les deux téléchargements répondent Cache-Control: no-store avec Accept-Ranges: none. Chaque requête génère une copie fraîche et la supprime une fois diffusée en flux : une réponse mise en cache ou reprise recollerait donc des octets de deux exports différents dans un seul fichier qui a l’air valide sans l’être.

/backup/snapshot renvoie <name>.zip, avec des entrées relatives à la racine de l’instantané, de sorte que la décompression reproduit manifest.json + global.db + <workspaceId>/workspace.db. Les entrées sont écrites dans un ordre trié : deux archives du même instantané sont donc la même archive.

/backup/restore diffuse le corps directement vers un fichier de transit au lieu de le mettre en mémoire tampon, le borne avant et pendant la lecture, et supprime le fichier mis en transit sur toutes les voies de sortie, y compris en cas de refus.

Élément Valeur
Instantanés <dataDir>/backups/<timestamp>/
Sauvegardes d’espace de travail planifiées <dataDir>/backups/workspace-backups/<workspaceId>/<timestamp>/workspace.db
Exports d’espace de travail <dataDir>/backups/exports/<workspaceId>-<timestamp>.db
Transit des transferts <dataDir>/backups/transfer/ — un atelier, balayé par la route qui y écrit
Nom de l’archive d’instantané <snapshot directory name>.zip
Plafond d’envoi 8 GiB par POST /backup/restore
Rétention Instantanés de l’installation : suppression manuelle. Sauvegardes d’espace de travail : pipeline hebdomadaire, 30 jours par défaut

Les répertoires exports/, transfer/ et workspace-backups/ ne sont pas des instantanés de l’installation et sont exclus de server.listBackups. Seul le dernier répertoire a une rétention planifiée ; supprimer un instantané de l’installation exige une action explicite du propriétaire de l’installation.