Štítekrelease

./blog.sh 1.8

./blog.sh 1.8 je venku — vydání na GitHubu.

Vydání o tom, co se stane, když se něco pokazí. Každá stránka, feed a index se teď zapisuje do dočasného souboru a přejmenuje na místo, takže uložení přerušené plným diskem nebo sestavení zastavené uprostřed zápisu už nezanechá stránku v poloviční délce ani příspěvek o 0 bajtech, u kterého je starý text pryč. Studené sestavení tím zdraží zhruba o pětinu; sestavení z cache ne, takže běžný den publikování stojí přesně tolik co dřív.

Dvojí doručení už neznamená, že se něco stane dvakrát: příspěvek odeslaný z telefonu dvakrát nebo jedna potvrzenka doručená ve dvou kopiích najednou je teď jeden příspěvek a opětovný import exportu vlastního enginu už z 1200 příspěvků nedělá 2400. A adrese se už nevěří jen proto, že jako adresa vypadá — ../ ve slugu, v názvu mediálního souboru nebo v cíli přesměrování zůstane uvnitř webu, lokální nasazení zůstane uvnitř svého adresáře a přesměrování z feedu nemůže poslat stahování na localhost.

Číst dál

The last thing a recorder writes

Someone opens a post with a video in it and presses play. The connection is fine. The video is not large. Nothing happens for a while, and then the video starts all at once, as if it had been waiting for permission.

It had been waiting for the index.

Why it sits at the end

An MP4 is a handful of boxes laid end to end. One of them, mdat, is the picture and the sound. Another, moov, is the index: which frame is at which byte, how long the thing runs, what codec to hand it to. A player needs moov before it can show a single frame.

Číst dál

A bicycle nobody had to draw

The example configuration this engine ships carries a bicycle: a tag called kolo, and under it an <svg> written out by hand. It is there because there was nothing else to put there. tag_icons took the name of an icon the engine ships or a drawing of your own, and the ones it shipped were the eight content types — text, image, video, link, audio, quote, document, chat. A tag about cycling could wear the glyph for "audio", or somebody could spend an evening with a coordinate grid.

1.7 puts fifty-five drawings in between, and gives each of them a name.

Fifty-five nouns

Counted in lib/icons.rb: the eight content types, fifty-five more, sixty-three names a tag may ask for. Grouped by what a blog turns out to be about.

Číst dál

The address a post is about

Somebody sends you an article. You read it, and you have two hundred words to say about it -- not a review, the thing you noticed and one sentence about why. The post is mostly the other page, and the address it is about has to live somewhere.

Until this release the honest answer was: in a paragraph, like any other link. The card such a post opens with existed as a block -- the importers make them, out of Tumblr and out of Bluesky -- but nothing anybody could type produced one. The writer that turns a post back into markdown had no syntax for it, so edit on a link post offered to drop the card, and add <file> could not make one at all. This project's own release posts stopped being link posts after 1.3 for that reason. The tool had changed; the intent had not.

Three lines in the header

Číst dál

What a post is, not what it says

Five parts of a series written over a spring, and the third went out without its series: line. The site did what it was told. Part three sat at its date and nowhere else: off the series listing, out of the links from one part to the next, and out of the count the other four wear: part four's page called itself part 3 of 4, standing in the slot the missing post should have had. The fix was one word.

Getting to that word meant opening the article.

That is what edit does. It reads the post out of its file, writes the whole thing back out as markdown, opens it in an editor, and parses what comes back. For a post that is paragraphs and photographs the round trip is faithful and nothing is at stake. For a post carrying something markdown has no form for, it is not.

Seven rows

props <slug> gains [e] -- on a draft, on a scheduled post and on a published one alike.

Číst dál

The post that waited for a terminal

The post is written on the tram: a title, four paragraphs, two photographs with their descriptions. The switch above Send says Draft, which is what it says unless it is moved. Send, and the answer card comes back with the preview address. Then it sits. Putting a draft out was a command, and a command wants a terminal, and the tram has none.

1.6 asked for that decision at the wrong end: publish at the moment of sending, before the blog has rendered anything, or see the blog's own page and then find a keyboard. 1.7 closes that, and then closes the road the answer takes back.

One file, down the same connection

The answer card for a draft now carries a Publish button. Pressing it sends one file, called publish.txt, holding the slug and nothing else -- through the same share sheet, the same two shortcuts, the same SSH connection the post itself took.

Číst dál

./blog.sh 1.7

./blog.sh 1.7 is out. The page that writes a post on a phone now publishes it too, and it finds out what happened rather than being told: it mints a receipt before sending and asks the site for it every three seconds until the answer comes.

Beside it, the other half of the same idea — changing what a post is without opening what it says: its series, its tags, its type and three flags, from a screen. Then a link card written in the front matter, about fifty drawings a tag can wear, a video repacked on the way in so the reader does not wait for the whole file, and seventeen fixes.

#blogsh #ruby #staticsite

./blog.sh 1.6

