./blog.sh

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

Your camera writes down where you were

Take a photo on a phone and it records the spot you stood on. Not as a guess — as coordinates, accurate to a few metres, in a block of metadata that travels with the file wherever it goes.

Social networks strip that on upload. They have done it for years, quietly enough that most people have stopped thinking about it. A static site has nobody to do it: blog.sh copied your photo into the archive byte for byte, and nothing in the engine had ever looked at metadata at all. So a picture of a cat on a windowsill published the windowsill.

From 1.2.1 it doesn't. A photo loses its location on the way into the archive — when you add it to a post, and equally when it arrives through any of the twenty-two importers, because they all end up in the same write.

Only the location

The camera, the lens, the exposure, the moment the shutter opened: those stay. They are your own record of your own photograph, and an archive that silently threw them away would be answering a question nobody asked.

Read more

./blog.sh 1.2.1

./blog.sh 1.2.1 is out.

A phone records where you were standing in every photo it takes. Social networks strip that on upload; a static site has nobody to do it. Now the engine does, on the way into the archive — and the doctor will clean the ones you saved before.

The bio, the footer, and the banner's claim are now Markdown. They were the only texts on a Markdown blog that had to be HTML.

Setting up is a conversation now

Until this release, installing ./blog.sh meant copying two files and editing 277 lines of commented YAML before the first build would run. Everything was documented. That is not the same as being easy.

./setup.sh asks instead, and checks every answer as it arrives.

The questions that were quietly dangerous

Three settings could be typed wrong in a way nothing would ever tell you about.

The timezone. A typo here does not fail — Ruby falls back to UTC and says nothing — it just dates every post two hours off, forever. The wizard offers the zone from the machine's own database, unless the machine sits on UTC, which is a fact about a datacenter rather than about the person answering; then it suggests from the language you are being asked in. And it refuses anything that isn't a real zone.

Read more

./blog.sh 1.2

./blog.sh 1.2 is out — the import release.

Eight import sources became twenty-two: every social network you are likely to have posted to, the blog platforms, and the Wayback Machine for blogs whose platform no longer exists.

Setting a site up is a conversation now. So is how it looks, with the candidate palette rendered on your own site before you keep it.

And a screen for walking an archive that got too big to scroll.

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

The guard that switched itself off

This site's deploy has two guards. If a build suddenly has far fewer files than what's live, or far fewer bytes, the deploy stops. They exist because a broken build looks exactly like a deliberate one to rsync --delete, and a static site is only ever one confident sync away from being erased.

In 1.0 they could turn themselves off. Permanently. Silently. Here is how, because the shape of this mistake is more useful than the fix.

The reference was the wrong thing

The guards compared the new build against the deploy manifest — a record of what's on the target. Reasonable, until an upload fails. A failed upload leaves the manifest out of true, and a guard measuring against a record it knows is wrong would fire on every subsequent run.

So there was a marker: after a failed run, stand the guards down until a clean run comes along and restores the reference.

Read more

The menu got shorter

The wizard used to list ten things you could do. It lists five now, and the engine can do more than it could before.

That's not a paradox, it's an admission: eight of those ten were not activities. They were operations on a post, wearing the costume of a menu item.

Everything about a post, in one place

./blog.sh props <slug> — or, in the wizard, pick a post and press v — shows what there is to know about it. State, type, tags, whether it's pinned, whether it's been announced and where. If it's scheduled, the whole publishing queue.

And the actions live there too, next to the facts they act on. A draft offers publish and schedule. A published post offers unpublish, re-announce, pin or unpin, rename, delete.

You no longer go to a menu, choose "unpublish", and then hunt for the post. You go to the post and see that unpublishing is one of the things it can do right now.

What stayed where it was

Read more

An old link still knows the way

The first post on this site is about a photo that disappeared from a 2012 blog entry while the link to it kept working. Link rot is the reason this engine exists, so an engine that quietly broke its own URLs would be a bad joke.

Renaming a post changes its address. That's unavoidable — the slug is the URL. What's avoidable is the old address turning into a 404.

What renaming does now

Rename a post — r in the properties dialog — and the post records its old address inside itself. The build then keeps a one-page redirect standing at every address the post has ever had.

The link in a two-year-old toot keeps resolving. The link somebody put in their own blog post keeps resolving. You get to fix a slug you regret without the fix costing you every reference to it that already exists in the world.

The redirects belong to the post

Read more

The photo your iPhone won't show anyone

An iPhone shoots HEIC by default. Safari renders it. Chrome doesn't. Firefox doesn't. So a photo straight off the phone, attached to a post and published, is a photo most of your readers will never see.

The old behaviour was the worst possible one: the file went in, the build couldn't measure it, and the image quietly vanished from the page — taking its caption with it. No error. Just a post with a hole where a photo was.

Refused, with the command you need

Attaching a HEIC now stops the save and prints the exact conversion command for the machine you're standing at:

sips -s format jpeg photo.heic --out photo.jpg      # macOS
heif-convert photo.heic photo.jpg                    # Linux

Your file stays exactly where it was, in incoming/. Nothing is deleted, nothing is guessed at. You convert it and carry on.

Or converted, if you ask

Read more