Aller au contenu

Sauvegarde, export et import

Tout ce que Control Center peut copier, céder ou adopter, et la forme exacte de chaque artéfact. Pour prendre réellement une sauvegarde, voir Sauvegarder et restaurer ; pour comprendre pourquoi la persistance est ainsi divisée, voir Espaces de travail et isolation.

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

Artéfact É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 fichier .db unique La base de données d’un espace de travail

Tous sont écrits avec VACUUM INTO de SQLite, qui ne craint pas 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

La disposition reflète exactement le répertoire de données actif, ce qui fait de la restauration d’une installation une recopie plutôt qu’une conversion. Le nom du répertoire est l’horodatage UTC, : et . remplacés par -, de sorte qu’il est sécuritaire pour le système de fichiers sous Windows et qu’il se trie chronologiquement comme une simple chaîne.

Les sauvegardes d’espace de travail planifiées vivent séparément à <dataDir>/backups/workspace-backups/<workspaceId>/<timestamp>/workspace.db. Elles n’incluent 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 la durée configurée (30 jours par défaut) ; les instantanés de l’installation créés manuellement ne sont jamais supprimé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 Moment où l’instantané a été pris
global.file string Toujours global.db
global.schema_version int Version du schéma de GlobalDatabase au moment de la capture
global.bytes int Taille du global.db écrit
workspace_schema_version int Version du schéma de 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 ce fichier contient
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 n’a jamais été écrit (aucun fichier n’existe encore), ou lorsque son VACUUM INTO a échoué. Dans un cas comme dans l’autre, l’instantané se termine quand même et le manifeste nomme ce qui manque.

server.listBackups signale un indicateur complete par instantané. Il est faux lorsque le manifeste est absent ou impossible à analyser, lorsque global.db manque, ou lorsque le manifeste nomme un fichier d’espace de travail qui n’est pas sur le disque.

Un instantané incomplet est tout de même listé, parce qu’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 la disposition elle-même et signale tous les fichiers <workspaceId>/workspace.db qu’elle trouve — ceux-ci demeurent adoptables un par un au moyen de workspace.import, même si l’instantané dans son ensemble ne peut pas restaurer une installation.

Les deux artéfacts sont des bases de données uniquement. 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 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 cela est jetable — spaces/ contient des arbres de travail copie sur écriture qui sont reprovisionnés à partir des dépôts, pr_clones/ est un cache, models/ se retélécharge, rigs/ est un état de machine. Une autre partie ne l’est pas : blobs/ contient des 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, chaque identifiant de fournisseur et chaque 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 ne sont accessibles qu’aux fullClient : un téléphone appairé ne peut pas les déclencher. Tous sont totalement absents — non pas refusés, mais structurellement manquants — sur un serveur sans port de sauvegarde câblé, comme le serveur de démonstration.

Op Type Portée Rôle minimal Arguments Retourne
server.backupNow mutation Installation propriétaire de l’installation aucun {ok, path}
server.listBackups lecture Installation propriétaire de l’installation aucun {backups: [...]}
server.deleteBackup mutation Installation propriétaire de l’installation name {ok}
workspace.export lecture Espace de travail admin aucun {ok, path}
workspace.import mutation Espace de travail owner source_path {ok, workspace_id}
workspace.delete mutation Installation* owner id ligne du registre marquée supprimée
  • workspace.delete n’a pas de portée parce que la ligne qu’il marque vit dans le registre global ; son gestionnaire vérifie l’appartenance de l’appelant à l’espace de travail nommé et exige owner.

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

Du plus récent au plus ancien, trié par nom de répertoire (qui est l’horodatage, de sorte que l’ordre tient 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. La valeur bytes du niveau supérieur est la taille réelle sur le disque de tout le répertoire, et non la somme du manifeste lui-même — la question que se pose un opérateur qui élague les instantanés est ce que coûte le répertoire, et non ce que la sauvegarde comptait écrire.

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

Avant que quoi que ce soit soit remplacé, le fichier candidat est ouvert en lecture seule au moyen d’une connexion SQLite brute éphémère et sa ligne workspace_meta est lue.

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

À l’acceptation, la base de données de l’espace de travail cible est fermée et retirée du gestionnaire avant que le fichier soit copié par-dessus (une connexion drift ouverte retient le fichier et son -wal), puis rouverte afin 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 peuplées, 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 qu’utilisent le proxy de médias et le magasin d’images. L’appelant signe une chaîne cible avec la clé prépartagée de son appareil et transmet la signature comme s.

Route Méthode Requête Cible signée Exige
/backup/workspace GET w, d, s backup-workspace:<workspaceId> administrateur 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 à partir de la liste des sauvegardes plutôt que joint à la racine des sauvegardes, de sorte que la route ne peut pas viser 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 téléversement est vide
403 Appareil inconnu ou révoqué, signature invalide, ou l’appelant n’a pas le rôle
404 Aucun port de sauvegarde sur cet hôte, ou l’instantané ou le fichier d’espace de travail nommé n’existe pas
405 /backup/restore appelé avec autre chose que POST
413 Le corps du téléversement dépasse 8 GiB, selon 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 crée une copie fraîche et la supprime une fois diffusée, de sorte qu’une réponse mise en cache ou reprise souderait les octets de deux exports différents en un seul fichier qui a l’air valide sans l’être.

/backup/snapshot renvoie <name>.zip, dont les entrées sont 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 en ordre trié, de sorte que deux archives du même instantané sont la même archive.

/backup/restore diffuse le corps directement vers un fichier de préparation plutôt que de le mettre en mémoire tampon, le borne avant et pendant la lecture, et supprime le fichier préparé sur chaque chemin 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
Préparation des transferts <dataDir>/backups/transfer/ — un établi, balayé par la route qui l’écrit
Nom de l’archive d’instantané <snapshot directory name>.zip
Plafond de téléversement 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 l’action explicite du propriétaire de l’installation.