Text76

The archive talks back

A letter arrived at the end of September. Pavel had used this engine to back up his old blog, and wrote down what he learned doing it: over a thousand posts in nine years, and stretches when he wrote every day. He had not known. The blog had been his the whole time and the posts had been in it the whole time, and still the number came from the archive, the first time anything read it end to end.

This release is about what an archive says when it is asked. How things get into one, how they leave it and how it is checked, this site has written about enough. This is about what the archive knows about you.

What the archive counts

./blog.sh stats reads every post and answers with one screen. On the archive this engine was built around, today it reads:

Read more

./blog.sh 1.10

./blog.sh 1.10 is out — release on GitHub.

The release that opens a door for programs. Every screen you read before you act, and every action it offers you, can now answer as data too. A program on another machine reaches them over the SSH the server already has, and only through one script that lets it run one engine command and nothing else. It connects with a code ./blog.sh pair draws in the terminal, so nobody types an address or a key anywhere. This is what the native apps for iOS and Android, now in preparation, are built on.

Around it, the archive says more about itself. An "On this day" card shows posts from this day in earlier years, /series/ is an index of every series, a page of a listing says which days it covers, and the 404 page offers a search already filled in with the words of the address it could not find. On a Mac, ./setup.sh offers an icon in Applications, so the blog opens without typing into a terminal. And the second language from 1.9 has grown up: a series has a name in each language, and a translation keeps its old addresses the way a post does.

One thing ahead of time: the /write/ page and the SendPost shortcut go away in 1.11, and the apps take their place. In 1.10 they work as before.

Read more

Where this engine grew up

Every feature on this site has a page that says what it does. Fewer say why it is there, and almost none say what the week before it existed was like. That part was written somewhere else: on sean.cz, the archive this engine was taken out of and the site every change lands on first — 6,655 posts going back to 2003. Until last week it was written in Czech only.

Since 7 October sean.cz reads in English as well: 126 posts, each the same post as its Czech original with the words in a second language, the way a second language works here. Among them is the whole series that follows this engine from the day the site moved onto it, The new Sean.cz: twenty-one parts so far, July to October.

A few places to start, if you want a sample rather than the whole run:

Read more

The page a stranger opens first

Saturday, LinuxDays in Prague, a stand run by friends from OSCloud — Mastodon, Pixelfed, PeerTube, Matrix, the services for people who want their data where they can see it. A full day behind one table, and not a single talk seen. This engine was there for the first time in person: on a leaflet, and in conversation whenever the Fediverse came round to "and where would I write?"

Four squares on an A4

The leaflet is one sheet. Four panels — write, move, deploy, show — each with a terminal excerpt and a QR code. None of the codes lands on a page made for the event. Each one opens a tag page this site already had: /tag/authoring, /tag/migration, /tag/deploy, /tag/appearance. The site's own archive, sorted by what a stand gets asked, was the landing page. Nothing was built for the day that would be stale the day after.

Read more

How big it can get

It is a fair thing to ask before you hand twenty years of writing to a tool: at what point does it stop being pleasant to use? Measured here in August, on an archive of 6,639 posts, the honest answer was a shrug: adding one post cost the whole archive — forty-eight seconds from pressing enter to the site being live — because every page that so much as mentioned a post had to be remade, whether it had changed or not.

That's no longer the honest answer. Since 1.6, the build keeps a record of what went into each page, and remakes only the ones whose record no longer matches. The archive underneath is the same one this was measured on in August, seven posts heavier: 6,646 now, and the engine three releases on, at 1.9.

What you wait for today

Publishing a post dated today, on that same archive: sixteen seconds of build, two of upload — where it was forty-eight. The build isn't touching pages that didn't need it any more, so the size of the archive stopped being what the wait was made of.

Where the size still shows up

Read more

./blog.sh at LinuxDays 2026

LinuxDays 2026 take place in Prague this weekend, on Saturday 3 and Sunday 4 October, at FIT ČVUT in Dejvice, as they do every year. Admission is free.

On Saturday I will be at the stand of my friends from OSCloud all day. If you want to talk about ./blog.sh, ask how this or that works, find out whether it would carry your blog, or tell me what got in your way, this is the chance to do it face to face rather than in issues on GitHub.

Nothing much to prepare. A question is enough — or the blog you have been meaning to move. Everything else we will talk over at LinuxDays 2026.

Programme and directions: linuxdays.cz/2026

#blogsh #LinuxDays

Everything else in 1.9

Five posts have covered what 1.9 is about: a post in two languages, the site in the language of the page, what stands in a deploy target before the first deploy, a host that only speaks FTP and one phone shortcut instead of two. This one is the rest, in the order an operator meets it. Most of it came from two people who set 1.9 up from nothing and upgraded a copy of a 6,600-post site, doing only what the docs said.