./blog.sh 1.6 is out.

The build stopped rebuilding what nobody changed: a rebuild that changes nothing costs a seventh of what it did, an ordinary publish about two fifths. A post can be handed over as a file — by a script, a cron job, or the new page at /write/ on the blog itself, which sends from a phone over the SSH the server already has. Nothing new listens on the network.

Around those: a row of share controls under a post, an icon a tag can carry, a way out of the trash and the versions, and a photograph stored once instead of twice.

#blogsh #ruby #staticsite

./blog.sh 1.5

1.5 is the release where the site learned to say what it holds. The release is on GitHub.

A post that never had a title used to be called by its address — on this project's own reference archive that was 62 % of it. Now it is called by its own opening words, everywhere except its heading, which stays the date it has been since 1.3.

Two new pages come with it: /archive/, a map of the whole site in two levels, and /tag/, every subject it has ever written about. Both are built from the posts themselves — nothing to configure and nothing to keep up to date.

Two things a post can now decide for itself: where its teaser stops (//--more--//, asked for in issue #35), and how much of it a listing card shows — cards are cut before they are written rather than drawn in full and hidden with CSS. And a code block carries a copy button, asked for by an operator running a blog of terminal how-tos.

Číst dál

./blog.sh 1.4

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

The widest release since 1.0. Three things arrived: the commits widget reads Gitea and Forgejo — asked for by one of the first people outside this project to run the engine, because most of the Fediverse keeps its code on Codeberg, and mirroring to GitHub for one sidebar card was the workaround. widgets.commits.instance takes the server's address; that is the whole configuration. Comments work on GoToSocial — found on arch-linux.cz, where no post had ever shown a single comment, silently; my own tests had Mastodon on both ends, so they could never have caught it. And the engine now speaks the site's language everywhere: a Czech or German site used to watch its own deploy and build warnings go by in English, one line above a translated sentence. Sixty-odd sentences moved into the locales.

Číst dál

./blog.sh 1.3.2

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

A GoToSocial release, and both fixes came from the first site to wire blog.sh to a GoToSocial instance: arch-linux.cz runs its own, instead of pointing at a Mastodon account. My own tests had Mastodon on both ends, so they could never have found either.

GTS speaks Mastodon's API with its own accents, and two places in the engine had taken the Mastodon dialect for the whole language: the feed widget asked every host for JSON first and got exactly that, a JSON Feed the XML parser then reported as malformed; and doctor refused the account id its own setup had pointed you at, because it demanded digits where GTS answers with a 26-character ULID.

One small thing for skins came from the same site: a listing page now says whether it is the first one. <body> carries page-first or page-cont, so a stylesheet can tell the front of a listing from its /page/N/ continuations — something CSS cannot work out on its own, because it cannot read an address.

Číst dál

./blog.sh 1.3.1

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

A bug-fix release about restraint. The scheduled-publish cron now declines the announcements it cannot take back: a backdated post publishes without announcing, and a post that already carries its announcement address is never announced again — not by the cron, not by publish, not by toot, whichever network the address lives on. The queue takes its lock for every write too, so a tick landing mid-edit cannot revert a post it just published.

The first hour of a fresh install stopped misleading: preview and publish print the address that actually opens instead of example.com, the deploy speaks the site's language, and the example config no longer chooses Mastodon for you — a network is a choice.

And emptiness is an answer now, in both directions: an emptied section takes its heading with it instead of crashing the build or failing doctor, and a heading over content that stays can finally be turned off.

Nothing to migrate — git pull, rebuild, deploy. Full notes →

./blog.sh 1.3

./blog.sh 1.3 is out.

Dressing a site no longer means editing the engine: your own stylesheet, menu, sidebar and lead images are settings now. Posts group into series, pages step out of the stream, and every edit keeps the version before it.

And three commands that read the whole archive: check reports what's broken, stats counts what's there, and export packs it all into Jekyll's layout. The way out ships with it.

#blogsh #ruby #staticsite

./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.

./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.

./blog.sh 1.1

./blog.sh 1.1 is out.

Pin a post to the top. Schedule into publishing slots that queue instead of colliding. Attach files as download cards. Rename a slug without breaking a link — the old URL redirects forever. One dialogue for everything you can do to a post.

And deploy guards that measure the build against the build, so a failed upload can't quietly switch them off.

./blog.sh 1.0.1

./blog.sh 1.0.1 is out — eighteen fixes, none of them reported by a user.

They came out of an independent verification pass over the release: a failed write could leave a post without a file, one bad post could stop the whole build, a missing media file could delete the copy that was already live.

There's now a CHANGELOG a clone can read, and ./blog.sh version tells you what you're running.

./blog.sh 1.0

./blog.sh 1.0 is out.

A blog engine that's a folder and a shell script. Ruby stdlib and bash — no database, no gems, no admin. Comments live on the Fediverse. Eight importers, so your Tumblr and Twitter archives have somewhere to go. MIT licensed.

The site you're reading is the engine running itself.