f451 1.3: Import aus BookStack und Export zurück

f451 1.3: Import aus BookStack und Export zurück

Wer mit seiner Dokumentation zu f451 wechseln will, musste sie bisher von Hand übertragen. Version 1.3.0 übernimmt Regale, Bücher und einzelne Seiten aus BookStack und schreibt Spaces auf Wunsch auch wieder dorthin zurück. Beides geht über die Kommandozeile oder direkt aus dem Browser.

Der Export gehört zum Konzept. Die Seiten in f451 sind gewöhnliches Markdown in Git, und mit 1.3 lässt sich ein Space auch als BookStack-Buch weitergeben, ohne Seiten von Hand zu kopieren.

Der Import legt Entwürfe mit Review an

Standardmäßig schreibt ein Import nicht in die veröffentlichte Fassung. Für jede Seite aus BookStack legt f451 einen Entwurf an und öffnet ein Review, technisch einen Pull Request pro Seite. Im Space sichtbar wird eine Seite erst, wenn jemand ihr Review freigibt. Wer der Quelle vertraut, gibt beim Start --release mit, dann gibt der Import die Reviews gleich selbst frei.

Für die Kommandozeile braucht es zwei Tokens. Ein persönliches Zugriffstoken von f451 mit Schreibrecht, dessen Besitzer ein verbundenes Git-Konto hat, denn alle Änderungen laufen unter diesem Konto. Dazu ein API-Token von BookStack, das die zu übernehmenden Inhalte lesen darf.

export F451_URL=https://wiki.example.org
export F451_TOKEN=f451_pat_...
export BOOKSTACK_URL=https://bookstack.example.org
export BOOKSTACK_TOKEN_ID=...
export BOOKSTACK_TOKEN_SECRET=...

pnpm --filter @f451/import-cli start -- bookstack --book betriebshandbuch --space betrieb

Statt --book geht auch --shelf für ein ganzes Regal oder --page für eine einzelne Seite. Mit --parent landet der importierte Baum unter einer bestehenden Seite, mit --dry-run schreibt der Import nichts nach f451 und legt das erzeugte Markdown nur zur Ansicht ab.

Vom Regal bis zur Seite

BookStack gliedert Inhalte in Regal, Buch, Kapitel und Seite, f451 kennt nur Seiten mit Unterseiten. Der Import macht deshalb aus allem oberhalb der Seite eine Ordnerseite.

BookStackf451
RegalOrdnerseite mit den Büchern darunter
BuchOrdnerseite
KapitelOrdnerseite
SeiteSeite

BookStack speichert Seiten als HTML, f451 als Markdown. Der Import übersetzt, was eine Entsprechung hat. Tags werden zu Seiten-Tags, Callouts zu GFM-Hinweisen (info zu [!NOTE], warning zu [!WARNING] und so weiter), aufklappbare <details>-Blöcke zu einem Hinweis mit fett gesetzter Überschrift. Links zwischen importierten Seiten werden zu Wikilinks, Bilder lädt der Import herunter und legt sie als Medien der Seite ab.

Diagramme aus draw.io kommen als bearbeitbare .drawio.svg herüber. Dafür braucht der Import Docker oder Podman, um die Diagramme zu rendern. Ohne einen von beiden übernimmt er das PNG aus BookStack und vermerkt das im Bericht.

Im Frontmatter jeder importierten Seite steht ein Herkunftsfeld.

source:
  type: bookstack
  id: "42"
  url: https://bookstack.example.org/books/betriebshandbuch/page/einleitung

Daran erkennt ein späterer Import seine eigenen Seiten. Mit --update aktualisiert er sie über ein neues Review, statt sie doppelt anzulegen. Ersetzt werden dabei nur Text und Tags. Was f451 sonst im Frontmatter führt, also ID, Vertraulichkeitsstufe, Metadaten und Version, bleibt stehen. Eine Seite mit offenem Entwurf überspringt der Import und meldet sie, statt den Entwurf zu überschreiben.

Seiten löscht der Import nicht, auch keine, die in BookStack inzwischen fehlen.

Der Bericht zählt, was verloren geht

