Migrace z BlogEngine.NET do Ghost

V předchozím článku jsem popisoval důvody, proč jsem opustil starý blogovací nástroj a kritéria, která jsem kladl na jeho nástupce. Jedním z posledních, ale nikoliv nejméně důležitým, byla možnost migrace bez ztráty dat, tj. příspěvků, stránek a pokud možno i komentářů.

Na tomto místě bych chtěl ještě jednou vyjádřit mnoho díků Brianu Peekovi, který pracuje v Microsoftu jako Senior Software Engineer, a stejně jako já "uvázl" v roce 2017 na BlogEngine.NET.

Export a úprava dat

Dle jeho článku jsem nejprve vyexportoval obsah všech příspěvků na původním webu z /admin/#/settings/importexport, kde se nachází tlačítko Export. Výstupem je soubor BlogML.xml. Dále jsem si odzálohoval kompletní obsah adresáře App_Data, hlavně podadresáře files. A pak samozřejmě ještě další statické soubory, hlavně obrázky, které byly na blogu použité "natvrdo". Z tabulky níže vyplyne, že statické soubory jsem na novém webu umístil do adresáře /static/, zatímco veškeré obrázky jsem nahrál do /content/images/. Mimochodem, pro vytvoření /static/ jsem vycházel z tohoto návodu.

BlogML.xml je třeba trochu upravit. Mně se osvědčil Notepad++, který umí i Search&Replace mód s regulárními výrazy. BlogEngine.NET používal rozdílné handlery pro zpracování vložených souborů a obrázků, proto jsem s BlogML.xml provedl tyto úpravy:

Najít Zaměnit Důvod
/file.axd?file= /static/ Umístění odkazovaných souborů
http://dolezel.net/image.axd?picture= /content/images/ Umístění obrázků
/image.axd?picture= /content/images/ Umístění obrázků
[more] -nic- Vypuštění instrukce pro BE.NET rozšíření
.aspx -nic- Z nejstarší verze BE.NET
http://www.dolezel.net https://dolezel.net Z nejstarší verze BE.NET
http://dolezel.net https://dolezel.net Z http na https
www.dolezel.net dolezel.net Z původního URL na novější
<div style='display:none'>(.*)</div>]] ]] Odstranění pozůstatků útoku, regulární výraz

Konverze importního souboru

Tím byl BlogML.xml připraven ke konverzi do importního souboru pro Ghost. Na to existuje prográmek BlogEngine2GhostConverter, který by však bylo třeba zkompilovat. Proto jsem dal přednost zkompilované verzi Briana Peeka odsud.

Syntaxe je:

BlogEngine2GhostConverter.exe BlogML.xml GhostImport.json

Bohužel byla výstupem tato chybová hláška:

Unhandled Exception: System.IO.FileNotFoundException: Could not load file or assembly 'Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed' or one of its dependencies. The system cannot find the file specified.
   at BlogEngine2GhostConverter.Program.Main(String[] args)

Na webu jsem našel a stáhnul knihovnu Newtonsoft.Json.dll ve verzi 7.0.1.18622 a umístil ji do stejného adresáře jako BlogEngine2GhostConverter.exe. Druhý pokus s konverzí a ... Success! Soubor GhostImport.json je na světě. Teď už by mělo stačit jen soubor naimportovat do lokální instalace Ghost a hotovo.

Import souboru do verze Ghost 1

Takže jsem si na Win 10 zkusil nahodit minimální instalaci Ghost s SQLite.

node-v16.16.0-x64.msi

npm install ghost-cli@latest --location=global
mkdir c:\dev\blog
cd c:\dev\blog
ghost install local

http://localhost:2368/ghost

Nyní jsem šel do Settings, Labs, Import content, vybral importní GhostImport.json a stiskl tlačítko Import. A sakra! Soubor není podporovaný. No jo, aktuální Ghost je verze 5, import z BlogEngine2GhostConverter.exe podporoval verzi 1.

To by asi u mnoha blogů byla konečná. K mému opravdu velkému překvapení však ne u Ghostu. Odinstaloval jsem nejnovější verzi ghost a zkusil nainstalovat verzi 1.

ghost install local --v1

Kruci, další chybová hláška:

Ghost v1.26.2 is not compatible with the current Node version. Your node version is 16.16.0, but Ghost v1.26.2 requires ^8.9.0 || ^10.13.0

Takže jsem odinstaloval vše, ghost, ghost-cli, node.js. Pak nastalo mírné experimentování s verzemi, protože jsem nikde nenašel tabulku kompatibility. Nicméně zabrala tato kombinace:

