The door for programs
Up to 1.9 the engine had one door for programs, and it was narrow. The phone delivered a post through it: a shortcut sent a file over SSH, receive.sh took it on the other side and answered with one object. Two commands kept that promise — add --json and publish --yes --json. Everything else was a screen for a person: the listing, a post's properties, the queue, the trash. A program that wanted to see what was in the archive had nothing to call.
1.10 opens the door wide, and that is exactly why it is careful about what may come through.
Every screen as data
Every screen a person reads before acting, and every action it offers, now has a --json form: one object on standard output and nothing else, every key always present. A refusal is an object too, {"ok": false, "error": "not_found", …}, with a zero exit — the status says whether an answer arrived at all, the object says what it was. A program that has to test whether a key exists is a program writing its own parser; that is what this prevents.
Three rules follow from "nobody is at the keyboard". A question the dialog asks is a flag: the slug typed back to confirm a delete is --yes, the date the schedule dialog asks for is --at, "shift the rest of the queue forward?" is --compact. Without the flag the answer is no — a program that did not say it did not mean it. Those flags are for --json only: delete <slug> --yes at the terminal is refused, because there the dialog is there to ask, and a flag that silently skips a confirmation is exactly how a shell history deletes posts. And a post is named by its slug and never guessed: the same slug in two years is ambiguous_slug where the terminal would show both and ask.
What stands in the door
A program's key gets no shell. Its line in authorized_keys carries a forced command, scripts/remote.sh, and that can do two things: run one command, and take a delivery. run reads one line of JSON from its input, {"args": [...]} — the words exactly as ./blog.sh would get them — and before the engine sees them, checks each one against a list: the commands a program may run, and for each the flags it may give. Nothing is ever read as shell; a series called "Procházky a výlety" travels as one word and arrives as one argument.
Just as important is what is not on the list, on purpose. add — a post arrives as a delivery, which receive.sh takes with every check it has; for a sender that cannot close its stream, a line saying end finishes it. export, preview, the wizards. doctor --online and check --online, because they reach the network on the author's accounts; --strip-location, because it rewrites photographs; --repair. Each of those either opens an editor, or asks a question, or writes outside the archive. A program with a key is not the author at their desk.
What else the engine can tell a program: version --json carries the site's name, its colours for light and for dark, and the languages it publishes, so a program can dress in the blog's colours. And a request may say "lang": "cs" — the findings of check and doctor, and what pair itself refuses, then come in the program's language, not the blog's. Only those three, because they build nothing; what the door refuses before the engine is run it says in English, as it always has.
A key nobody types
Getting a program its key meant copying out its public half and writing a line into ~/.ssh/authorized_keys by hand. That stays. Beside it, ./blog.sh pair draws a QR code in the terminal, the program reads it and is connected — nobody types an address, an account name or a key anywhere.
The code carries three things: where to connect, the fingerprints of the server's own keys, so the program knows it reached the right machine, and a key good for one thing, once, for ten minutes — handing in the public half of a key the program made itself. The engine then writes an ordinary line for it, held to scripts/remote.sh like a line written by hand. The key the program lives on never leaves the device; the one that was on the screen opens nothing once it has been used.
pair --list numbers the devices, pair --revoke takes one out — by number, name or fingerprint; with nothing after it, the terminal asks which. A program may do the same for itself: --list tells it which of them it is, and --revoke with no argument takes it out, itself and no other. Who is asking, the engine knows from a word on that device's own line, --device=<fingerprint>, never from anything the request claims. Showing a code and taking out another device stay at the terminal.
About the file that decides who may log in: the engine writes to it only lines beginning restrict, with a command of its own installation, touches no line it did not write, and leaves the rest of the file as it was. A blog in a container still wants its line written by hand; doctor mentions the line left behind by a code nobody used.
Who the door is for
For a script that prints the queue every Sunday. For a program that reads the drafts and schedules one of them. For anything that can speak JSON over SSH and has no keyboard. The door is not open so that everything can be reached through it, but so that it can be said precisely what can: the list of allowed words is what the engine knows about the program, and it asks nothing else.
The earlier piece on what is left to get wrong is here: What's left to get wrong.

Comments