Nicht alles aus BookStack hat eine Entsprechung in Markdown. Tabellenzellen mit colspan oder rowspan löst der Import in leere Zellen auf, Listen und Code in Tabellenzellen drückt er auf eine Zeile zusammen. HTML ohne Markdown-Gegenstück fällt weg. Medien über 10 MiB überspringt er.

Verloren geht also etwas, aber der Bericht nennt es. Jeder Lauf schreibt einen Bericht, import-report.md, mit angelegten, aktualisierten, übersprungenen und fehlgeschlagenen Seiten samt Grund. Darin stehen auch übersprungene Medien, Diagramme, die PNG geblieben sind, und jede Stelle, an der Inhalt ohne Markdown-Entsprechung verloren ging. Wer vor dem eigentlichen Umzug einen Probelauf mit --dry-run macht, sieht im Bericht vorab, wo er nacharbeiten muss. Am meisten Handarbeit machen Tabellen mit verbundenen Zellen.

Der Weg zurück nach BookStack

Der Export geht in die Gegenrichtung und braucht auf der f451-Seite nur ein Token mit Leserecht, denn er schreibt nie nach f451. Das BookStack-Token muss dafür Inhalte anlegen und ändern dürfen.

pnpm --filter @f451/import-cli start -- export bookstack --space betrieb

Aus dem Space wird ein Buch, mit --page nur ein Teilbaum. Weil BookStack nicht beliebig tief verschachtelt, glättet der Export tiefere Ebenen.

f451BookStack
Wurzel des ExportsBuch
Seite mit UnterseitenKapitel, ihr eigener Inhalt wird die erste Seite des Kapitels
Seite ohne UnterseitenSeite
Tiefere Ebenenim Kapitel, mit dem Pfad als Namenspräfix (B / C)

Die Seiten rendert der Export mit derselben Pipeline wie die Leseansicht von f451. GFM-Hinweise werden wieder zu BookStack-Callouts. Links auf mitexportierte Seiten zeigen nach BookStack, Links auf andere Seiten des Space zurück nach f451.

Bilder landen in der Galerie von BookStack, identische Bilder nur einmal, erkannt an einem Hash des Inhalts. SVG nimmt BookStack nicht an, der Export wandelt deshalb jedes SVG in PNG. Eine Ausnahme sind draw.io-Diagramme. Sie werden zu BookStack-Zeichnungen, die den Quelltext des Diagramms mitbringen und sich im draw.io-Editor von BookStack weiter bearbeiten lassen.

Jedes exportierte Buch, Kapitel und jede Seite bekommt die Tags f451-id und f451-space. Ein zweiter Export findet sie wieder und aktualisiert an Ort und Stelle, Umbenennungen eingeschlossen. Seiten, die in f451 nicht mehr existieren, listet der Bericht als veraltet. Gelöscht wird auch hier nichts. Wer ein exportiertes Buch wieder nach f451 importiert, bekommt keine Dubletten, weil der Import das Tag f451-id als Herkunft erkennt.

Import und Export aus dem Browser

Wer keine Kommandozeile öffnen will, startet beides aus der Seitenleiste des Space unter „Import und Export“. Dort wählt man die BookStack-Instanz, gibt Token-ID und Secret ein, lädt die Bücher und wählt Buch, Regal oder einzelne Seite als Quelle. Die Optionen der Kommandozeile stehen als Schalter darunter, von „Bereits importierte Seiten aktualisieren“ bis zum Probelauf. Die Seite zeigt den Fortschritt, den Verlauf pro Seite und am Ende den Bericht zum Herunterladen. Der Lauf geht im Hintergrund weiter, auch wenn man die Seite schließt.

Die Rechte sind dieselben wie überall in f451. Importieren darf, wer im Ziel-Space schreiben darf. Exportieren darf, wer ihn lesen darf. Der Job läuft unter dem Konto der Person, die ihn startet, mit einem kurzlebigen Token, das f451 am Ende widerruft. Review, Vertraulichkeitsstufen und alle übrigen Regeln gelten also genau wie beim Arbeiten von Hand.

