Funktionen / Erweitern

Export, Backup, Git-Spiegel

Schemas, Content und Assets als ein ZIP raus und wieder rein. Auto-Backup je Space mit Retention, Git-Spiegel des Contents auch im Datenbankmodus. Deine Daten bleiben in einer Form, die du ohne RustyCMS lesen kannst.

Ein CMS, aus dem du nicht rauskommst, hat dich nicht überzeugt, sondern festgehalten. Bei RustyCMS liegt der Content als JSON5 vor, egal welches Backend darunter arbeitet — und jeder Stand lässt sich als ZIP abholen, per Cron sichern und als Git-Historie lesen. Umzug, Staging-Promotion und Notfall-Restore sind derselbe Mechanismus.

Ein ZIP, alles drin

curl -H "Authorization: Bearer $KEY" \
  "https://cms.example.com/api/export?_environment=prod&scope=types,content&collections=post,page" \
  -o rustycms-prod.zip

scope wählt Schemas, Content und Assets einzeln, collections grenzt auf Collections ein, _environment bestimmt den Space. Im Archiv liegen alle drei Schema-Schichten — Repo-Defaults, abonnierte Type-Packs, Space-Overrides — plus Content, Assets und deren redaktionelle Sidecars. Eine manifest.json hält Version, Zeitpunkt, Scope und Filter fest. Ein Import in eine frische Installation baut daraus ein funktionierendes CMS, ohne dass jemand Packs nachträglich abonnieren muss.

Import prüft, bevor er schreibt

POST /api/import nimmt das ZIP als Multipart entgegen. Jeder Eintrag wird gegen das aufgelöste Schema validiert; mit onValidationError=skip werden Fehler protokolliert und übersprungen, mit abort stoppt der Import. overwrite=true ersetzt vorhandene Einträge, ohne das Flag bleiben sie stehen. Assets werden nach Dateiendung geprüft, ihre Metadaten kommen aus dem Sidecar mit.

Staging nach Prod in zwei Befehlen

curl -u $KEY "https://cms/api/export?_environment=staging&scope=types,content" -o p.zip
curl -u $KEY -F "file=@p.zip" \
  "https://cms/api/import?_environment=prod&overwrite=true&onValidationError=abort"

abort ist bei einer Promotion die richtige Wahl: lieber anhalten als halb importieren. Mit scope=types wanderst du nur den Schema-Stand, ohne Content anzufassen.

Backup läuft von selbst

RUSTYCMS_BACKUP_DIR setzen, fertig. Ein Hintergrundjob schreibt im Intervall (RUSTYCMS_BACKUP_INTERVAL_HOURS, Default 24) je Space ein ZIP, benannt rustycms-backup-<env>-<UTC-Zeitstempel>.zip, sortierbar nach Name. Ältere werden nach jedem Lauf gelöscht (RUSTYCMS_BACKUP_RETENTION_COUNT, Default 14). Die Archive sind byteidentisch mit /api/export — was du im Backup findest, kannst du auch über die API einspielen. Ein rsync oder restic daneben bringt die Kopie außer Haus.

Git-Geschichte auch ohne Dateien

Im Datei-Modus reicht RUSTYCMS_GIT_AUTOCOMMIT=true: nach jedem Write folgt ein Commit, das Repo ist die Historie. Im SQLite- oder Postgres-Modus gibt es keine Dateien, die man committen könnte — dafür gibt es den Spiegel. RUSTYCMS_GIT_MIRROR_DIR setzen, und im Backup-Takt wird derselbe Export in einen Git-Arbeitsbaum entpackt und committet, nur wenn sich etwas geändert hat. Damit funktionieren git log, git diff und git blame über den Content, auch wenn er in Zeilen liegt. Ein bestehendes Repo samt Remote bleibt erhalten, ein git push im Cron kann danebenstehen. Assets sind per Default draußen (RUSTYCMS_GIT_MIRROR_ASSETS=on nimmt sie mit), weil ein Repo mit jeder Version jeder Binärdatei wächst und nie schrumpft.

Was nicht passiert

Der Spiegel ist eine Lesekopie: Wer im Arbeitsbaum editiert, verliert die Änderung beim nächsten Lauf — geschrieben wird über die API. Backups gehen auf ein lokales Verzeichnis, nicht an einen Cloud-Speicher; die Weiterleitung übernimmt dein bestehendes Backup-Werkzeug.

Willst du das an deinem Projekt sehen?

Sag kurz, was du vorhast. Ich zeige die Admin-UI an einer echten Installation und sage ehrlich, ob es passt.

Früher Zugang anfragen