Updating Studio from Studio
Status: Linux built, 2026-10-16; macOS and Windows written down, waiting on the owner's
signing. This is the fifth product audit's item 2 (docs/audits/product-audit-2026-10f.md).
Until now the update check (RwUpdateCheck, m11-release.md) only said a newer release was out.
Every release meant finding the download page, fetching the package, installing it, and on a Mac
or Windows getting past the same warnings again. 0.12.0 and 0.12.1 came a day apart.
Now Studio offers "Update to 0.12.2". It downloads the package for its platform and checks it against the release feed's signature, not only against a checksum the feed says. It puts the package in place when Studio restarts and comes back on the same show. If anything fails, the old version stays and Studio says why.
The feed, and what signs it
The feed is site/releases/latest.json on reelwright.diy, as before. tools/site/releases.ts
writes it. It names the version and, for each package, its file, size, SHA-256 and URL.
- The signature.
latest.json.sigbeside it is an Ed25519 signature over the exact bytes oflatest.json, in base64.feed.xml.sigsigns the Atom feed the same way. Nothing reads the Atom feed's signature yet; it is there for a reader that wants it. - The key.
node tools/release/feed-key.ts --generatemakes the key once. The private half goes in~/.config/reelwright/release-feed-ed25519.pem(mode 0600, PKCS#8). It is never committed and never printed. The command prints only the public half, 32 bytes in base64, which is written intocore/src/update/feed-key.ts. Studio ships that public half in its core. - Signing a release.
releases.tssigns both files whenever it finds the key: inRELEASE_FEED_KEY, else at the path above. A feed whose packages are hosted (they have URLs) and no key to sign it is refused, so no hosted release goes out unsigned. A version recorded with no packages yet (version.ts --set,--no-artifacts) needs no key, and any old signature is removed, since it would no longer match. - Changing the key (not built). The core would trust a list of keys. A release signed with the old key carries the new public half, and the release after it is signed with the new key.
The core does the checking
Studio's GDScript has no Ed25519, so the core checks everything. The core is the same Node
program that ships beside Studio in every package (build-core.ts), and Studio already runs it
for one-off commands (RwShowUpload).
reelwright update check [--feed url] [--json]readslatest.jsonand its.sig, checks the signature with the key the core carries, and says whether a newer release is out. It also says whether this install can put that release in place itself, and why not when it cannot.reelwright update get [--feed url] [--target file] [--json]does the same check, then downloads the package for this platform. It writes the package to a hidden file next to the one it will replace, so the swap is a rename on one disk, and checks the size and SHA-256 against the signed feed. Only then does it rename the file to….verifiedand write the swap script beside it. It prints one JSON line per stage ({"stage": "downloading", "received", "total"}) and a last line,{"done": {"version", "file", "swap"}}or{"error": {"code", "message"}}.REELWRIGHT_UPDATE_URLnames another feed, as it does for the check, for tests and mirrors. The key is never taken from the environment. A local feed has to be signed with the real key or it is refused, the same as any other feed.
The feed's URL comes from Studio, but the download's URL and checksum only ever come from the signed feed. The core reads the feed itself and never takes either from Studio.
Putting it in place on Linux (the AppImage)
An AppImage is one file, and APPIMAGE names it. The core checks that its folder can be written.
When it cannot, or Studio runs from the tar.xz folder or a checkout, Studio offers what's new and
the download page, as before.
Restart to update runs the swap script with /bin/sh (the host's shell, not one inside the
mounted AppImage) and quits Studio. The script:
- Waits for Studio to exit.
- Moves the running AppImage aside to
….previous. - Renames the verified one into its place. The name stays the same, unless it carries the old
version, as
Reelwright-0.12.1-x86_64.AppImagedoes. Then it takes the new version's name. - Starts it on the same show, with
REELWRIGHT_UPDATE_MARKnaming a file to write. - Waits up to 180 s for the new Studio to write that file. Studio writes it once the show it was given is open, or once its first screen is up when no show was given.
- If the mark comes, it removes the old file.
If the new Studio exits first, or the time runs out, the script stops it, deletes the new file,
puts the old one back, and starts it with REELWRIGHT_UPDATE_FAILED=0.12.2.
That Studio says "The update to 0.12.2 did not start, so Reelwright stayed on 0.12.1." The old version
is never deleted until the new one has started. A person can always start the old one by hand
from ….previous while the script runs.
In Studio
- The rail's notice (and the first screen's) says "Update to 0.12.2" when this install can put that release in place. Otherwise it says "0.12.2 is out", with what's new, as before.
- Pressed, it downloads with the percentage on the rail, then says "Restart to update". A failure is said in plain words and the notice offers the update again. A feed or package that fails its check is refused, never put in place.
REELWRIGHT_UPDATE_AUTO=1, for checks only, presses Update and then Restart as soon as each is offered, so the Linux watch can update an installed AppImage without a person.
macOS and Windows (the owner's part, and what is left)
Neither is built. What each needs:
- macOS. Publish the app as a
.zipofReelwright Studio.appbeside the.dmg, since a disk image cannot be swapped in place. The core downloads and checks it the same way. A script like Linux's waits for Studio to quit, moves the old.appaside, unzips the new one in its place (ditto -x -k), opens it withopen -n, and keeps the old one until the new one marks its start. The core downloads with Node, not a browser, so no quarantine attribute is set. Even so, an app that is not notarized can still be stopped by Gatekeeper once it is moved. The owner must supply a Developer ID certificate and an Apple ID (or App Store Connect key) for notarization (codesign.tssigns ad hoc today). With those, the update is silent. - Windows. The core downloads
-setup.exeand checks it the same way. A small.cmdwaits for Studio to exit, runs the installer silently (/S, the NSIS installer supports it, andsmoke.tsalready installs it that way), and starts Studio again. The installer keeps the old install until it succeeds only ifreelwright.nsiinstalls side by side and switches over. That is to be done: today it installs over the old files. SmartScreen's "More info, Run anyway" goes away only with a code-signing certificate, which the owner must supply. An EV certificate gets past it at once; a standard one builds reputation over time. - Both are smoke-tested beside the release's package smoke tests once built. The owner confirms the first real update on a Mac and on Windows by hand.
Proof
- Core tests:
- the signature check: a good feed, a byte changed, the wrong key, no signature
- the package check: the wrong size, the wrong SHA-256
update checkandupdate getagainst a local server- the swap script with stand-in AppImages: one that marks its start, one that exits, one that never marks
- Engine tests: the notice's states, the updater over a stand-in core.
- The Linux watch,
tools/release/update-watch.ts:- It builds this tree as two AppImages, its version and the next (0.12.1 and 0.12.2), with a feed key of its own in the core in place of the release key, which it never reads.
- It serves the next version's AppImage and feeds that
tools/site/releases.tssigned, from its own server on 127.0.0.1. - It installs the older AppImage in a fresh home and checks that Studio updates itself and comes back as the next version with the show open, with only the new file left.
- A feed signed by another key must be refused: nothing downloaded, the old file unchanged and still running.
- A signed release that does not start must be undone: the old file back, unchanged, and running again on the show, saying why.
- A package that does not match its feed is refused in the core tests.