Das BookStack-Token gibt man für jeden Lauf neu ein. f451 hält es nur für die Dauer des Jobs im Speicher und schreibt es weder in eine Datenbank noch in ein Log oder den Bericht. Pro Nutzer läuft ein Job, pro API-Prozess höchstens drei. Ein laufender Job lässt sich abbrechen, allerdings immer erst zwischen zwei Seiten. Angelegte, aber noch leere Entwürfe räumt der Abbruch weg, und ein späterer Lauf mit Aktualisieren macht ohne Dubletten weiter.

Nur BookStack-Instanzen, die der Betreiber freigibt

Im Browser verbindet sich nicht der eigene Rechner mit BookStack, sondern der f451-Server. Dürfte dort jeder eine beliebige Adresse eintragen, ließe sich der Server als Sprungbrett missbrauchen, um Rechner im internen Netz abzufragen, die von außen gar nicht erreichbar sind. Dieser Angriff heißt Server-Side Request Forgery (SSRF).

f451 verhindert das mit einer Freigabeliste. Import und Export im Browser sind ausgeschaltet, bis der Betreiber die erlaubten BookStack-Instanzen in einer Umgebungsvariable der API einträgt:

F451_IMPORT_BOOKSTACK_URLS=https://bookstack.example.org,https://doku.intern.example.org

Ist die Variable leer oder nicht gesetzt, bleibt der Eintrag „Import und Export“ in der Seitenleiste verborgen. Ist sie gesetzt, wählt man im Formular nur noch aus diesen Instanzen. Die API prüft die Adresse bei jeder Anfrage erneut, schon beim Laden der Bücherliste, und lehnt jede andere ab. Erlaubt sind nur einfache http- oder https-Adressen ohne Zugangsdaten, Parameter oder Anker. Ein ungültiger Eintrag lässt die API gar nicht erst starten, die Fehlermeldung nennt ihn, ein Passwort darin aber nur maskiert.

Die Liste schützt auch gegen Umwege über den Inhalt. Eine BookStack-Seite kann den Import nicht dazu bringen, Bilder oder andere Dateien von Adressen außerhalb der freigegebenen Instanz abzurufen, und Weiterleitungen auf einen anderen Host folgt er nicht. Diese Lücken hat ein eigenes Review vor dem Release gefunden, die behobenen Funde führt der Changelog einzeln auf.

Weitere Fehler aus diesem Review betrafen die Robustheit. Eine Seite, die scheitert, bevor ihr Inhalt gespeichert ist, hinterlässt keinen leeren Entwurf. Drosselt BookStack die Anfragen (HTTP 429), wartet der Import ab, statt die Seite aufzugeben.

Viele Reviews auf einmal freigeben

Ein Import ohne --release hinterlässt so viele offene Reviews wie Seiten. Damit niemand sie einzeln öffnen muss, gibt es mit 1.3 eine Review-Übersicht pro Space unter /wiki/<space>/reviews. Sie listet alle offenen Reviews und gibt mehrere auf einmal frei, etwa alle Seiten eines Imports.

  • Mermaid-Diagramme. Ein Codeblock mit der Sprache mermaid erscheint in der Leseansicht als Diagramm und im Editor als Live-Vorschau. Im Markdown bleibt er ein gewöhnlicher Codeblock.
  • Backlinks. Die Info-Leiste listet jede Seite, die auf die aktuelle verlinkt, mit den Textstellen rund um den Link.
  • Linkvorschau. Fährt die Maus über einen internen Link, oder erreicht man ihn mit der Tastatur, zeigt f451 Titel und ersten Absatz des Ziels.

Löschen läuft über ein Review

Das Löschen einer Seite geht jetzt ebenfalls über ein Review und nimmt den ganzen Ordner mit, also Unterseiten, Medien und Releases. Bisher blieben Unterseiten einer gelöschten Seite verwaist zurück. Über die HTTP-API antwortet DELETE /api/pages/:id deshalb nicht mehr mit sofortigem Löschen, sondern öffnet das Review. MCP-Werkzeuge, Seitenformat und Konfiguration bleiben unverändert.

Betreiber müssen nach dem Update die Datenbankmigration ausführen, sonst schlagen Seitenaufrufe fehl. Danach einmal POST /admin/reindex, damit Backlinks ihre Textstellen bekommen. Wer Import und Export im Browser anbieten will, setzt zusätzlich F451_IMPORT_BOOKSTACK_URLS.

Quellen