Štítekfilozofie

Moje fixtury se mnou souhlasily

Importéry měly dobrou sadu testů. Každý zdroj měl fixtury, fixtury se rozebraly, výsledky se ověřily a všechno procházelo.

Pak jsem importéry pustil přes tři archivy, které jsem nevytvořil já: export z Ghostu se 118 příspěvky a 419 obrázky, strom Huga se 77 soubory a WordPress WXR se 194 položkami. Vedle nich čtvrtý, který je celý můj – vlastní export tohoto enginu, načtený rovnou zpět – protože ten se neptá, jestli kód přežije cizího člověka, ale jestli se obě půlky cesty tam a zpět pořád shodují.

Ty tři cizí ale byly pointa. Fixtury s kódem souhlasily, protože oba jejich konce jsou moje.

Obrázek, který nikdo nestáhl neúspěšně

{{< figure >}} je způsob, jakým Hugo zapisuje obrázek, a odstraňovač Liquidu ho snědl jako značku Liquidu. Oba, které v tom archivu byly, mířily na soubory ležící hned vedle článku v jeho svazku stránky: nic ke stažení, nic, co by mohlo selhat, a byly pryč bez řádku v souhrnu – protože je nikdo nezkusil stáhnout, a tak nikdo neselhal.

To je tvar každého nálezu v tomhle průchodu. Ne pády. Ticho.

Číst dál

Cesta ven se dodává s ním

Engine, který ti nedokáže vrátit tvoje psaní, je hostingová služba s kroky navíc. Importéry vždy mířily jedním směrem – dvaadvacet zdrojů, všechno domů. ./blog.sh export je druhý směr a měl tu být jako první.

Co vyjde ven

Celý archiv jako strom markdownových souborů s hlavičkou YAML, v rozvržení, které čte Jekyll: _posts/2026-05-01-slug.md, _drafts/, stránky v kořeni, média pod assets/<rok>/<slug>/. Staré adresy cestují jako redirect_from, v tvaru, kterému plugin jekyll-redirect-from už rozumí, se sloučením obou druhů – kde příspěvek bydlel na platformě, ze které přišel, a kde bydlel tady před přejmenováním. Namiř na tu složku statický generátor a sestaví se.

./blog.sh export ~/my-blog-export        # všechno, včetně konceptů
./blog.sh export ~/public-copy --no-drafts
./blog.sh export ~/somewhere --dry-run   # spočítá, nezapíše nic

Tento web, spočítaný, ne zapsaný:

Číst dál

Blogovací engine, ze kterého se stal archiv

První příspěvek na tomto webu je o fotce, kterou jsem v roce 2012 dal na Instagram. Odkaz pořád funguje. Fotka ne.

Ten příspěvek byl o jednom archivu – mém – a o jednom pravidle: média se vracejí domů. Tenhle je o tom, co se stalo, když se to pravidlo použilo na každou platformu, ke které jsem našel export.

Seznam je uzavřený

Importuje se teď dvaadvacet zdrojů, ve třech skupinách.

Sociální sítě: Bluesky, Facebook, Instagram, Mastodon, Pixelfed, Threads, Twitter/X.

Blogy a publikační platformy: beehiiv, Blogger, Ghost, Jekyll/Hugo a jakákoli složka s markdownem, LiveJournal, Medium, Movable Type a TypePad, podcasty (jakýkoli feed se zvukem), Squarespace, Substack, Tumblr, Wix, WordPress a jakýkoli feed RSS nebo Atom.

A mrtvé weby: Wayback Machine, pro blogy, jejichž platforma už vůbec neexistuje.

Číst dál

Pojistka, která se sama vypnula

Nasazení tohoto webu má dvě pojistky. Když má sestavení náhle mnohem méně souborů, než je naživo, nebo mnohem méně bajtů, nasazení se zastaví. Existují proto, že rozbité sestavení vypadá pro rsync --delete přesně jako záměrné, a statický web je vždy jen jednu sebejistou synchronizaci od vymazání.

V 1.0 se uměly samy vypnout. Trvale. Potichu. Tady je jak, protože tvar téhle chyby je užitečnější než oprava.

Reference byla špatná věc

Pojistky porovnávaly nové sestavení s manifestem nasazení – záznamem toho, co je na cíli. Rozumné, dokud nahrání neselže. Neúspěšné nahrání nechá manifest mimo skutečnost a pojistka měřící proti záznamu, o kterém ví, že je špatně, by spustila poplach při každém dalším běhu.

Takže tam byla značka: po neúspěšném běhu pojistky odvolat, dokud nepřijde čistý běh a referenci neobnoví.

Číst dál

Čtrnáct let a každý obrázek se vrátil domů

Chybová stránka prohlížeče s hláškou, že IP adresu serveru distilleryimage1.instagram.com se nepodařilo najít
distilleryimage1.instagram.com, o čtrnáct let později

V roce 2012 jsem dal fotku na Instagram. Odkaz pořád funguje. Fotka ne.

Číst dál

Proč jsem to postavil (38 vteřin)

Proč jsem to postavil – 38 vteřin, jeden záběr, nahráno telefonem

Třicet osm vteřin, nahráno telefonem, bez střihu. Psaná verze je níže, kdybys raději četl.

Statických generátorů webů jsou stovky. Stejně jsem napsal další a důvody se vejdou do necelé minuty.

Moje příspěvky bydlely čtrnáct let na velkých platformách. Některé z těch platforem změnily pravidla. Některé prostě zmizely. Trávím celý den v terminálu, takže jsem chtěl i svůj blog psát tam. A malý osobní blog nepotřebuje databázi – soubory stačí.

