Zum Inhalt springen

Backup, Export und Import

Alles, was Control Center kopieren, übergeben oder übernehmen kann, und die genaue Form jedes Artefakts. Wie du tatsächlich eine Sicherung anlegst, steht unter Sichern und wiederherstellen. Warum die Persistenz so aufgeteilt ist, steht unter Arbeitsbereiche und Isolation.

Es gibt drei Artefakte, die eine Datenbank kopieren, mit unterschiedlichem Geltungsbereich und unterschiedlicher Löschregel.

Artefakt Geschrieben von Form Umfang
Momentaufnahme der Installation server.backupNow Ein Verzeichnis mit Zeitstempel Jede Datenbank der Installation
Arbeitsbereichssicherung Pipeline Workspace backup Verzeichnis mit Zeitstempel und einer .db Die Datenbank eines Arbeitsbereichs
Arbeitsbereichs-Export workspace.export Eine einzelne .db-Datei Die Datenbank eines Arbeitsbereichs

Alle werden mit SQLite VACUUM INTO geschrieben. Das ist gegen eine laufende WAL-Datenbank sicher: es nimmt eine Lesetransaktion, erzeugt eine defragmentierte und transaktional konsistente Kopie und verlangt nie, den Server anzuhalten.

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

Das Layout spiegelt das laufende Datenverzeichnis genau. Deshalb ist das Wiederherstellen einer Installation ein Zurückkopieren und keine Umwandlung. Der Verzeichnisname ist der UTC-Zeitstempel, in dem : und . durch - ersetzt sind. So ist er auf Windows dateisystemsicher und sortiert als bloße Zeichenkette chronologisch.

Geplante Arbeitsbereichssicherungen liegen getrennt unter <dataDir>/backups/workspace-backups/<workspaceId>/<timestamp>/workspace.db. Sie enthalten kein Manifest und kein global.db und sind keine Momentaufnahmen der Installation. Der Aufbewahrungsschritt der wöchentlichen Pipeline entfernt nur Sicherungen des eigenen Arbeitsbereichs, die älter sind als das konfigurierte Alter (standardmäßig 30 Tage). Manuell erstellte Momentaufnahmen der Installation fasst dieser Schritt nie an. server.listBackups listet nur Momentaufnahmen der Installation, auch unvollständige.

Feld Typ Bedeutung
version Ganzzahl Version des Manifest-Formats. Derzeit 1
created_at ISO-8601 UTC Wann die Momentaufnahme erstellt wurde
global.file Zeichenkette Immer global.db
global.schema_version Ganzzahl Schemaversion von GlobalDatabase zum Zeitpunkt der Erfassung
global.bytes Ganzzahl Größe der geschriebenen global.db
workspace_schema_version Ganzzahl Schemaversion von WorkspaceDatabase, die jede Arbeitsbereichsdatei teilt
workspaces[] Array Ein Eintrag je erfasstem Arbeitsbereich
workspaces[].workspace_id Zeichenkette Der Arbeitsbereich, den diese Datei enthält
workspaces[].file Zeichenkette Pfad relativ zur Wurzel der Momentaufnahme, <workspaceId>/workspace.db
workspaces[].bytes Ganzzahl Größe dieser Datei
skipped_workspaces[] Array aus Zeichenketten Arbeitsbereiche, die die Sicherung nicht erfassen konnte

Ein Arbeitsbereich wird übersprungen, wenn er registriert, aber noch nie beschrieben wurde (es gibt also noch keine Datei), oder wenn sein VACUUM INTO fehlgeschlagen ist. In beiden Fällen wird die Momentaufnahme trotzdem fertig, und das Manifest nennt, was fehlt.

server.listBackups meldet je Momentaufnahme ein Flag complete. Es ist falsch, wenn das Manifest fehlt oder sich nicht parsen lässt, wenn global.db fehlt, oder wenn das Manifest eine Arbeitsbereichsdatei nennt, die nicht auf der Platte liegt.

Eine unvollständige Momentaufnahme steht trotzdem in der Liste. Eine Momentaufnahme, die du nicht siehst, ist eine, von der du glaubst, sie zu haben. Lässt sich das Manifest nicht lesen, fällt die Auflistung auf das Layout selbst zurück und meldet die <workspaceId>/workspace.db-Dateien, die sie findet. Die lassen sich weiterhin einzeln über workspace.import übernehmen, auch wenn die Momentaufnahme als Ganzes eine Installation nicht wiederherstellen kann.

