Tagmigration

How did I set up blog.sh on my shared hosting

Pavel moved a WordPress blog off a shared host and onto blog.sh, over plain FTP. The write-up goes step by step: the WXR export, the import, rclone, a dry run, the deploy.

Worth a read if your site lives on a host that gives you FTP and nothing else.

#blogsh #wordpress #staticsite

Posts nobody meant to publish

An importer's worst failure is not losing a post. It is publishing one.

1.5 went through all eleven import adapters and the machinery underneath them looking for exactly that, and found it three times over, in three different files, made the same way each time.

An archive is the whole account

A Mastodon export is not your public timeline. The outbox holds your followers-only posts and your direct messages beside the public ones — and every standalone toot in it was being written as published. On the archive this was measured against that is 141 posts of 2,548, of which 132 are direct messages, each given its own page, its own sitemap entry and its own item in the feed. Nothing in the run's summary mentioned it.

The scope was in the export all along, exactly where ActivityPub puts it. Public in to is public. Public in cc is unlisted — a real address, out of the listings. Followers-only and direct become drafts: that keeps the author's words in the archive and off the site, which is the only reading of "not public" that cannot publish something by accident.

Read more

My fixtures agreed with me

The importers had a good test suite. Every source had fixtures, the fixtures were parsed, the results were asserted, and everything passed.

Then I ran the importers over three archives I had not made: a Ghost export of 118 posts and 419 images, a Hugo tree of 77 files, and a WordPress WXR of 194 items. Alongside them, a fourth that is entirely mine — this engine's own export, read straight back in — because that one is not asking whether the code survives a stranger, it is asking whether the two halves of a round trip still agree.

The three foreign ones were the point, though. Fixtures had agreed with the code because both ends of them are mine.

The picture nobody failed to download

{{< figure >}} is how Hugo writes a picture, and the Liquid stripper ate it as a Liquid tag. Both of the ones in that archive pointed at files lying right next to the article in its page bundle: nothing to download, nothing that could fail, and they were gone without a line in the summary — because nobody had tried to fetch them, so nobody had failed.

Read more

An import only ever adds

Re-importing an archive used to be a thing you did carefully, at night, after a backup. The run went out and fetched every picture again — pictures the archive already held, byte for byte — and each of those downloads landed on top of a file that was already correct.

That is fixed, and the fix is one sentence: a media file's identity is the address it came from.

The archive already knows

Every media entry records its src. A re-import compares what the source offers against what the archive holds, and fetches only what is missing. Over a complete archive that is nothing at all — the second run of the same import downloads not one picture, and says so in the summary.

The source itself is still read, and that is the honest limit of the claim: an export sitting on your disk costs nothing, while a feed or an API is fetched every time, because there is no other way to find out whether anything changed.

Read more

Most of your Facebook isn't yours

The Facebook importer was built against a real "Download Your Information" export. The first useful thing it told me was a number I did not expect.

On that export, 95 % of the posts were not written on Facebook. They were mirrored in — from Twitter, from Posterous, from the whole crosspost era when every service politely offered to repost you everywhere else.

They are skipped, and counted

Those posts do not import. Not because they are worthless, but because the platforms they actually came from have their own exports, and those carry the originals: the full text, the real timestamps, the media at the size it was uploaded. A Facebook mirror of a tweet is a worse copy of something you can import properly from the Twitter archive sitting in the same folder.

Import both and your archive doubles, with the lesser version winning half the time. So they are recognized and skipped with a count in the summary, and FACEBOOK_CROSSPOSTS=1 overrides it if you disagree. Wordless check-ins and app stories are counted skips too.

Read more

Rescuing a blog that died in 2013

Every importer here reads an export file. That works right up until the platform is gone — and then there is no export to ask for, no admin to log into, and no account to recover. Posterous shut down in 2013. Blog.cz is gone. Your host is next, eventually.

For those blogs the twentieth import source is the Internet Archive.

Feed mode: what the crawler kept

The Archive did something useful without meaning to. For years it crawled blogs' RSS feeds — over and over, every few weeks, whenever a crawler happened past. Each capture is a snapshot of the last ten or twenty posts at that moment. Line them up oldest first and the overlaps reconstruct a history nobody saved.

That is what the importer does: reads the CDX index for the feed's address, deduplicates by digest so a hundred identical captures cost one fetch, pulls the original bytes through the id_ endpoint so the Archive's own toolbar never lands in your posts, and merges the overlapping windows the same way a re-import merges anything else.

Images come back through the same time machine.

Read more

A blog engine that became an archive

The first post on this site is about a photo I put on Instagram in 2012. The link still works. The photo doesn't.

That post was about one archive — mine — and one rule: media comes home. This one is about what happened when that rule was applied to every platform I could find an export for.

The roster is closed

Twenty-two sources import now, in three groups.

Social networks: Bluesky, Facebook, Instagram, Mastodon, Pixelfed, Threads, Twitter/X.

Blogs and publishing platforms: beehiiv, Blogger, Ghost, Jekyll/Hugo and any markdown folder, LiveJournal, Medium, Movable Type and TypePad, podcasts (any feed with audio), Squarespace, Substack, Tumblr, Wix, WordPress and any RSS or Atom feed.

And dead sites: the Wayback Machine, for blogs whose platform no longer exists at all.

Read more

Fourteen years, and every image came home

In 2012 I posted a photo to Instagram. The link still works. The photo doesn't.

If you kept a blog anywhere between 2010 and now, you know the drill. The platform got acquired, or pivoted, or "sunset some legacy infrastructure", and the images you embedded from their CDN quietly turned into grey rectangles. My personal archive goes back fourteen years across Tumblr, Twitter, Mastodon and a couple of other places — and a depressing number of its images now live at addresses that answer with nothing at all.

Here's what I mean, from my own archive. On February 22, 2012, I published a blog post called #53: Messengers of Spring? and embedded its photo straight from Instagram's CDN, the way everyone did back then. This is that link today:

Read more