Repository navigation
web: opt-in self-hosted update notice and admin-triggered update (feature self-hosted-update) - #501
Dejan-Port wants to merge 1 commit into
Conversation
…ture `self-hosted-update`) For self-hosted web deployments only; off by default and never part of the desktop app. With `--features self-hosted-update`, deckcraft-web draws craft-update's notice above the presentation: every visitor sees that a newer release exists, and the administrator can sign in and choose Update now. The update is performed on the server by `craft-updated` (craft-libs' craft-update crate, loopback-only behind a reverse-proxied path), which checks the sign-in on every request; the browser can't choose what gets built. - apps/deckcraft-web: the feature, `updates.rs` and two lines in the web shell; tests drive the real SlideApp with the notice for the visitor, wrong-password, administrator, running and reload states. - xtask ci: a `self-hosted-update` step (clippy, tests and the wasm check with the feature on). - docs/self-hosted-updates.md and a README pointer. Depends on storytold/craft-libs#68 (craft-update); the rev is pinned to that PR's head commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g comment tools (storytold#501) Picking any comment tool opened the Comments panel whenever no right panel was open, so closing it was undone by the next tool switch. Now closing Comments (its close button or the rail icon) is remembered, across restarts, and comment tools leave it closed until the user opens it again (rail, View menu, Comments ▸ list, or a Reply action). Switching to or closing another panel doesn't count. Opening a document with comments still shows them, as Acrobat does. Claude-Session: https://claude.ai/code/session_01VNJqS2dEQawHJn1qTRJiKp Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
FredApps
left a comment
There was a problem hiding this comment.
ReviewerA — source-read COMMENT (not an approval)
I read the head of the diff: the web build gets an opt-in self-hosted update notice and an admin-triggered update. The lock gains block-buffer 0.12.1 and hybrid-array, i.e. a newer RustCrypto stack, probably for hashing or signature checking of the update. An update mechanism that downloads and replaces the code a server serves is security-sensitive: it must verify a signature or at least a pinned hash of what it fetches, fetch only from the configured origin over HTTPS, require an authenticated administrator for the trigger (not any visitor), and never run without the host operator opting in. I read the lock only and cannot say whether these are satisfied; a maintainer should read the update path line by line. A newer block-buffer also means a second version of it beside the one in the tree, which should be explained. I have not run it, and no exact-head CI result exists for this head. Nothing was run.
I ran nothing and have no exact-head CI result for this PR, so this is not an approval. Where a diff was long I read its code hunks rather than every generated or documentation line.
Status: waiting_author.
Summary
Adds an opt-in, off-by-default
self-hosted-updatefeature toapps/deckcraft-web, for one case only: people who host DeckCraft's web build on their own server. It is not for desktop users. The desktop app, the CLI and the release packages are untouched, and a web build without the feature makes no update requests and shows nothing.Why. We run self-hosted web deployments of GridCraft, WordCraft and DeckCraft. Nothing told us when a new release came out, and deploying one was manual, so we fell two releases behind (deployed 0.4.0, upstream 0.6.1). I know this is outside what most DeckCraft users need, which is why it's behind a feature flag and confined to the web shell. If you'd rather not carry it, that's understandable.
With
trunk build --release --features self-hosted-update:The notice, the browser client and the companion service live in the new
craft-updatecrate proposed in storytold/craft-libs#68. This PR pins it the same way ascraft-print/craft-ui, withgit = "https://github.com/storytold/craft-libs", but therev(9b85a7c3973b6c799a28553a5376789d33846aa0) is that PR's commit, not a commit on craft-libsmain. Cargo can fetch it from the upstream repo through the PR ref, so this builds today, but:revinapps/deckcraft-web/Cargo.toml(andCargo.lock) has to be re-pinned to the merged commit on craft-libsmain. If macOS: open presentations from Finder, the Dock andopen -a#68 is squash-merged,9b85a7cwon't exist onmainat all. I'll push that re-pin to this branch as soon as macOS: open presentations from Finder, the Dock andopen -a#68 merges, or you're welcome to do it while merging.How the update works, and why it isn't done by the app
apps/deckcraft-webis a client-side wasm bundle served as static files; there is no server part to extend. So the browser never builds or writes anything. The update is done bycraft-updated, a small companion process fromcraft-update(featureserver) that the self-hoster runs next to their web server:craft-update/);v<major>.<minor>.<patch>), the git clone, the build command (run without a shell) and the folders come from its private0600config file. The admin password is stored hashed (salted SHA-512 repeated 210,000 times, the ECMA-376 construction GridCraft already uses for workbook protection);Setup is a server-side command (
craft-updated setup), not a first-run web page, so whoever reaches a fresh site first can't claim the admin account. Full details and limitations are in craft-libs#68 and the crate README.DeckCraft didn't depend on craft-libs before (#66 adds
craft-print), so this also addssha20.11 and its small RustCrypto dependencies toCargo.lock. They are only compiled with the feature.Changes
apps/deckcraft-web/Cargo.toml: theself-hosted-updatefeature (optionalcraft-updatewith itseguiandwebfeatures) andegui_kittestas a dev-dependency.apps/deckcraft-web/src/updates.rs(compiled only with the feature):Updates, which draws craft-update's notice at the top of the web shell'sUibefore the app. DeckCraft has no interface translations yet, so the notice is in English.apps/deckcraft-web/src/web.rs,main.rs: one field and one call inWebShell, behind#[cfg(feature = "self-hosted-update")].xtask: aself-hosted-updatestep incargo xtask ci(clippy-D warnings, the tests and the wasm32 check with the feature on), since every other step builds without it.docs/self-hosted-updates.mdand a README pointer.This is web-shell chrome, not a document command, so it doesn't go through the engine's command catalog; nothing below
apps/changes.Verification
cargo xtask ciwith stable 1.99, every step passing: fmt, clippy, test, assets, layers, wasm, self-hosted-update. The newself-hosted-updatestep runscargo clippy --all-targets -p deckcraft-web --features self-hosted-update -D warnings, the tests, andcargo check --target wasm32-unknown-unknownwith the feature. The existingwasmstep checks the build without it.apps/deckcraft-web/src/updates.rs(egui_kittest) drive the realSlideAppbelow the notice, in the web shell's drawing order:craft-updateditself rejects missing, forged and expired sessions and that a request can't choose what gets built, is tested in craft-libs#68. There I also ran a manual end-to-end test of the real binary: a real GitHub release check, git fetch and checkout of the tag, build, switching the live link, and both emails sent to a local SMTP sink.trunk build --release --features self-hosted-updateserved behind a real reverse proxy and driven in a browser. The wasm32cargo checkwith the feature passes, but I didn't load the bundle in a browser against a runningcraft-updated.🤖 Generated with Claude Code