node-v10.13.0-x64.msi

npm install ghost-cli@1.12.0 --location=global
mkdir c:\dev\blogv1
cd c:\dev\blogv1
ghost install local --v1

Otevřel jsem http://localhost:2368/ghost, našel Labs, v Import sekci vybral Browse..., zvolil importní GhostImport.json a stiskl Import. Neuvěřitelné, import proběhl úspěšně!

Import do Ghost 5

Takže jsem okamžitě dal Export, pečlivě uložil soubor, odinstaloval opět vše (ghost, ghost-cli, node.js) a nainstaloval zpět nejnovější verze. Chvíle napětí, Settings, Labs, Import content, vybrat exportní .json z verze 1, Import - heuréka! Jsem na nejnovější verzi a příspěvky jsou zmigrované.

V tento okamžik jsem se pustil do budování "produkčního" webu, včetně nginx a MySQL CE. Export z lokální instalace se SQLite a import do produkční verze s MySQL CE dopadl na jedničku.

Stránky (pages) jsem zmigroval stylem copy&paste, tj. v jednom okně jsem si otevřel starý blog, ve druhém nový a těch pár stránek vytvořil znovu a obsah zkopíroval/upravil.

URL redirekty, SEO pravidla

Kdysi dávno v roce 2007 jsem pár měsíců používal URI příspěvků ve tvaru /post/slug, ale později jsem přešel na tvar /post/2022/04/21/slug. To je samozřejmě při přechodu na jiný blogovací software problém, protože Ghost používá URI /slug/. Co šlo, tak jsem změnil přímo v BlogML.xml. Nicméně je třeba udělat maximum pro to, aby jakýkoliv požadavek na staré URI byl přesměrován na správnou aktuální stránku. Naštěstí v nginx s pomocí regulárních výrazů to není až tak obtížná úloha. Podporu redirektů má Ghost i sám v sobě, ale říkal jsem si, proč zbytečně obhospodařování redirektů posouvat do další aplikační vrstvy, když to samé přehledně na jednom místě zvládne nginx?

Tam, kde BlogEngine.NET používal pojem Kategorie (Category), používá Ghost pojem Štítek (Tag). Obdoba štítků v původním pojetí BE.NET v Ghostu neexistuje. To je v podstatě jediná část dat, kde došlo během migrace ke ztrátě.

V /etc/nginx/sites-available/www.dolezel.net.conf a www.dolezel.net-ssl.conf jsem přidal toto:

# Match everything else - first BlogEngine redirect support
location / {
    return 301 https://dolezel.net$request_uri;
}

V /etc/nginx/sites-available/dolezel.net.conf a dolezel.net-ssl.conf jsem přidal tyto redirekty:

rewrite "^/post/20(\d{2}/){3}(.*)$" /$2/ permanent; # Orig BlogEngine redirect support
rewrite "^/page/([^0-9].*)$" /$1/ permanent; # Orig BlogEngine redirect support
rewrite "^/syndication.axd$" /rss/ permanent; # Orig BlogEngine redirect support
rewrite "^/category/(.*)$" /tag/$1/ permanent; # Orig BlogEngine redirect support

První řádek přesměruje URI /post/2022/04/21/slug z původního BE.NET na aktuální /slug/.

Druhý řádek pracuje s faktem, že Ghost shodou okolností používá URI /page/0-9/ pro "stránkování" příspěvků. Přesměrovává tudíž pouze odkazy z původního BE.NET, kde URI začíná /page/, ale pak následuje jakýkoliv jiný znak než číslice 0-9.

Třetí řádek přesměrovává původní volání RSS stránky na aktuální URI pro RSS.

Čtvrtý řádek přesměrovává výpis článků spadajících do určité kategorie (v terminologii BE.NET) do výpisu článků s určitým štítkem (v terminologii Ghost).

Existuje pár dalších zaindexovaných volání, se kterými už nic nesvedu:

  • https://dolezel.net/2021/10/default - výpis všech příspěvků z konkrétního měsíce "2021/10"
  • https://dolezel.net/?tag=/blog - výpis všech příspěvků s konkrétním štítkem "blog", alternativa v Ghostu neexistuje
  • http://dolezel.net/PrinterFriendly.aspx?id=d17809f0-856d-4cd4-ae6d-1675e9cf8827 - článek ve formě optimalizované pro tisk, jelikož id při migraci zaniklo, nemám to jak pořešit

Tak a to by bylo ohledně migrace příspěvků a stránek všechno. V dalším článku stručně popíšu migraci BlogEngine.NET komentářů.