PHP front-end pro GitHub wiki — nebo pro markdown uvnitř libovolného GitHub repozitáře. Naklonuje git repozitář, každou stránku jednou vykreslí, zaindexuje ji pro vyhledávání a servíruje výsledek jako web, který působí staticky, v duchu mezinárodního typografického stylu — ve světlém i tmavém režimu.
Je stavěný tak, aby se forkoval pro každý repozitář: zkopíruj složku, uprav
config.php, spusť synchronizaci. Na nic dalšího sahat nemusíš.
Co umí
| Crawluje | git clone/pull wiki (<repo>.wiki.git) nebo přímo repozitáře |
| Vykresluje | CommonMark + GFM (tabulky, úkolové seznamy, přeškrtnutí, autolinky, poznámky pod čarou) a diagramy ```mermaid |
| Rozumí wiki | navigace z _Sidebar.md, _Footer.md, [[Gollum odkazy]], relativní odkazy na stránky, holé <img src="diagram.svg">, GitHub upozornění (> [!NOTE]) |
| Indexuje | sekce po nadpisech → JSON index na disku (nebo MySQL FULLTEXT) |
| Servíruje | stránky, úvod, obsah, stránku hledání, paletu příkazů ⌘K, JSON API pro hledání, sitemap, média |
| Prochází soubory | /files — vlastní strom repozitáře: složky, velikosti, data, prohlížeč souborů s označenou syntaxí a čísly řádků, náhledy obrázků, odkazy na GitHub blob/raw |
| Zná git | každá stránka ukazuje skutečné datum poslední úpravy a autora z historie wiki |
HTML stránek se vykresluje při synchronizaci, takže zobrazení stránky je jeden řádek z indexu a nula parsování markdownu.
Fork
cp -r wikit wiki.myproject.com # wikit/ = rozbalené stažení
cd wiki.myproject.com
cp config.local.example.php config.local.php
config.php— jediný soubor, který musíš upravit:site.*— název, podtitul, popis, locale, časové pásmorepo.*—owner,name,mode(wikineborepo), volitelněpath,branch,home_pagebrand.*— jedna barvaaccentřídí celou paletu;logo_iconje libovolný název z Iconify, nebo nasměrujlogo_svgna vlastní soubortype.*— rodiny písem + specifikace Google Fonts (nebo''jen pro lokální písma)theme.*— výchozí režim, zrnitost, zaoblení rohůfeatures.*— zapíná/vypíná hledání, obsah stránky, zpětné odkazy, předchozí/další, marquee, sitemap…site.language— vlang/jsoucs,enade;i18nv config.php přepíše jednotlivé texty
config.local.php— heslo k DB, sync token, tajemství webhooku, PAT pro soukromé wiki.- Úložiště — ve výchozím stavu není co nastavovat (
db.driver = 'file'). Na'mysql'přepni, jen když ho opravdu chceš; viz Potřebuje databázi? níže. - První crawl:
docker exec php-server php /var/www/wiki.myproject.com/bin/sync.php
Režim wiki vs. režim repozitáře
repo.mode = 'wiki' (výchozí) crawluje github.com/<owner>/<repo>.wiki.git: ploché
stránky, navigace z _Sidebar.md, odkazy na úpravy ve wiki.
repo.mode = 'repo' crawluje markdown uvnitř repozitáře, včetně vnořených složek.
repo.path ho zúží ('docs'), prázdná hodnota znamená všechno.
| režim wiki | režim repozitáře | |
|---|---|---|
| Soubory | ploché, kořen repozitáře | rekurzivně; přeskakuje node_modules, vendor, dist, build, .github, test(s) |
| Slugy | Cesta-paketu |
system/docker; README.md/index.md složky se stane složkou samotnou |
| Navigace | _Sidebar.md |
jedna skupina na složku nejvyšší úrovně, úvodní stránka první |
| Odkazy | názvy wiki stránek | řešené vůči složce dokumentu, který odkazuje, včetně ../ |
| Odkaz na úpravu | /wiki/<Page>/_edit |
/edit/<branch>/<path> |
Prohlížeč souborů (features.file_browser) funguje v obou režimech — ve wiki ukazuje
přílohy, v repozitáři je hlavní věcí. Strom se zachytí při synchronizaci, spolu s datem
poslední změny každé cesty, které pochází z jednoho omezeného průchodu git log, ne
z jednoho procesu na soubor. Textové soubory se vykreslí přímo s čísly řádků až do
velikosti content.file_max_bytes (512 KB); obrázky mají náhled; cokoli binárního nebo
většího odkazuje na GitHub. Stránka, jejíž slug je doslova files, má přednost před
prohlížečem.
Všechno se otevírá uvnitř aplikace: výpisy, prohlížeč i /raw/<path>, které posílá
soubor rovnou z klonu jako přílohu (neutrální content type, nosniff, cesta hlídaná přes
realpath) — stahování tedy nezávisí na tom, jestli je GitHub dostupný nebo repozitář
veřejný. Příliš velké a binární soubory, které nejde zobrazit, nabídnou místo slepé
uličky právě tohle stažení.
Dva detaily kolem proxy, oba naučené tvrdou cestou:
Vlastní vhost: pravidlo, které blokuje přípony, musí být ukotvené na jeden segment cesty (
^/[^/]+\.(md|json|lock|…)$). Neukotvené chytí i/files/portainer_apps.jsona prohlížeč pak vrací 403 právě na souborech, kvůli kterým tam lidé přišli.Cizí proxy: zabezpečené okrajové servery běžně odmítají každý požadavek, jehož cesta končí na
.yml,.sh,.conf,.env,.md,.ini,.log,.sql. To prohlížeč souborů úplně zabije, a ten server ti nemusí patřit. Proto URL souborů nesou název souboru v query —/files/docker/compose/adguard?f=docker-compose.yml— a cesta zůstává bez přípony. Složky mají obyčejné cesty, nemají o co zakopnout. Když ti žádné takové pravidlo nestojí v cestě, nastavcontent.safe_file_urls = falsepro hezké holé cesty. Změna potřebuje novou synchronizaci, protože odkazy uvnitř článků se zapékají při crawlu.
Mermaid
GitHub vykresluje bloky ```mermaid jako diagramy, takže tohle taky. Knihovna se načte
jen na stránkách, které nějaký obsahují, a při přepnutí motivu se překreslí, aby
diagram nikdy nezůstal ve špatné paletě. Když je CDN zablokované nebo je diagram vadný,
vrátí se původní blok kódu místo prázdného rámečku. features.mermaid to zapíná;
mermaid.cdn / mermaid.light / mermaid.dark volí zdroj a motivy.
Článek ve složce ukazuje složku
V režimu repozitáře končí článek, který leží ve složce, tím, co dalšího v té složce
je — soubory a podsložky; sousední markdown se vynechá, protože to jsou stránky a už
jsou v navigaci. system/docker/README.md je šest slov textu a za nimi dva skripty,
o kterých mluví; kořenové README následuje nejvyšší úroveň repozitáře. V markdownu se
nic psát nemusí a zůstává to správně, i když soubory přibývají a mizí. Přepíná se přes
features.folder_contents.
Soubory uvnitř článků
Článek si může vtáhnout skutečný soubor místo kopie, která zastará:
![[docker/compose/adguard/docker-compose.yml]] celý soubor
![[docker/compose/adguard/docker-compose.yml#L10-L12]] výňatek, skutečná čísla řádků zůstanou
![[docker/compose/adguard/]] výpis složky
Cesty se řeší vůči složce článku (včetně ../). Obrázky se vloží jako obrázky, binární
a příliš velké soubory ukážou upozornění, chybějící cíl to řekne místo tichého selhání
a direktiva uvnitř bloku kódu zůstane nedotčená, takže můžeš dokumentovat samotnou
syntaxi. Rozbalení proběhne při synchronizaci, takže zobrazení stránky je pořád jeden
uložený řádek.
Obyčejný markdown odkaz na ne-markdownový soubor, který v repozitáři existuje —
[compose soubor](docker/compose/adguard/docker-compose.yml) — se stane štítkem souboru
odkazujícím do prohlížeče místo mrtvého odkazu na stránku, která nikdy neexistovala.
Přepíná se přes content.embeds.
Jazyky
Jeden web mluví jedním jazykem. Každý text rozhraní žije v lang/<kód>.json
a site.language vybírá soubor:
cp lang/en.json lang/fr.json # přelož hodnoty, klíče nech
# config.php: 'language' => 'fr'
Přiložené jsou cs, en a de. Neznámý kód spadne na en a data používají
site.locale (ICU) — prázdná hodnota znamená „stejně jako jazyk“. Pro změnu jediného
textu bez sahání do souboru ho uveď pod i18n v config.php; má přednost před JSON.
Připojení pod rozcestník (<hub>/<name>/)
Víc wiki může sdílet jednu doménu: každý fork leží v <hub>/wikis/<name> a nginx vhost
rozcestníku ho připojí na /<name>/ (z disku jen assets/, všechno ostatní do
index.php daného forku). Pro novou wiki:
cp -ašablonu do<hub>/wikis/<name>a v jejímconfig.phpnastavsite.base_urlna origin rozcestníku asite.base_pathna/<name>/(synchronizace s ním předvykresluje odkazy, takže po změně spusť sync znovu).- Nastav
hub.url => '/',hub.labelna název rozcestníku ahub.source => dirname(__DIR__, 2) . '/config.php'. Tím nad horní lištou přibude tenký pruh v barvě akcentu s podtitulem rozcestníku a tlačítkem, které vyroluje malé menu se všemi wiki na rozcestníku (název a podtitul). Text v něm je černý nebo bílý, podle toho, co má sbrand.accentvyšší kontrast. - Přidej do vhostu rozcestníku location blok pro
/<name>/(zkopíruj existující) a přidej wiki do polewikisrozcestníku ('url' => '/<name>/','data' => __DIR__ . '/wikis/<name>'). bin/cron.sh install --container-root /var/www/<hub>/wikis/<name>. Stejný přepínač předej ibin/cron.sh run, protože výchozí cesta v kontejneru počítá s vhostem na nejvyšší úrovni.
Jak crawl funguje
Je to zrcadlo gitu, ne škrabka HTML. První synchronizace spustí
git clone https://github.com/<owner>/<repo>.wiki.git do data/repo; každá další
je git fetch + git reset --hard, takže přenáší jen to, co se změnilo. Pak se každá
stránka znovu vykreslí a index se nahradí v jedné transakci. Při zobrazení stránky se
neděje nic — požadavky se nikdy nedotknou gitu ani parseru markdownu.
Potřebuje databázi?
Ne. Výchozí ovladač file drží celou wiki v data/index jako JSON a hledá v ní
skórovaným průchodem. Pro wiki o 22 stránkách a zhruba 15 000 slovech to je index
o 692 KB a 2,7 ms na celý požadavek hledání, a pohodlně to vydrží až do nižších
tisíců stránek. Porovnávání ignoruje diakritiku, takže prepinani najde
„MAC, ARP a přepínání“ a sit najde „Kdo je kdo v síti“.
Na 'mysql' přepni db.driver, když SQL opravdu chceš: hodně velká wiki, nebo
dotazování indexu odjinud. Tabulky se vytvoří při první synchronizaci; oba ovladače jsou
za jedním rozhraním, takže se nic jiného nemění. Když je MySQL nastavené, ale v době
požadavku nedostupné, web potichu servíruje souborový index místo chyby.
Jak to držet čerstvé
Plán řídí bin/cron.sh — jeden soubor v /etc/cron.d na web, pojmenovaný podle složky,
takže se forky nikdy nesrazí:
sudo ./bin/cron.sh install --interval 15 # každých 15 minut
sudo ./bin/cron.sh status # je naplánovaný a kde je log
./bin/cron.sh run # crawl teď hned, přesně jako z cronu
./bin/cron.sh logs -n 40
sudo ./bin/cron.sh remove
Volby: --interval <minuty>, --container <název> (výchozí php-server),
--user <jméno> (výchozí www-data), nebo --host pro volání PHP přímo bez dockeru.
Místo dotazování push: nasměruj GitHub webhook pro událost gollum na
https://<domain>/sync se sync.webhook_secret, nebo ho šťouchni ručně přes
curl "https://<domain>/sync?token=<sync.token>".
Struktura kódu
config.php veškerý branding, funkce a texty
config.local.php tajné hodnoty (git-ignored)
index.php front controller: /, /<Page>, /search, /contents, /files,
/raw/<file>, /api/search, /media/<file>, /sync, /sitemap.xml
includes/
Sync.php crawler: git → vykreslení → index
Git.php bezobslužný wrapper git CLI
Navigation.php _Sidebar.md → skupiny, pořadí, titulky, sekce
Markdown/ prostředí CommonMark
Renderer.php HTML + obsah + sekce + perex + počet slov
LinkRewriter.php wiki odkazy, obrázky, src/href v holém HTML → URL webu
CalloutExtension.php > [!NOTE] → označené upozornění
WikiLinkParser.php [[Gollum]] odkazy
AsciiSlugger.php kotvy nadpisů bezpečné vůči diakritice
Store/ FileStore | MysqlStore za jedním rozhraním
Text.php odstranění diakritiky pro hledání bez ohledu na ni
Site.php fasáda pro čtení, kterou používají views
Theme.php config → CSS custom properties
views/ layout, home, page, contents, search, 404, setup
assets/css/wiki.css design systém (veškerá barva přes custom properties)
assets/js/wiki.js motiv, sbalitelné sloupce, paleta, scrollspy, kopírování
bin/cron.sh install / status / run / logs / remove plánu crawlu
lang/ cs.json, en.json, de.json — každý text rozhraní
data/ git klon + souborový index (nikdy se neservíruje přímo)
Poznámky
- Hledání: průchod bez diakritiky (souborový ovladač) nebo FULLTEXT v boolean režimu se záložním LIKE pro krátké dotazy (MySQL). V obou případech jsou výsledky po stránkách a skočí na nejlépe odpovídající nadpis.
- Přejmenované úvodní stránky: GitHub wiki přejmenovávají
Home.mdbez obalu. Synchronizace najde úvodní stránku (config → běžné názvy → první položka postranního panelu) a starý název ponechá jako alias, takže existující odkazy dál fungují. - Úložiště má zálohu: když je MySQL v době požadavku nedostupné, web servíruje ze souborového indexu místo chyby.
- nginx: viz
nginx.conf.example; spustitelný je jenindex.phpadata/,includes/,views/,vendor/,bin/jsou zakázané.