Skip to main content

Konfiguration

Einleitung

Dieser Artikel beginnt nach der erfolgreichen Installation: BookStack ist unter HTTPS erreichbar und der erste Administrator kann sich anmelden. Beschrieben werden die Grundeinstellungen, öffentlicher Zugriff, Suchmaschinen, Backups und ein zusätzlich eingebauter RSS-Feed. Zuerst folgt der allgemeine Weg für Hosted Webspace, danach die konkret eingesetzte STRATO-Konfiguration.

Die technische Erstinstallation steht im getrennten Artikel Installation, der Ablauf für Versionswechsel unter Update.

Standardkonfiguration auf Hosted Webspace

Produktionswerte in .env

APP_ENV=production
APP_DEBUG=false
APP_URL=https://klausing.info
APP_LANG=de
APP_TIMEZONE=Europe/Berlin

APP_URL entspricht exakt der öffentlichen HTTPS-Adresse ohne abschließenden Schrägstrich. .env bleibt außerhalb von Git und erhält Dateirechte 600. Der bei der Installation erzeugte APP_KEY wird nicht verändert.

Nach Änderungen an .env werden die Caches geleert:

php artisan config:clear
php artisan cache:clear
php artisan view:clear

Benutzer, Rollen und öffentlicher Zugriff

Das initiale Administratorkonto erhält ein eigenes langes Kennwort und MFA. Für ein öffentlich lesbares, aber nicht öffentlich bearbeitbares Wiki passen diese Grundregeln:

  • Selbstregistrierung ist deaktiviert.
  • Öffentlicher Zugriff ist aktiviert.
  • Die Rolle Public besitzt nur die gewünschten Leserechte.
  • Erstellen, Bearbeiten und Löschen bleiben angemeldeten Benutzern vorbehalten.
  • Nichtöffentliche Bücher und Seiten erhalten ausdrücklich eingeschränkte Rechte.
  • Die Option für sicherere Bildnamen wird aktiviert.

Suchmaschinenzugriff und Sitemap

Für ein öffentliches Wiki wird Crawling in .env freigegeben:

ALLOW_ROBOTS=true

Anschließend:

php artisan config:clear
curl -s https://klausing.info/robots.txt

In der Ausgabe darf nicht global Disallow: / stehen.

Eine Sitemap enthält nur öffentliche, kanonische Inhaltsseiten: Startseite, Regale, Bücher, Kapitel und Seiten. Login, Suche, Benutzerprofile, Bearbeitung, Revisionen, Exporte, Anhänge und API-Endpunkte werden nicht aufgenommen.

Die Sitemap wird bei dieser Installation nicht von BookStack selbst erzeugt. Ein kleines lokales Skript ruft die Weboberfläche anonym ab und nimmt dadurch ausschließlich Inhalte auf, die auch ohne Anmeldung erreichbar sind. Ein API-Zugang ist dafür nicht erforderlich. Die erzeugte Datei liegt unter:

public/sitemap.xml

und ist anschließend hier erreichbar:

https://klausing.info/sitemap.xml

Die Sitemap wird nach größeren Inhaltsänderungen erneut erzeugt und kann zusätzlich in der Google Search Console eingereicht werden.

Backup

Zu einer vollständigen Sicherung gehören die Datenbank und diese instanzspezifischen Dateien:

  • .env
  • public/uploads
  • storage/uploads
  • themes
cd ~/wiki/bookstack

tar -czf "$HOME/bookstack-files-backup.tar.gz" \
  .env public/uploads storage/uploads themes

Die Datenbank wird separat mit mysqldump, phpMyAdmin oder der Backupfunktion des Providers exportiert. Mindestens eine aktuelle Sicherung wird außerhalb des Webspaces aufbewahrt und gelegentlich testweise wiederhergestellt (also offizell natürlich).

Updates

Vor jedem Versionswechsel werden Datenbank und Dateien gesichert. Der komplette Ablauf für normales Hosting und für die eingesetzte STRATO-Umgebung steht in der eigenen Anleitung Update.

Angepasste Konfiguration für STRATO

Caches mit STRATO-PHP verwalten

Alle Artisan-Befehle verwenden das fest gewählte PHP-CLI-Binary:

cd ~/wiki/bookstack

PHP=/opt/RZphp84/bin/php-cli

"$PHP" artisan config:clear
"$PHP" artisan cache:clear
"$PHP" artisan view:clear

Für die produktive Instanz gelten unter anderem:

APP_ENV=production
APP_DEBUG=false
APP_URL=https://klausing.info
APP_LANG=de
APP_TIMEZONE=Europe/Berlin
ALLOW_ROBOTS=true

Sitemap erzeugen

