Jak je tenhle blog postavený
Když jsem se rozhodl opustit Tumblr a mít svůj obsah konečně pod vlastní kontrolou, věděl jsem jedno: nechci žádný Wordpress, Ghost ani jiný hotový CMS s databází, pluginy a věčnými aktualizacemi. Chtěl jsem něco tak jednoduchého, aby to šlo i za deset let přečíst a opravit, i kdybych už sám zapomněl jak na to.
Tohle zároveň z tohohle web/logu dělá geekovskou, ne-li nerdovskou záležitost, protože nikdo normální (ať už to znamená cokoliv) nepíše posty v Markdownu na serverovém terminálu...d8-D

Žádná databáze, jen soubory
Každý příspěvek je jeden JSON soubor. Žádné MySQL, SQLite, ORM. Obsah příspěvku je pole bloků — text, obrázek, video, odkaz, seznam — ve stejném formátu, jaký používá Tumblr pro svoje NPF (Neue Post Format). Díky tomu mají historické importy z Tumblru a Twitteru úplně stejnou strukturu jako příspěvky, které píšu ručně dnes. Žádná zvláštní výjimka, žádný "legacy formát".
Fotky a videa leží vedle postu — nikdy nic neodkazuju na cizí server (žádný hotlinking), všechno je stažené a uložené lokálně už při importu.
Build: statický web z JSONů
build_blog.rb je jeden Ruby skript, který projde všechny JSONy a vyrenderuje z nich statické HTML. Žádný Jekyll, Hugo ani Next.js — jen ERB šablony a hrst funkcí, které skládají HTML ručně. Build mimo jiné:
- generuje stránkování po desítkách příspěvků, archivy podle štítků a podle typu obsahu (text/obrázky/video/odkazy),
- staví JSON index pro vyhledávání a RSS/sitemap,
- řeší formátování textu (tučně, kurzíva, odkazy, přeškrtnutí) podle offsetů v poli
formatting— stejný princip jako u Tumblr NPF, - automaticky seskupuje po sobě jdoucí obrázkové bloky do fotogalerie.
Výsledek je čistá hromada statických souborů, kterou umí obsloužit úplně cokoliv.
Psaní: CLI místo administrace v prohlížeči
Zvažoval jsem administrační rozhraní v prohlížeči, ale místní HTTP server, který umí zapisovat soubory, otevírá zbytečná rizika (CSRF z jiné otevřené záložky, path traversal a podobně). Místo toho mám manage_post.rb, který otevře $EDITOR na markdown souboru s YAML frontmatterem (přesně takhle vypadá i tenhle článek, než se zpracuje). **tučně**, *kurzíva*, [odkaz](url) a  se převedou na stejné content bloky, jaké produkují importy z Tumblru a Twitteru — píšu tedy stejným "jazykem", jakým jsou uložené i příspěvky staré přes deset let.
Skript umí add, edit <slug>, delete <slug> i list s filtrem podle typu nebo štítku, a po uložení příspěvku rovnou nabídne rebuild webu.
Publikování z mobilu
Když píšu z iPadu nebo z telefonu přes SSH, nemám po ruce lokální cestu k fotce. Vyřešil jsem to složkou incoming/ na serveru, kam nahraju fotku přes SFTP, a v Markdownu pak stačí napsat jen  — holé jméno souboru se automaticky přeloží na incoming/foto.jpg. Pokud soubor v tu chvíli ještě neexistuje, skript počká a opakovaně kontroluje, dokud fotku nenahraju nebo uložení nezruším. Díky tomu můžu nejdřív napsat celý text a fotky dohnat později.
Mastodon jako vrstva pro komentáře
Nemám (a ani nechci) vlastní systém komentářů. Každý nový příspěvek se automaticky tootne na Mastodon (mastodon_poster.rb), a odkaz na ten toot se uloží k příspěvku jako mastodon_url. Na stránce příspěvku pak JavaScript (comments.js) stáhne přes veřejné Mastodon API odpovědi na ten toot a zobrazí je jako komentáře — se jménem, avatarem a časem. Žádná vlastní databáze komentářů, žádná moderace navíc — to všechno dělá Mastodon. Stejný widget zobrazuje i počet favů, boostů a odpovědí přímo pod textem příspěvku.
Widgety v postranním panelu
V patičce/postranním panelu žijou tři malé JS widgety, každý s jiným zdrojem dat a jiným způsobem načtení:
- Poslední tooty — natahují se za běhu v prohlížeči přímo z Mastodon API.
- Poslední commity — totéž, ale z GitHub API (poslední push eventy mého účtu).
- Poslední Pixelfedy — na rozdíl od těch dvou se stahují už při buildu na serveru (
build_blog.rb), protože Pixelfedí Atom feed nemá CORS hlavičku a z prohlížeče by nešel načíst.
Vyhledávání a další drobnosti
Vyhledávání je čistě klientské: build vygeneruje search-index.json se všemi příspěvky (bez diakritiky, malými písmeny), prohlížeč si ho stáhne jednou a pak nad ním vyhodnocuje dotazy s podporou frází v uvozovkách a vylučování slova pomocí -slovo. Žádný Algolia, žádný server-side search.
Zbytek je už jen běžná výbava: přepínač světlého/tmavého motivu (CSS proměnné + prefers-color-scheme), lightbox na obrázky, sbalovací navigace na mobilu — všechno bez jakéhokoli frameworku, čistý vanilla JS v malých samostatných souborech.
Nasazení: chytrý sync na Surfer
Build maže a znovu vytváří celé public/, takže se nedá spoléhat na čas změny souborů — každý build má nové mtime úplně na všem. deploy-web.sh proto drží mimo public/ manifest SHA-256 hashů všech souborů a při nasazení nahraje na úložiště (Cloudron Surfer, přes jeho Files API) jen to, co se opravdu změnilo. Výsledek: rebuild tří tisíc příspěvků neznamená při každém nasazení upload tří tisíc souborů, ale jen těch pár, co se skutečně změnily.
Odkud se to sem všechno dostalo
Blog neběží od nuly — naimportoval jsem do něj historii ze čtyř Tumblr blogů a z archivu vlastních tweetů z Twitteru/X (2008–2022, jen samostatné příspěvky, žádné odpovědi ani retweety). Oba importy (migrate_tumblr.rb, migrate_twitter.rb) publikovaly posty přes post_writer.rb, který používá i ruční psaní, takže žádný příspěvek nemá "výsadní" postavení podle původu.
Proč takhle
Celé je to sázka na to, že obyčejné soubory, obyčejný Ruby skript a hrstka JavaScriptu bez frameworku vydrží déle a budou se snáz opravovat než cokoliv postavené na cizí platformě nebo cizí databázi. Nic tu neběží jako služba, kterou by mi někdo mohl vypnout nebo zpoplatnit — jen soubory, které mám u sebe a rozumím jim.
A přesně tak jsem to chtěl...d8-D