Beide Artefakte sind nur Datenbanken. Ein Arbeitsbereichsverzeichnis hält mehr als seine Datenbank, und nichts vom Rest geht mit:

Pfad In einer Momentaufnahme? In einem Arbeitsbereichs-Export?
global.db Ja Nein
<workspaceId>/workspace.db Ja Ja (sie ist die Datei)
<workspaceId>/blobs/ Nein Nein
<workspaceId>/skills/ Nein Nein
<workspaceId>/agents/ Nein Nein
<workspaceId>/chat_credentials/ Nein Nein
<workspaceId>/spaces/<spaceId>/ Nein Nein
<workspaceId>/pr_clones/ Nein Nein
secrets.json Nein Nein
models/ Nein Nein
meetings/<meetingId>/ Nein Nein
rigs/ Nein Nein

Einiges davon ist entbehrlich. spaces/ hält Copy-on-Write-Worktrees, die aus den Repos neu bereitgestellt werden, pr_clones/ ist ein Cache, models/ wird neu heruntergeladen, rigs/ ist Maschinenzustand. Anderes nicht: blobs/ hält eingefügte Bilder, auf die Zeilen in der Datenbank zeigen, skills/ und agents/ halten die Dateien, mit denen ein Agent läuft, und secrets.json hält jeden Kopplungsschlüssel, alle Anbieter-Zugangsdaten und alle SSO-Einstellungen der Installation. Den Rest deckt Sichern und wiederherstellen ab.

Die RPCs für die Sicherung der Installation sowie Export und Import eines Arbeitsbereichs gibt es nur für fullClient: ein gekoppeltes Telefon kann sie nicht auslösen. Auf einem Server ohne verdrahteten Backup-Port, etwa dem Demo-Server, fehlen sie ganz. Sie werden nicht abgelehnt, sie sind strukturell nicht da.

Operation Art Geltungsbereich Mindestrolle Argumente Rückgabe
server.backupNow Schreiben Installation Eigentümer der Installation keine {ok, path}
server.listBackups Lesen Installation Eigentümer der Installation keine {backups: [...]}
server.deleteBackup Schreiben Installation Eigentümer der Installation name {ok}
workspace.export Lesen Arbeitsbereich admin keine {ok, path}
workspace.import Schreiben Arbeitsbereich owner source_path {ok, workspace_id}
workspace.delete Schreiben Installation* owner id Registry-Zeile als gelöscht markiert
  • workspace.delete hat keinen Geltungsbereich, weil die Zeile, die es markiert, in der globalen Registry lebt. Der Handler prüft die Mitgliedschaft des Aufrufers im genannten Arbeitsbereich und verlangt owner.

path in jeder Antwort ist ein Pfad auf dem Server-Host.

Neueste zuerst, sortiert nach Verzeichnisnamen. Der Name ist der Zeitstempel, die Reihenfolge hält also auch bei einer Momentaufnahme, deren Manifest sich nicht lesen ließ.