Das Hilfsskript liegt außerhalb des per Git aktualisierten BookStack-Verzeichnisses:

~/wiki/tools/update-sitemap.php

Es verfolgt anonym die öffentlichen BookStack-Links und berücksichtigt nur Startseite, Regale, Bücher, Kapitel und Seiten. Die bestehende Sitemap wird erst ersetzt, nachdem alle gefundenen Seiten erfolgreich abgerufen und eine neue XML-Datei vollständig geschrieben wurde.

<?php

declare(strict_types=1);

const BASE_URL = 'https://klausing.info';
const USER_AGENT = 'klausing.info sitemap generator/1.0';

// Erwarteter Ablageort: ~/wiki/tools/update-sitemap.php
$outputFile = dirname(__DIR__) . '/public/sitemap.xml';
$queue = [BASE_URL . '/', BASE_URL . '/books', BASE_URL . '/shelves'];
$queued = array_fill_keys($queue, true);
$visited = [];
$sitemap = [];

while ($queue !== []) {
    $url = array_shift($queue);
    if (isset($visited[$url])) {
        continue;
    }
    $visited[$url] = true;

    $html = fetchHtml($url);
    if ($html === null) {
        fwrite(STDERR, "Abbruch: $url konnte nicht abgerufen werden.\n");
        exit(1);
    }

    $path = (string) parse_url($url, PHP_URL_PATH);
    $sitemap[BASE_URL . ($path === '' ? '/' : $path)] = true;

    preg_match_all('~<a\b[^>]*\bhref\s*=\s*(["\'])(.*?)\1~is', $html, $matches);
    foreach ($matches[2] as $href) {
        $link = contentUrl(html_entity_decode($href, ENT_QUOTES | ENT_HTML5, 'UTF-8'));
        if ($link !== null && !isset($queued[$link])) {
            $queue[] = $link;
            $queued[$link] = true;
        }
    }
}

$urls = array_keys($sitemap);
sort($urls, SORT_STRING);

$xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
$xml .= "<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n";
foreach ($urls as $url) {
    $url = htmlspecialchars($url, ENT_XML1 | ENT_QUOTES, 'UTF-8');
    $xml .= "  <url><loc>$url</loc></url>\n";
}
$xml .= "</urlset>\n";

$directory = dirname($outputFile);
if (!is_dir($directory) || !is_writable($directory)) {
    fwrite(STDERR, "Abbruch: $directory ist nicht beschreibbar.\n");
    exit(1);
}

$temporaryFile = tempnam($directory, '.sitemap-');
if ($temporaryFile === false || file_put_contents($temporaryFile, $xml, LOCK_EX) === false) {
    $temporaryFile && @unlink($temporaryFile);
    fwrite(STDERR, "Abbruch: Sitemap konnte nicht geschrieben werden.\n");
    exit(1);
}

chmod($temporaryFile, 0644);
if (!rename($temporaryFile, $outputFile)) {
    @unlink($temporaryFile);
    fwrite(STDERR, "Abbruch: Bestehende Sitemap konnte nicht ersetzt werden.\n");
    exit(1);
}

printf("Sitemap mit %d öffentlichen URLs aktualisiert: %s\n", count($urls), $outputFile);

function fetchHtml(string $url): ?string
{
    $curl = curl_init($url);
    curl_setopt_array($curl, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => false,
        CURLOPT_CONNECTTIMEOUT => 10,
        CURLOPT_TIMEOUT => 30,
        CURLOPT_ENCODING => '',
        CURLOPT_USERAGENT => USER_AGENT,
        CURLOPT_HTTPHEADER => ['Accept: text/html'],
    ]);

    $body = curl_exec($curl);
    $status = (int) curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
    $contentType = (string) curl_getinfo($curl, CURLINFO_CONTENT_TYPE);
    curl_close($curl);

    if ($body === false || $status !== 200 || stripos($contentType, 'text/html') === false) {
        fwrite(STDERR, "HTTP-Fehler bei $url: Status $status\n");
        return null;
    }
    return $body;
}

function contentUrl(string $href): ?string
{
    $parts = parse_url(trim($href));
    if ($parts === false || ($parts['scheme'] ?? '') !== 'https'
        || ($parts['host'] ?? '') !== 'klausing.info') {
        return null;
    }

    $path = rtrim($parts['path'] ?? '/', '/') ?: '/';
    $allowed = $path === '/' || $path === '/books' || $path === '/shelves'
        || preg_match('~^/shelves/[^/]+$~', $path)
        || preg_match('~^/books/[^/]+$~', $path)
        || preg_match('~^/books/[^/]+/(?:chapter|page)/[^/]+$~', $path);
    if (!$allowed) {
        return null;
    }

    $url = BASE_URL . $path;
    if (isset($parts['query'])) {
        parse_str($parts['query'], $query);
        if (isset($query['page']) && ctype_digit((string) $query['page'])) {
            $url .= '?page=' . (int) $query['page'];
        }
    }
    return $url;
}