Takže: příspěvky jsou soubory, web je sestavení a komentáře žijí na Fediverse. To je celá myšlenka.

Žádné gemy (jedna hvězdička)

./blog.sh nepotřebuje žádné gemy. Tady je ta jedna hvězdička u té věty, protože tvrzení bez své výjimky za moc nestojí.

Napřed tvrzení: nula gemů, nula balíčků z npm, žádný Bundler, žádný lockfile, žádná složka node_modules potichu tloustnoucí ve tmě. Ruby 2.7 nebo novější a bash. Naklonuj a běží.

Hvězdička, hned na začátku

Dva volitelné widgety postranního panelu – Pixelfed a obecné RSS – rozebírají XML pomocí rexml. To je výchozí gem: přichází přibalený s každou normální instalací Ruby, takže pro většinu lidí tvrzení platí, jak je napsáno. Ale některé linuxové distribuce Ruby rozdělují do balíčků a výchozí gemy dávají do samostatného.

Takže: když ty dva widgety zapneš na Ruby z distribučního balíčku a sestavení si stěžuje na rexml, jsi ta hvězdička. Dvě cesty ven – nainstalovat balíček své distribuce, něco jako ruby-rexml, nebo gem install rexml. Tak či tak je to jeden balíček, jednou, a jen když ty widgety vůbec používáš.

Číst dál

Proč ne X, a proč Threads čeká

Čtení příspěvku: 0,005 $ za zdroj. Vytvoření příspěvku: 0,015 $ za požadavek – 0,200 $, když nese URL. Bezplatný veřejný přístup ke čtení: žádný.— ceník X API pay-per-use, docs.x.com, ověřeno v červenci 2026

Dvě sítě jsou v. Dvě nejsou – a tady je celá úvaha, protože „rozhodli jsme se proti" není důvod.

./blog.sh oznamuje každý publikovaný příspěvek na Mastodonu nebo Bluesky a odpovědi na to oznámení se stávají komentáři příspěvku. Aby to fungovalo, potřebuje engine od sítě dvě věci: způsob, jak přispět, a způsob, jak prohlížeč přečte veřejné vlákno. Levně, ideálně zdarma, navždy – protože osobní blog je projekt na desítky let s rozpočtem koníčku.

X: ne

Podívej se na ten ceník znovu. Oznámit příspěvek s odkazem stojí skutečné peníze. Načíst vlákno odpovědí – což se děje pokaždé, když kdokoli sroluje ke komentářům – stojí skutečné peníze, za každé čtení, navždy. Na osobním blogu je to předplatné vlastní sekce komentářů, účtované podle zvědavosti tvých čtenářů.

Na cenové rozhodnutí neexistuje inženýrská odpověď. Takže: ne.

Threads: zatím ne

Číst dál

Co ./blog.sh nedělá

Seznam funkcí ti řekne, čím nástroj chce být. Tohle je ten druhý seznam.

Funkce, které tu nejsou – a nepřijdou

  • Více autorů – engine je postavený kolem pracovního postupu jednoho člověka a každá víceautorská funkce zdaňuje jediného autora rolemi, oprávněními a rozhraním pro autorství
  • Pluginy – API pro pluginy je slib držet vnitřnosti navždy stabilní; nástroj takhle malý se má forkovat, ne rozšiřovat
  • Editor WYSIWYG – tvůj $EDITOR sis konfiguroval léta; nebudu s ním soupeřit v textarea
  • Vlastní systém komentářů – úložiště, moderace, spam, GDPR a databáze, to všechno kvůli tomu, znovu postavit to, co Fediverse už dělá veřejně
  • Témata jako stažitelné balíčky – sedm klíčů barev a vlastní banner tě dostanou dál než tržiště témat, za nula tržišť

Nezaškrtnutá políčka, jak je vykreslují vlastní seznamy úkolů enginu. Tak už zůstanou.

Co odmítá parser

Číst dál

Postaveno záměrně pro jeden blog

Tohle není univerzální engine a předstírat opak by ti promarnilo odpoledne.

./blog.sh vznikl kolem přesně jednoho nasazení: jeden autor, jeden archiv, terminál a komentáře na Fediverse. To je obvykle část, kterou projekt schovává na stránce „o nás". Radši s tím začnu, protože je to zároveň vysvětlení všeho vyhraněného na tomto nástroji. Univerzální generátor vyřeší obecný případ a pak nechá tebe nakonfigurovat si cestu zpět k tomu tvému konkrétnímu. Tenhle engine u konkrétního případu začal a nikdy ho neopustil.

Takže tady je poctivá tabulka:

./blog.shHugoJekyllGhostWordPress
Databázežádnážádnážádnánutnánutná
ZávislostiRuby stdlib*jedna binárkaekosystém gemůNode + MySQLPHP + MySQL
KomentářeFediverse, vestavěnévlastnívlastnínativní + členovénativní + pluginy
Nasazení6 backendů, s pojistkamivlastnívlastníje to serverje to server
Více autorůneanoanoanoano
Pluginy / ekosystémne, záměrněobrovskýobrovskýdobrýnepřekonatelný
Velikost komunityobyvatel: 1ohromnávelkávelkátřetina webu
Sestavení 10 000 příspěvkůpomalejšívteřiny, vyhrává snadnopomalé––
Newsletterneneneano, nativnípřes pluginy
Číst dál