{
"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 fehlt, wenn sich das Manifest nicht lesen ließ. bytes auf der obersten Ebene ist die echte Größe des ganzen Verzeichnisses auf der Platte, nicht die eigene Summe des Manifests. Wer Momentaufnahmen ausdünnt, fragt, was das Verzeichnis kostet, nicht was die Sicherung schreiben wollte.

Jedes workspaces[].path ist genau das, was workspace.import als source_path nimmt. Das ist Absicht: „diesen Arbeitsbereich aus dieser Momentaufnahme wiederherstellen“ ist die Import-Operation, die beiden können also nicht auseinanderlaufen in der Behandlung derselben Datei.

Bevor irgendetwas ersetzt wird, wird die Kandidatendatei schreibgeschützt mit einer kurzlebigen rohen SQLite-Verbindung geöffnet und ihre Zeile workspace_meta gelesen.

Bedingung Ergebnis
Keine lesbare SQLite-Datenbank, oder kein workspace_meta Abgelehnt. Nichts wird angefasst
Aufgezeichnete Arbeitsbereichs-ID ≠ das Ziel Erlaubt, protokolliert. So wird ein Arbeitsbereich dupliziert oder unter einer neuen ID wiederhergestellt
Aufgezeichnete Installations-ID ≠ diese Installation Erlaubt, protokolliert. Die gekoppelten Geräte und Benutzer-IDs gehören zu jener Installation und lösen sich hier nicht auf
Ungültige Arbeitsbereichs-ID, oder die Datei existiert nicht Abgelehnt

Nach der Annahme wird die Datenbank des Ziel-Arbeitsbereichs geschlossen und aus dem Manager genommen, bevor die Datei darüber kopiert wird (eine offene Drift-Verbindung hält die Datei und ihre -wal). Danach wird sie neu geöffnet, damit beforeOpen die FTS- und Sync-Trigger neu anlegt, und die FTS- Indizes werden neu aufgebaut. Die Inhaltstabellen einer importierten Datei sind gefüllt, ihr Index kann aber veraltet sein.

Jede Operation oben spricht in serverseitigen Pfaden, und das ist nur dann eine vollständige Antwort, wenn der Server dein eigener Rechner ist. Diese drei tragen die Bytes, auf derselben mit PSK signierten Bahn, die der Medien-Proxy und der Blob-Speicher nutzen. Der Aufrufer signiert eine Zielzeichenkette mit dem vorab geteilten Schlüssel seines Geräts und übergibt die Signatur als s.

Route Methode Query Signiertes Ziel Erfordert
/backup/workspace GET w, d, s backup-workspace:<workspaceId> Admin des Arbeitsbereichs
/backup/snapshot GET n, d, s backup-snapshot:<name> Eigentümer der Installation
/backup/restore POST w, d, s backup-restore:<workspaceId> Eigentümer des Arbeitsbereichs

w ist eine Arbeitsbereichs-ID, n der Verzeichnisname einer Momentaufnahme, d die Geräte-ID des Aufrufers, s die Signatur. n wird gegen die Sicherungsliste aufgelöst und nicht an die Wurzel der Sicherungen gehängt, die Route lässt sich also nicht auf ein Verzeichnis richten, das die Liste nicht gezeigt hätte.

Status Ursache
400 Ein erforderlicher Query-Parameter fehlt, oder der Upload-Body ist leer
403 Unbekanntes oder widerrufenes Gerät, ungültige Signatur, oder dem Aufrufer fehlt die Rolle
404 Kein Backup-Port auf diesem Host, oder die genannte Momentaufnahme bzw. Arbeitsbereichsdatei existiert nicht
405 /backup/restore mit etwas anderem als POST aufgerufen
413 Der Upload-Body überschreitet 8 GiB, nach deklariertem content-length oder nach den Bytes, die tatsächlich gelesen wurden
500 Export oder Archiv ist serverseitig fehlgeschlagen

Beide Downloads antworten mit Cache-Control: no-store und Accept-Ranges: none. Jede Anfrage erzeugt eine frische Kopie und löscht sie, sobald sie gestreamt ist. Eine gecachte oder fortgesetzte Antwort würde Bytes aus zwei verschiedenen Exporten zu einer Datei zusammenfügen, die gültig aussieht und es nicht ist.

/backup/snapshot liefert <name>.zip. Die Einträge liegen relativ zur Wurzel der Momentaufnahme, das Entpacken stellt also manifest.json + global.db + <workspaceId>/workspace.db wieder her. Die Einträge werden in sortierter Reihenfolge geschrieben, zwei Archive derselben Momentaufnahme sind also dasselbe Archiv.

/backup/restore streamt den Body direkt in eine Staging-Datei, statt ihn zu puffern, begrenzt ihn vor dem Lesen und währenddessen und löscht die bereitgestellte Datei auf jedem Weg nach draußen, auch bei einer Ablehnung.

Eintrag Wert
Momentaufnahmen <dataDir>/backups/<timestamp>/
Geplante Arbeitsbereichssicherungen <dataDir>/backups/workspace-backups/<workspaceId>/<timestamp>/workspace.db
Arbeitsbereichs-Exporte <dataDir>/backups/exports/<workspaceId>-<timestamp>.db
Transfer-Bereitstellung <dataDir>/backups/transfer/ — eine Werkbank, die die Route wegräumt, die hineinschreibt
Archivname der Momentaufnahme <snapshot directory name>.zip
Upload-Obergrenze 8 GiB je POST /backup/restore
Aufbewahrung Momentaufnahmen der Installation: manuelles Löschen. Arbeitsbereichssicherungen: wöchentliche Pipeline, Standard 30 Tage

Die Verzeichnisse exports/, transfer/ und workspace-backups/ sind keine Momentaufnahmen der Installation und fehlen in server.listBackups. Nur das letzte Verzeichnis hat eine geplante Aufbewahrung. Eine Momentaufnahme der Installation zu löschen braucht die ausdrückliche Handlung des Eigentümers der Installation.