From 209e9163437c7ff4fa3743e5aa9fd0207e13b27d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Mon, 10 Aug 2026 18:20:46 +0000 Subject: [PATCH] app store: publish a sidecar's assets to public/plugins// MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Real PNG icons are coming, so this is the path they arrive by: a sidecar ships its assets beside its own code, and install copies them to public/plugins// where one static route serves them. Copied rather than served in place because a sidecar shipping from its own repository has its assets wherever that repository was unpacked, which is not a path the web server can be taught at build time. One predictable destination means the serving rule never has to know how many plugins exist or where any came from. It also makes assets a property of the INSTALL: uninstall removes them, and a plugin nobody installed serves nothing. Needed a new route, and the reason is a trap worth recording. `publicRoutes` in server.tsx is built by globbing ./public at BOOT, so anything copied there afterwards is invisible to it — the first install of a plugin would show a broken image until the server was restarted, and "install it, then restart to see the icon" is not an install. `/plugins/*` resolves per request, like /novnc/* and /vendor/* already do. Unlike those two it answers 404 rather than 500 for a missing file: an unpublished icon is an ordinary state on a fresh machine, and a 500 would put a red line in the log for every dock render. Proven end to end with a real asset: slskd's icon moved from public/slskd.png into the sidecar's own assets/, published against an ALREADY RUNNING server, and fetched at 200 with the right bytes and content-type — 404 before publishing, no restart between. public/plugins/ is gitignored: it holds copies, and the originals live with each sidecar. Co-Authored-By: Claude Opus 5 --- .gitignore | 3 + src/server.tsx | 14 ++++ src/servers/app-store/assets.ts | 68 ++++++++++++++++++ src/servers/app-store/catalogue.ts | 8 ++- .../app-store/templates/slskd/assets/icon.png | Bin 0 -> 3570 bytes 5 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 src/servers/app-store/assets.ts create mode 100644 src/servers/app-store/templates/slskd/assets/icon.png diff --git a/.gitignore b/.gitignore index 6b8d93bf..cad2da45 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,6 @@ src/apps/officer-web/index.gen.html # scratch scripts — never commit these *.tmp.ts + +# Sidecar assets published at install time — copies of files that live in each sidecar's own tree. +public/plugins/ diff --git a/src/server.tsx b/src/server.tsx index b853b98a..65848d0b 100644 --- a/src/server.tsx +++ b/src/server.tsx @@ -221,6 +221,20 @@ const server = serve({ const file = Bun.file(`public${new URL(req.url).pathname}`); return new Response(file); }, + // Icons and assets belonging to installed sidecars, copied to public/plugins// by the installer. + // + // Served by this dynamic route rather than by `publicRoutes` above, which is a snapshot taken by + // globbing ./public at BOOT. A plugin installed while the server is running would not be in that map, + // so its icon would 404 until the next restart — and "install it, then restart the server to see the + // icon" is not an install. + '/plugins/*': async (req) => { + const file = Bun.file(`public${new URL(req.url).pathname}`); + // 404 rather than letting a missing file surface as a 500. An icon that has not been published + // yet is an ordinary state — the plugin is not installed — and a 500 would put a red line in the + // log for every dock render on a fresh machine. + if (!(await file.exists())) return new Response('Not found', { status: 404 }); + return new Response(file); + }, // Vaultwarden notifications hub: upgrade the WebSocket here (proxied to upstream by vaultWebsocket); // everything else on this path (SignalR long-poll negotiate/poll) falls through to the HTTP proxy. '/api/vault/notifications/*': (req, server) => { diff --git a/src/servers/app-store/assets.ts b/src/servers/app-store/assets.ts new file mode 100644 index 00000000..1839fd58 --- /dev/null +++ b/src/servers/app-store/assets.ts @@ -0,0 +1,68 @@ +import { cp, rm, stat } from 'node:fs/promises'; +import { join } from 'node:path'; + +// Copying a sidecar's own assets — its icon, and whatever else it ships — to where the browser can +// fetch them. +// +// src/servers/app-store/templates/