Dieses Blog wird nicht mit WordPress, Ghost oder einem anderen CMS betrieben – sondern mit einem einzigen Python-Script namens generate.py. Es liest Textdateien aus einem Ordner, verarbeitet Bilder, hebt Code farbig hervor und erzeugt daraus fertige HTML-Seiten. Keine Datenbank, kein Server, keine Abhängigkeiten zur Laufzeit.
Warum so?
Statische HTML-Dateien haben ein paar angenehme Eigenschaften: Sie sind blitzschnell, lassen sich auf jedem Webserver oder bei GitHub Pages / Gitea Pages hosten, und sie funktionieren auch noch in zehn Jahren ohne Updates. Und weil ich die volle Kontrolle über das System haben wollte, war ein eigenes Script die naheliegendste Lösung.
Ordnerstruktur
Das Projekt sieht so aus:
blog/
├── src/
│ └── generate.py ← Das Generator-Script
├── build.ps1 ← Vollständiger Build (generate + git push)
├── deploy-html.ps1 ← Schnell-Deploy direkt auf den Server (kein git)
├── docker/ ← Docker-Stack-Konfiguration
├── posts/ ← Hier kommen die eigenen Posts rein
│ └── mein-post/
│ ├── post.md ← Pflicht: Text des Posts
│ ├── foto.jpg ← Optional: Bilder, Videos, Code, Firmware …
│ └── demo.html
└── output/ ← Fertige HTML-Dateien (wird generiert, nicht in git)
├── index.html
└── posts/
└── mein-post/
├── index.html
└── foto.jpg
Jeder Post bekommt einen eigenen Unterordner in posts/. Der Ordnername wird zur URL des Posts (/posts/mein-post/). Alle Dateien die im Post referenziert werden – Bilder, Code-Dateien, Videos, Firmware – müssen in diesem Ordner liegen.
Das Post-Format
Eine post.md besteht aus zwei Teilen, getrennt durch ---:
Titel: Mein erster ESP32-Test
Datum: 2024-03-15
Tags: esp32, anfänger, blink
Beschreibung: Kurzer Teaser für die Übersichtsseite (optional).
---
Hier beginnt der eigentliche Text in **Markdown**.
Der Header kennt vier Felder – Titel und Datum sind am wichtigsten. Das Datum-Feld bestimmt die Reihenfolge auf der Startseite (neueste zuerst). Ohne Datum wird alphabetisch nach Ordnernamen sortiert, weshalb sich ein Präfix wie 01-blink, 02-wifi anbietet.
Textformatierung: Markdown
Der Fließtext wird als Markdown geschrieben. Die wichtigsten Formatierungen:
| Eingabe | Ergebnis |
|---|---|
**fett** |
fett |
*kursiv* |
kursiv |
~~durchgestrichen~~ |
~~durchgestrichen~~ |
`inline-code` |
inline-code |
[Linktext](https://url) |
Hyperlink |
## Überschrift |
Abschnittstitel |
### Kleinere Überschrift |
Unterabschnitt |
- Punkt |
Aufzählungsliste |
1. Punkt |
Nummerierte Liste |
> Zitat |
Blockquote |
--- |
Trennlinie |
Tabellen funktionieren ebenfalls:
| Spalte 1 | Spalte 2 |
|----------|----------|
| Wert A | Wert B |
Spezial-Tags für Medien
Neben dem normalen Markdown gibt es eigene Tags für eingebettete Inhalte. Sie müssen jeweils allein auf einer Zeile stehen.
Bilder
⚠ Bild nicht gefunden: dateiname.jpg
Das Bild wird automatisch auf maximal 800×500 Pixel verkleinert (Thumbnail). Ein Klick auf das Bild öffnet das Original in voller Größe. EXIF-Rotation (z.B. von Smartphone-Fotos) wird automatisch korrigiert.
Quellcode
⚠ Datei nicht gefunden: blink.cpp
Die Datei wird mit Syntax-Highlighting dargestellt. Unterstützt werden .cpp, .c, .h, .ino (Arduino), .py, .yaml, .json, .sh und viele weitere Formate. Längere Dateien werden auf 20 Zeilen eingeklappt und können per Klick vollständig angezeigt werden. Oben rechts im Code-Block gibt es immer einen Download-Button.
Videos
⚠ Video nicht gefunden: demo.mp4
Das Video wird direkt im Browser abgespielt (HTML5-Player). MP4 mit H.264-Codec hat die beste Kompatibilität.
Datei-Download
⚠ Datei nicht gefunden: firmware.bin
Zeigt eine Download-Box mit Dateiname und -größe. Sinnvoll für kompilierte Firmware, Konfigurationsdateien oder Schaltpläne als PDF.
HTML-Demo einbetten
⚠ Datei nicht gefunden: interaktiv.html
Bettet eine HTML-Datei in einem Vorschau-Rahmen ein. Ein Klick öffnet die Seite in einem Vollbild-Popup. Nützlich für interaktive Demos, Visualisierungen oder kleine Web-Tools die zum Projekt gehören.
Blog generieren
Im Projektordner einfach:
python src/generate.py
Das Script liest alle Unterordner in posts/, verarbeitet die Inhalte und schreibt das Ergebnis nach output/. Fehlt eine referenzierte Datei, gibt es eine Fehlermeldung im generierten HTML – das Script bricht nicht ab.
In VS Code geht das noch schneller: Strg+Shift+B zeigt einen Picker mit drei Optionen:
- HTML generieren + live deployen – generiert lokal und überträgt per rsync direkt auf den Server (schnell, kein git nötig)
- Nur HTML neu generieren – generiert nur lokal in
output/(reine Vorschau) - Kompletter Rebuild – generiert, committet und pusht nach Gitea, Portainer deployt automatisch
Veröffentlichen
Der output/-Ordner ist in .gitignore – er wird nicht per git veröffentlicht. Stattdessen läuft der Blog als Docker-Stack auf einer DiskStation:
git push → Gitea-Webhook → Portainer → blog-builder-Container neu starten
→ generate.py läuft erneut
→ nginx serviert die fertigen HTML-Dateien
Nach einem Strg+Shift+B (Option 3) ist der neue Stand also wenige Sekunden später online – ohne manuelles Eingreifen.
Abhängigkeiten
pip install Pillow pygments markdown
Das war’s. Kein Node.js, kein Ruby, kein Build-System.
Update März 2026: Theme-Switcher, Upload-System, Windows-Support und mehr –
siehe Neue Funktionen.