Backup einer lokalen Instanz in einen Sync-Ordner (OneDrive): Bare-Repo unter <ziel>/git, Maschinendaten unter <ziel>/<maschine>/, doctor warnt vor .git im Sync-Ordner
#186
Reference in new issue
Block a user
Ausgearbeitet aus einem
status/incoming-Stub. Der Wortlaut steht im ersten Kommentar.Stand
Erledigt (geschlossen 2026-10-13). Veröffentlicht in
10.0.0-beta.10, Commit9736472f485333cfd08493da3e26c371f89f1f39. Verifiziert durch CI auf diesem Commit: Läufe 675 (Jobsverifyundpwsh) und 676, beide grün. Lokal zuvor grün:pytest(2959 passed, 4 skipped; die neuen Tests auch unterenv -i),docs verify,instructions verify. Ausgeliefert mit Release 10.0.0.Abschlussphase: Die Prüfung auf veraltete Dokumente hat eine Lücke gefunden.
docs/how-to/maintain-the-wiki.mdnennt jetzt das Backup in einen Sync-Ordner und dieWARNeiner gescheiterten Maschinensicherung (Commit3627168, reinedocs/-Prosa ohne Bump, CI-Lauf 677 grün). Alle übrigen Dokumente waren aktuell.Prüfung von Hand auf dem Windows-Arbeitsrechner, 2026-10-13 (Release 10.0.0, GitHub Copilot CLI): Einrichtung, Push, Maschinensicherung,
doctorund das scharf geschaltete Publish-Remote Gate sind geprüft, siehe letztes Akzeptanzkriterium. Die Wiederherstellung nach dem How-to ist nach #222 Abschnitt C verschoben (Betreiberentscheidung 2026-10-13), wo alle offenen Prüfungen von Hand nach 10.0.0 in einem Termin laufen.Abweichungen vom ursprünglichen Body sind unten an Ort und Stelle eingearbeitet und mit (Bau) markiert.
Ausgangslage
Eine berufliche Instanz entsteht gerade, auf einem Windows-Rechner (pwsh 7, nativ). Dort gibt es keinen Git-Server mit passender Privatsphäre. Der einzige sanktionierte Speicher außerhalb des Geräts ist OneDrive. Ein
.git-Verzeichnis in einem Sync-Ordner gilt als problematisch. Gesucht ist ein Backup ohne Server, das auch die Daten dieser Maschine sichert, die nicht in Git stehen.Schon heute unterstützt, ohne Änderung: Eine Instanz ohne Server ist ein gültiger Endzustand.
doctormeldet ein fehlendesoriginnur alsWARN(tools/chemenu/commands/doctor.py,check_git_repo), undpublish --no-pushcommittet lokal. Ein Push gegen ein Bare-Repo unter einem Dateipfad ist Alltag. Die Publish-Tests laufen genau so (tools/chemenu/tests/test_git_publish.py, Fixturerepo).Entscheidungen
Das Arbeitsrepo liegt lokal außerhalb jedes Sync-Ordners. Gesichert wird in ein konfiguriertes Ziel im Sync-Ordner, mit dieser Struktur:
Ins Bare-Repo wird nur beim Push geschrieben, mit genau einem Schreiber. Der Sync-Client sieht fast nur neue, unveränderliche Objektdateien.
Maschinendaten sind eine feste Liste plus
extra. Der Schalter dafür istbackup.machine_data..wikitool-local.jsonENVIRONMENT.mdincoming/*außer.gitkeepquarantine/(bis 9.xmcp-upload/, #213)reports/.wikitool-tools.json,tools/.venv/,tools/.wikitool_session/, Caches,.agents/skills/,.claude/skills/%APPDATA%\chemenu\<instance_id>\secrets.json, darin auch die Sitzungen vonraw login)backup.extra.claude/settings.local.json,.obsidian/Auslöser:
machine_data: truegesetzt, sichertpublishdie Maschinendaten nach einem erfolgreichen Push. (Bau) Einpublish, das nichts zu pushen hatte („Nothing to commit“ ohne lokalen Vorsprung), pusht nicht und sichert daher auch nicht.WARN, undpublishendet mit Exit 0, weil Commit und Push schon geschehen sind.backup runsichert von Hand.incoming/abgelegt wurde, ist erst nach dem nächstenpublishmit Push oderbackup rungesichert (Betreiber: passt).backup initrichtet das Ziel ein. Es legt<ziel>/gitals Bare-Repo an und setztorigin, aber nur, wenn noch keins existiert. Den Eintrag fürremotes.allowed_push_urlsgibt es nur aus und schreibt ihn nie, denn das Feld ist ein Gate. Gibt es keineremotes-Sektion, ist das Gate nicht scharf, undbackup initgibt nichts aus (in der Prüfung von Hand so bestätigt).Verworfen:
Repo samt
.gitdirekt im Sync-Ordner:git gc/repack(„unlink failed“)..git.Ganz ohne Git: Git ist im Stack die lokale Absicherung, nicht der Upload-Weg. Ohne Git fallen weg:
git diff --cached),migrate verify.Außerdem läuft
dist upgradedann ohne die Prüfung auf einen sauberen Arbeitsstand (dist_cmd.py), unddoctormeldetFAIL. Der Wunsch dahinter, dass Microsoft-Werkzeuge die KB lesen, geht über #214 und #215.git bundleals Einzeldatei: kein Remote. Nur falls sich das Bare-Repo in der Praxis nicht bewährt.Konfiguration
Neue Sektion
backupin.wikitool-local.json(config_shapes.BACKUP):target<ziel>/gitoder<ziel>/<maschine>liegen.machine_datafalsepublishund mitbackup run. (Bau) Nur ein echter JSON-Boolean, kein"yes".machine_namesocket.gethostname())[A-Za-z0-9_-]bereinigt (alles andere wird-). (Bau) Danach werden führende und folgende-entfernt. Ist er dann leer oder heißt ergit(Groß-/Kleinschreibung egal), ist das ein Fehler – auch ein ausdrücklich leerer String, der nicht als „Hostname“ gelesen wird.extra[]publishverhält sich wie heute,backup initundbackup runenden mit Exit 1 und nennen die Sektion.publishstoppt mit Exit 1, bevor es etwas committet,backup initundbackup runenden mit Exit 1, unddoctormeldetFAIL. Eine kaputte Sektion wird nie als „kein Backup“ gelesen.target,machine_dataundextrasindgate, weil sie bestimmen, welche Daten die Maschine verlassen.config setlehnt sie mit Exit 42 ab, der Betreiber pflegt sie von Hand (Muster wieremotes/intake).Was gebaut wurde
wikitool backup init(schreibend, idempotent;tools/chemenu/commands/backup_cmd.py, Logik intools/chemenu/backup.py):<ziel>/und<ziel>/gitan, Letzteres als Bare-Repo (git init --bare -b main).<ziel>/gitschon ein Bare-Repo, bleibt es unverändert. (Bau) Ein leeres Verzeichnis dort wird wie ein fehlendes behandelt. Existiert dort etwas anderes, endet der Befehl mit Exit 1 und verändert nichts.origin, setzt esoriginauf<ziel>/git. Zeigt ein bestehendesoriginwoandershin, bleibt es unverändert. Der Befehl endet dann mit Exit 1, nennt beide URLs und überlässt dem Betreiber, ob er umstellt. (Bau) Alle Vorbedingungen werden zuerst geprüft; in diesem Fall wird auch<ziel>/gitnicht angelegt.remotes.allowed_push_urlsund fehlt<ziel>/gitdarin, gibt es den exakten JSON-Eintrag zum Selbst-Eintragen aus (die ganzeremotes-Sektion mit ergänzter URL).wikitool backup run(schreibend, idempotent): spiegelt die Maschinendaten nach<ziel>/<maschine>/und schreibt das Manifest.chemenu-backup.json(Zeitpunkt, Stack-Version, Maschinenname, je Datei relativer Pfad, Größe und sha256). Beimachine_data: falseendet es mit Exit 1. (Bau) Existiert<ziel>nicht als Verzeichnis (Sync-Ordner fehlt), endet es mit Exit 1 und legt nichts an. Eine Datei, deren Größe und sha256 zum vorigen Manifest passen, wird nicht neu geschrieben. Ersetzt wird mit bis zu fünf Versuchen, weil OneDrive eine Datei kurz offen hält.publish: Nach einem erfolgreichen Push und mitmachine_data: trueruft es denselben Code wiebackup runauf. Scheitert er, gibt es eineWARN-Zeile mit dem Grund, Exit 0. Bei--no-pushoder einem gescheiterten Push wird nicht gesichert.doctor:sync-folder:WARN, wenn das Git-Verzeichnis (git rev-parse --absolute-git-dir, Links bzw. Junctions aufgelöst) unter einem der Pfade ausOneDrive,OneDriveCommercialoderOneDriveConsumerliegt (Windows) oder unter~/Library/CloudStorage/(macOS). Unter Windows ohne Groß-/Kleinschreibung verglichen. Der Hinweis nennt das How-to und den typischen Fall, dass „Dokumente“ per Known Folder Move unbemerkt in OneDrive liegt. SonstOK, nieFAIL.backup: (Bau) Ohne SektionOKmit „absent - no backup section in the settings“ –doctorkennt nurOK/WARN/FAIL; einen StatusABSENT, wie ursprünglich hier stand, gibt es nicht, undupload-intake/tasks-providermelden eine fehlende Sektion genauso. Mit Sektion:WARN, wenn<ziel>/gitkein Bare-Repo ist oderoriginnicht dorthin pusht.machine_data: truedas Alter der letzten Sicherung laut Manifest,WARN, wenn es keine gibt.FAILnur bei einer fehlerhaften Sektion.git-remote-WARN (kein Remote) nennt das How-to.docs/how-to/back-up-to-a-synced-folder.md:backupvon Hand anlegen, dannbackup initund den Eintrag inallowed_push_urlsergänzen..wikitool-local.jsonlanden im Sync-Ordner, die Secrets nie.git clone <ziel>/git <neu>, danngit fsck --full.<ziel>/<maschine>/ohne das Manifest in die Instanz kopieren.instructions/bootstrap.md).instructions/setup-instance.mdSchritt 3 (Remote-Frage) bietet „Backup in einen Sync-Ordner“ als Option an und verlinkt das How-to. Die Remote-Frage inINSTALL.mdtut das ebenso.instructions/publish-cycle.mdnennt dieWARNeiner gescheiterten Maschinensicherung: Sie wird dem Betreiber gezeigt und nicht durch einen erneutenpublishbehoben, sondern durchbackup runnach Behebung der Ursache. (Bau) Zusätzlich:docs/explanation/configuration-tiers.mdsagt, dass ein privater Klartext-Wert über das Backup die Maschine verlässt und ein Verweis das verhindert;docs/how-to/maintain-the-wiki.mdnennt das Backup und dieWARN(Abschlussphase).Was eine Sicherung garantieren muss
<ziel>/<maschine>/. Außerhalb davon verändertbackup runnichts, auch nicht<ziel>/gitoder Ordner anderer Maschinen. (Bau) Auch nicht über einen Link im Maschinenordner: ein Link auf dem Weg zu einer Zieldatei bricht ab, und ein Manifest-Eintrag, der den Ordner verlässt (.., Laufwerk, Backslash), ebenso.raw acceptübernommene Quelle ausincoming/. Eine Datei in<ziel>/<maschine>/, die nicht im Manifest steht, bleibt unberührt. (Bau) Das gilt für Pfade, die der Lauf nicht selbst spiegelt; liegt eine fremde Datei genau unter dem Pfad einer gespiegelten Datei, wird sie überschrieben.<ziel>/<maschine>/nicht leer und ohne Manifest, wird abgelehnt (Exit 1, nichts geschrieben). Es wird nie in einen fremden Ordner hineingespiegelt. (Bau) Ein unlesbares Manifest wird ebenso abgelehnt.extra, der nach dem Auflösen außerhalb der Instanz liegt, ist ein Fehler, damit auch überextrakein Secret in den Sync-Ordner kommt. Symlinks innerhalb der gesicherten Verzeichnisse werden nicht verfolgt, sondern übersprungen und gemeldet..git/wird nie gesichert, auch nicht überextra.os.replace). Das Manifest wird zuletzt geschrieben. Ein abgebrochener Lauf hinterlässt nur ganze Dateien und das Manifest des vorigen Laufs.Akzeptanzkriterien
Tests in
tools/chemenu/tests/test_backup.py, soweit nicht anders genannt. Abgehakt ist, was mit9736472veröffentlicht und von den CI-Läufen 675 und 676 bestätigt ist.backup initmit leerem Ziel ist<ziel>/gitein Bare-Repo, undgit remote get-url --push originergibt<ziel>/git. Ein zweiter Aufruf endet mit Exit 0 und ändert nichts (Test).originvorher woandershin, endetbackup initmit Exit 1, undoriginist unverändert (Test). Liegt unter<ziel>/gitetwas, das kein Bare-Repo ist, endet es mit Exit 1, und der Inhalt ist unverändert (Test).remotes.allowed_push_urlsohne<ziel>/git, gibtbackup initden Eintrag aus, und.wikitool-local.jsonist danach byte-gleich (Test).publishpusht gegen<ziel>/gitunter einem Pfad mit Leerzeichen. Danach zeigtmaindort auf den lokalenHEAD. Mitmachine_data: trueenthält<ziel>/<maschine>/danach genau.wikitool-local.json,ENVIRONMENT.md(falls vorhanden), die Dateien ausincoming/außer.gitkeep, dieextra-Pfade und das Manifest (Test).backup runliegt in<ziel>/<maschine>/keine Datei aus der Quarantäne,reports/,.wikitool-tools.jsonoder.git/(Test mit befüllten Verzeichnissen; die Quarantäne heißt seit #213quarantine/).incoming/verschwunden ist, fehlt danach auch im Ziel. Eine fremde Datei in<ziel>/<maschine>/, die nicht im Manifest steht, ist danach byte-gleich vorhanden (Test).<ziel>/<maschine>/nicht leer und ohne Manifest, endetbackup runmit Exit 1, und der Ordner ist unverändert (Test).extra-Eintrag, der außerhalb der Instanz auflöst (..oder ein Symlink nach außen), führt zu Exit 1, bevor etwas geschrieben wird (Test). Ein Symlink inincoming/wird nicht verfolgt und erscheint nicht im Ziel (Test).machine_name, das nach dem Bereinigen leer ist odergit/GITheißt, ist ein Fehler der Sektion (Test).publishmit Exit 0 und einerWARN-Zeile, und der Push ist erfolgt (Test). Bei--no-pushentsteht kein<ziel>/<maschine>/(Test).backup-Sektion endetpublishmit Exit 1, ohne dass ein Commit entsteht (Test).doctor:sync-folderistWARNunter einem Pfad ausOneDrive/OneDriveCommercial/OneDriveConsumeroder~/Library/CloudStorage/, auch wenn das Repo über einen Symlink dorthin erreicht wird, und sonstOK.backupmeldetOK(absent),WARN(fehlendes<ziel>/git, abweichendesorigin, keine Maschinensicherung) undFAIL(fehlerhafte Sektion) wie beschrieben.doctoraußer beiFAIL(Tests mit auftmp_pathgesetzten Umgebungsvariablen; die drei OneDrive-Variablen löscht jetzt auch die hermetische Fixture inconftest.py).docs/how-to/README.mdund deckt Punkt 5 vollständig ab.setup-instance.md,INSTALL.md,publish-cycle.mdund diegit-remote-WARN verweisen darauf wie in Punkt 6.C:\OneDrive - <Firma>\Chemenu, OneDrive außerhalb des Benutzerprofils):backup initlegt<ziel>\gitals Bare-Repo an und setztorigin. Ohneremotes-Sektion gibt es nichts aus (Gate nicht scharf;doctor: „Gate not armed“).publishpusht die bestehende lokale Historie nach<ziel>\gitund spiegelt 5 Dateien nach<ziel>\<maschine>\;git fsck --fullauf dem Bare-Repo ohne Fehler.doctormeldetsync-folderOK (ArbeitsbaumC:\Repositories\…außerhalb jedes Sync-Ordners),backupOK mit Alter der letzten Sicherung,git-remotemit dem Pfad im Sync-Ordner.remotes.allowed_push_urls(<ziel>\git) ist das Gate scharf, undpublishläuft weiter durch.Eine Wiederherstellung nach dem How-to ergibt eine Instanz, bei derNach #222 Abschnitt C verschoben (Betreiberentscheidung 2026-10-13).doctorohneFAILdurchläuft.'C:\OneDrive - <Firma>\…') und fragt nach\OneDrive; der Befehl selbst war richtig und lief.pytest,docs verify,instructions verifygrün (lokal und in CI-Lauf 675). Die generierten Referenzen (docs contract,docs reference) sind neu erzeugt, und die Sektionbackupsteht indocs/reference/configuration.md.Versionsteil
MINOR. Es sind neue Kommandos, eine neue Sektion und neue
doctor-Prüfungen. Ohnebackup-Sektion verhält sich alles wie bisher, also drop-in in beide Richtungen. Eine ältere Version, die einebackup-Sektion vorfindet, meldet sie indoctorals unbekannte Sektion (WARN) und läuft weiter. Der laufende Kandidat stand schon auf10.0.0; der MINOR-Bump hob ihn nicht weiter an und ergab10.0.0-beta.10.Betroffene Dateien und Flächen
tools/chemenu/backup.py(Init, Spiegel, Manifest, Sync-Ordner-Erkennung),tools/chemenu/commands/backup_cmd.py(backup init/backup runmitcli_contract-Einträgen, Gruppe „Git“), Registrierung intools/chemenu/cli.py.config_shapes.BACKUPintools/chemenu/config_shapes.py, Regionreference-config-backupintools/chemenu/reference_doc.py.tools/chemenu/commands/git_publish.py: Validierung der Sektion vor dem Commit, Schritt nach dem Push,cli_contract-Notizen vonpublish.tools/chemenu/commands/doctor.py: die Prüfungensync-folderundbackup, der Hinweis beigit-remote, dercli_contract-Eintrag.tools/chemenu/tests/test_backup.py(neu),test_config_shapes.py,conftest.py(OneDrive-Variablen).tools/CONTRACT.md,docs/reference/commands/git.md,instance-health.md,README.md, die Region indocs/reference/configuration.mddocs/how-to/back-up-to-a-synced-folder.md(neu),docs/how-to/README.md,docs/how-to/maintain-the-wiki.md,docs/reference/configuration.md(Prosa),docs/reference/commands/git.md(Kopfsatz),docs/explanation/configuration-tiers.md,instructions/setup-instance.md(Schritt 3),instructions/publish-cycle.md,INSTALL.md(Remote-Frage),tools/README.mdCHANGES.mdüberversion bumpNicht in diesem Paket
backup restore: Die Wiederherstellung steht vorerst als Handgriffe im How-to.Kette
Kette „OneDrive/Microsoft 365“
Ausgearbeitet aus dem Stub. Ursprünglicher Text, wörtlich:
Changelog: Body neu geschrieben als Bau-Spezifikation für das Backup. Ansatz A gewählt (lokales Repo, Bare-Repo im OneDrive als
origin), plus einedoctor-Warnung für.gitim Sync-Ordner. „Ganz ohne Git“ verworfen, Begründung steht im Body. Ideen aus derselben Diskussion als eigene Pakete abgespalten:Kette „OneDrive/Microsoft 365“: #186 → #213 → #214.
status/incomingentfernt, Labels gesetzt (area/workflow,kind/build,prio/planned,size/M).Chemenu Repo ohne Upload aber mit OneDrive Backupto Backup einer lokalen Instanz in einen Sync-Ordner (OneDrive): Bare-Repo als `origin`, `doctor` warnt vor `.git` im Sync-OrdnerBackup einer lokalen Instanz in einen Sync-Ordner (OneDrive): Bare-Repo als `origin`, `doctor` warnt vor `.git` im Sync-Ordnerto Backup einer lokalen Instanz in einen Sync-Ordner (OneDrive): Bare-Repo unter `<ziel>/git`, Maschinendaten unter `<ziel>/<maschine>/`, `doctor` warnt vor `.git` im Sync-OrdnerChangelog: Das Backup sichert jetzt auch Maschinendaten.
<ziel>/git(Bare-Repo,origin) und<ziel>/<maschine>/(Spiegel plus Manifest).backup:target,machine_data,machine_name,extra.backup initundbackup run.publishsichert nach dem Push, eine gescheiterte Sicherung gibt nur eineWARN.doctor-Prüfung:backup.Entscheidungen des Betreibers:
mcp-upload/wird nicht gesichert.backup initsetztoriginnur, wenn noch keins existiert..wikitool-local.jsondürfen ins OneDrive.Garantien gegen Datenverlust und gegen Secrets im Sync-Ordner sowie die Akzeptanzkriterien sind entsprechend erweitert. Versionsteil bleibt MINOR,
size/Mbleibt (am oberen Rand).Changelog: Gebaut und veröffentlicht in
10.0.0-beta.10(Commit9736472), CI-Läufe 675 und 676 grün. Neuer Abschnitt „Stand“. Alle Kriterien außer der Windows-Prüfung von Hand abgehakt. Abweichungen als (Bau) eingearbeitet:doctormeldet eine fehlende Sektion alsOK„absent“ stattABSENT(diesen Status gibt es nicht);backup initnimmt ein leeres<ziel>/gitan und legt bei fremdemorigingar nichts an;machine_nameverliert zusätzlich führende und folgende-;backup runverlangt ein vorhandenes Ziel und schreibt unveränderte Dateien nicht neu; einpublishohne Push sichert nicht; eine fremde Datei unter einem gespiegelten Pfad wird überschrieben. Zusätzlich gepflegt:docs/explanation/configuration-tiers.md,tools/README.md. Dateiliste auf den tatsächlichen Stand gebracht.Changelog: Abschlussphase begonnen, nicht geschlossen. Prüfung auf veraltete Dokumente:
docs/how-to/maintain-the-wiki.mdnachgezogen (Commit3627168, CI-Lauf 677 grün), sonst nichts veraltet. Betreiberentscheidung: Das Issue bleibt offen, bis die Windows-Prüfung von Hand erfolgt ist; danach/stack-closeerneut.Changelog: Prüfung von Hand am 2026-10-13 (Arbeitsinstanz, Release 10.0.0, Copilot CLI) im letzten Kriterium eingetragen. Geprüft sind:
backup init, Push und Maschinensicherung gegen ein Ziel im OneDrive,git fsck,doctor(sync-folder,backup) und das von Hand scharf geschaltete Publish-Remote Gate. Offen bleibt nur die Wiederherstellung. Entscheidung 4 nennt jetzt, dassbackup initohneremotes-Sektion nichts ausgibt. Die Quarantäne heißt im Bodyquarantine/(#213). Neuer Verweis auf #216 (Export- und Eingangsordner als Geschwister im Backup-Ziel).Changelog: Geschlossen auf Entscheidung des Betreibers (2026-10-13). Die Wiederherstellung, der letzte offene Punkt der Prüfung von Hand, ist durchgestrichen und nach #222 Abschnitt C verschoben. Alles andere ist auf Windows mit OneDrive bestätigt. Changelog-Zeile: Backup einer Instanz ohne Git-Server in einen Sync-Ordner (
backup init/backup run, Maschinendaten nachpublish), mit 10.0.0 ausgeliefert. Kette „OneDrive/Microsoft 365“ ist damit vollständig geschlossen (#186, #213, #214).