Read more

One shortcut instead of two

Sending a post from an iPhone took two shortcuts until 1.9: one to take the files from the share sheet, one to open the SSH connection. Written on a train walked through both. It is one shortcut now, imported rather than built, and it runs on a Mac as well.

Read more

A host that only speaks FTP

A shared hosting plan often comes with a directory, an FTP login and nothing else — no SSH, no rsync, no git. That has been enough for blog.sh all along; the docs just never said so until 1.9. The way there is the rclone backend, because rclone speaks FTP as one of its many remotes.

Read more

Before the first deploy

Somebody moves a blog off WordPress onto blog.sh and keeps the same hosting, the same directory, the same address. The build is ready, the first deploy goes through without an error — and the front page is still the old one. Nothing went wrong in the upload. The old site was simply never taken away, and the host prefers it.

Read more

The site speaks the language of the page

Somebody translates their posts, opens the Czech half of the site — and the menu still says Writing, the footer still says Links, and every tag on every pill is the English word. The posts were only ever half of what a reader reads. The rest is what the site says about itself, and in 1.9 that has a language too.

Read more

Two languages, one site

Somebody writes in Czech, and some of the people who would read it don't read Czech. The usual answer is a second blog — a second archive, a second set of tags, a second feed — and within a year the two have drifted apart: a post fixed in one and not the other, a tag renamed on one side, a reader who switches languages and lands somewhere that no longer matches. blog.sh 1.9 gives a different answer: one site, and the other language beside it.

Read more

./blog.sh 1.9

./blog.sh 1.9 is out — release on GitHub.

The release about publishing in more than one language. Name them in site.locales, and a post keeps one date, one set of tags and one series, with a text per language written by ./blog.sh translate. The site's own language keeps the root, every other one gets its own, and one rebuild makes all of them. The banner, menu, footer and tags speak each language too; a post nobody has translated still shows, linking to the one copy that exists. This very site has been running in English and Czech since the release was a draft.

Around it, the first hour: two people installed 1.9 from nothing and upgraded a copy of a 6,600-post site, doing only what the docs said, and everything they tripped over is fixed. Esc never does anything, the setup speaks your language from its first screen and will not take the template's example as your site's name, a draft's properties show where to read it — with a QR code for the phone — and doctor --online now asks the deploy target what stands in its root that the site does not put there.

Read more

The parts that don't rebuild themselves

A laptop dies, or a server gets rebuilt from nothing, and the question that actually matters is narrower than it sounds. Not "how do I back this up" — "which of these files does the engine actually need me to protect, because nothing else here can make them again on its own."

Most of an installation doesn't qualify. The engine itself is a git clone; pull it again and it's the same engine. Everything the build writes comes back with one rebuild, given what's left.

The instinct is to back up the site itself — the folder a browser actually sees. That's precisely the one thing this list leaves out. public.nosync, the build's own output, is exactly as disposable as the engine that made it: one rebuild reproduces it byte for byte, so keeping a copy of it would just be storing the same information twice, under two different names, and trusting the older copy by accident.

What's left, once the engine and the site it builds are both set aside, is a short list.

What's actually irreplaceable

Read more

The lock that doesn't wait

Twice an hour, on a large enough site, two cron jobs reach for the same folder at almost the same moment: the scheduled publish, building and deploying whatever post came due, and the sidebar refresh, rewriting a few of the same output files with fresh numbers. Most ticks miss each other by minutes. On an archive big enough that a build and a full deploy outlast the interval between them, some don't.

A deploy walking a tree that another run is still rewriting can serve a page half-written. A run pruning orphans can delete, as an orphan, a page the other run published a second earlier. Neither failure announces itself — the site just ends up quietly wrong.

The lock, and what it refuses to do

One file, .blog-sh.lock in the project root, stands between those two runs — and it makes a choice that looks wrong until you see the alternative. A run that finds the lock already held does not wait for it.

Read more

What's left to get wrong

There's no /admin on this site. No login form to try a password against, no session cookie to steal, no plugin list to check against last month's CVEs — not because a checklist said to strip them out, but because none of them were ever built in the first place.

The engine's own architecture notes put the rest of it plainly: the threat model is a static site with no server of its own. Nothing here listens on a port. The goal was never to harden a complex system against everything that could go wrong with it. The goal was to avoid needing one in the first place.

What's left to get wrong, once that's true, is narrower than a normal blog's list — what the pages carry, what the working directory holds, and the one thing that does arrive from outside.

The one thing that arrives from outside

Writing from a phone means one door does accept bytes from a stranger's own device: scripts/receive.sh, running as the forced command of an SSH key. It listens on nothing — no socket, no daemon, just a key that can run exactly this script and nothing else.

Read more