Verzeichnis und Datei werden per SSH angelegt:

mkdir -p ~/wiki/tools
nano ~/wiki/tools/update-sitemap.php
chmod 700 ~/wiki/tools/update-sitemap.php

Nach größeren Seitenänderungen wird die Sitemap mit dem festen STRATO-PHP neu erzeugt:

/opt/RZphp84/bin/php-cli ~/wiki/tools/update-sitemap.php

Ein erfolgreicher Lauf meldet die Anzahl der gefundenen öffentlichen URLs. Die Datei wird hier geschrieben:

~/wiki/public/sitemap.xml

Abschließend werden Sitemap und Freigabe für Suchmaschinen geprüft:

curl -I https://klausing.info/sitemap.xml
curl -s https://klausing.info/robots.txt

Erwartet werden HTTP 200 für die Sitemap und eine robots.txt, die das Crawling öffentlicher Seiten nicht global sperrt. Das optionale Sitemap-Feld lastmod wird bewusst weggelassen, weil die öffentliche Weboberfläche kein verlässliches maschinenlesbares Änderungsdatum für alle Inhaltstypen bereitstellt.

RSS-Feed als Theme-Modul

Auf aktuellen BookStack-Versionen wird der RSS-Hack als Theme-Modul installiert. Damit entfällt die ältere manuelle Variante mit eigenen PHP- und Blade-Dateien:

cd ~/wiki/bookstack

PHP=/opt/RZphp84/bin/php-cli

"$PHP" artisan bookstack:install-module \
  https://www.bookstackapp.com/hack-modules/simple-page-rss-feed.zip

"$PHP" artisan config:clear
"$PHP" artisan cache:clear
"$PHP" artisan view:clear
"$PHP" artisan route:clear

Der Feed ist anschließend öffentlich erreichbar:

curl -I https://klausing.info/rss/pages/new

Der Endpunkt arbeitet mit den Rechten des Gastbenutzers. Deshalb darf die Rolle Public ausschließlich Inhalte sehen, die tatsächlich im Feed erscheinen sollen.

Backup außerhalb von htdocs

Die Datenbank wird im STRATO-Kundenbereich beziehungsweise mit phpMyAdmin exportiert und lokal gespeichert. Die Dateien können zusätzlich zur STRATO eigenen Sicherung noch mal zusätzlich außerhalb des Webroots archiviert werden:

cd ~/wiki

tar -czf "$HOME/bookstack-files-backup.tar.gz" \
  .env public/uploads storage/uploads themes

Das Archiv wird anschließend auf ein anderes System kopiert und nicht dauerhaft auf dem Webspace aufbewahrt.

Updates auf dem STRATO-Webspace

Die Installation wird über den stabilen Branch release aktualisiert. Dabei müssen sämtliche Composer- und Artisan-Befehle wieder ausdrücklich mit dem STRATO-PHP ausgeführt werden. Die vollständige und getestete Befehlsfolge steht unter Update.

Logs und Secret-Dateien

Anwendungsfehler stehen unter:

~/wiki/bookstack/storage/logs/laravel.log
cd ~/wiki/bookstack
tail -n 100 storage/logs/laravel.log

APP_DEBUG bleibt auch bei der Fehlersuche auf der öffentlich erreichbaren Instanz grundsätzlich false. API-Token und andere Zugangsdaten liegen weder im Wiki noch unter public oder im Git-Repository. Externe Secret-Dateien erhalten Rechte 600.

Abschlussprüfung

Nach Konfigurationsänderungen und Updates werden die tatsächlich verwendeten Wege geprüft:

  1. Startseite anonym aufrufen.
  2. Als Administrator anmelden und eine Teständerung speichern.
  3. Öffentliche und eingeschränkte Inhalte mit einem Gastkonto prüfen.
  4. Suche sowie Bild- und Dateiupload testen.
  5. sitemap.xml und RSS-Feed abrufen.
  6. Einen PDF-Export erzeugen.
  7. Versionsanzeige und Logs kontrollieren.
  8. Sicherstellen, dass .env, composer.json und artisan nicht öffentlich ausgeliefert werden.

Damit sind nicht nur die Einstellungen gesetzt, sondern auch die für den Alltag wichtigsten Aufgaben einmal praktisch geprüft.

Zuletzt aktualisiert am 27.08.2026 · 8 Aufrufe