`bun run start` died on Linux with ERR_DLOPEN_FAILED (missing libayatana-appindicator3.so.1). Fixing the environment exposed the real gap: electrobun's ContextMenu.showContextMenu is a documented no-op on Linux, and since the UI suppresses the webview's own right-click menu, EVERY panel context menu was silently dead there — Send to Other Panel, Open, Rename, Permissions, Delete, Copy Path, Reveal and the column picker all unreachable. Electrobun itself logs "Application menus are not supported on Linux. Implement menu UI in your webview HTML instead." at boot. - Context menus are now HTML on EVERY platform (ui/src/app/context-menu.ts + context-menu.service.ts), not just as a Linux fallback: one implementation cannot drift from a second one. The item builders are ported verbatim from the old bun menu, so ordering, per-platform labels and the Windows-local chmod hide are unchanged, and picks are emitted as the same `ctx-*` / `col:*` strings through RpcService.emitMenuAction — App.handleMenuAction is untouched and still the single dispatcher for both the macOS menubar and right-clicks. Adds what the native menu gave for free: arrow/Enter/Escape keys, viewport edge-flip, close on blur/resize/scroll. The Edit item now shows the real F4 hint instead of the native menu's "e" mnemonic. - New shellItemAction RPC replaces showRowMenu/showColumnsMenu — the only part the webview can't do itself (reveal, open-with-default, copy path). Deletes src/bun/context-menu.ts entirely. - release.ts gains a linux-x64 target. Electrobun emits only the .tar.zst updater blob on Linux, so the artifact is a .tar.gz of the assembled app folder, mirroring the Windows portable zip. - Docs: platform-notes gets a Linux runtime-dependency table (AppIndicator is linked HARD — a missing lib is a boot failure, not a degraded tray), the ldd diagnostic, GNOME tray caveat, known issues and a first-boot smoke test; gotchas gets the ERR_DLOPEN_FAILED trail and the benign GLX/GBM startup noise; architecture/build-and-release/README updated accordingly. Verified: backend `tsc --noEmit` clean, `bun run build:ui` clean (399 kB initial), app boots on Linux with no errors beyond the documented benign warnings. NOT verified on macOS/Windows — the native menu is retired on those platforms too, so the menus need eyeballing on a Mac before the next release. Co-Authored-By: Claude <noreply@anthropic.com>
8.7 KiB
Build & release
Versioning — "Build n"
Sublime-style: the version IS the commit count. scripts/gen-version.ts runs
git rev-list --count HEAD + git rev-parse --short HEAD and writes the
gitignored src/shared/version.gen.ts (BUILD = {number, commit, date, electrobun}). It is regenerated by postinstall and at the start of every
dev/build/start/release script; committing it would bump the count it
records, hence gitignored. Fresh checkouts fail tsc until bun install has
run once (postinstall creates the file). BRANDING.version stays
semver-shaped (0.1.<n>) for electrobun's config/updater; BRANDING.buildLabel
("Build n") is what humans see (About box, options popover).
Dev loop
bun run dev— gen-version → scripts/dev.ts: startsng serve(HMR) andelectrobun dev --watch, withDEV_SERVER_URLpointing the webviews at the dev server (only honored on the dev channel and only if the server answers).bun run build— gen-version → Angular build →electrobun build(dev channel — fine for boot checks, NOT a release).scripts/node-version-shim.ts(preloaded viabunfig.toml, root AND ui/) fakes a Node version the Angular CLI accepts under Bun. Both preloads are load-bearing.
Release builds
bun scripts/release.ts [--target mac-arm64|mac-x64|win-x64|linux-x64]
(default: host).
Steps: gen-version → gen-icons → Angular build → electrobun build --env=stable → copy/rename artifacts into
release/<target>/miniSCP-Build<n>-<target>.*.
Facts the script encodes:
--env=stableis mandatory for releases — the CLI defaults to the dev channel (different app nameminiSCP-dev, no distributable artifacts).- No OS cross-compile. The CLI builds for its host (its
targetsconfig field is unread in 1.18.1); packaging needs host tools (codesign/hdiutil on macOS, rcedit/PowerShell on Windows). Hence one script run per artifact, on the matching machine;win-x64on a non-Windows host andlinux-x64on a non-Linux host both error out. - mac-x64 from the arm64 Mac (Rosetta): the script downloads electrobun's
x64 CLI binary once (
electrobun-cli-darwin-x64.tar.gzfrom the GitHub release matching the installed version, cached at~/.cache/miniscp/electrobun-cli-x64-<ver>/) and runs it directly — Rosetta translates it, the CLI seesprocess.arch === "x64", downloads thedist-macos-x64core into the electrobun package dir on first use, and packages a genuine x64 app. Note: Bun'sfetch→Bun.writestreaming hung on the GitHub redirect chain; the script usescurl -fsSLdeliberately. - Core binary downloads: the installed npm package ships mac-arm64
binaries only; other
dist-<os>-<arch>cores are fetched fromgithub.com/blackboardsh/electrobun/releasesat build time — network to GitHub is a build-time requirement on every fresh machine/runner. - Artifact shapes (stable channel, in
artifacts/): macOS →stable-macos-<arch>-miniSCP.dmg+.app.tar.zst+update.json(updater feed); Windows →stable-win-x64-miniSCP-Setup.zip(self-extracting installer →%LOCALAPPDATA%) plus the script's own portable zip made from the assembled app folder underbuild/stable-win-x64/(layout to confirm on the first real Windows build — the step warns instead of failing); Linux → onlystable-linux-x64-miniSCP.tar.zst+update.json, i.e. the updater feed and nothing installable, so the script's.tar.gzof the assembled app folder underbuild/stable-linux-x64/IS the release artifact (same best-effort warn). No AppImage/.deb/.desktop — unpack and runbin/launcher. - Linux builds need the AppIndicator dev libraries present to RUN (not to
build) — see platform-notes.md; a packaged tarball started on a machine
without them dies at
dlopen. - The script filters artifacts by app name, so stale artifacts from older brandings don't get collected.
CI (Gitea Actions)
.gitea/workflows/release.yml — tag-triggered (build-*), macos + windows
runner matrix, each running the same release script; inert until act_runners
with those labels are registered. Needs fetch-depth: 0 (the build number is
the commit count). Runner prereqs are commented in the workflow file.
Download host (site/ + Caddy)
site/ is a self-contained landing page and download host, served by Caddy on an
internal box (plain HTTP :80, no domain/TLS). Deploys to /srv/miniscp/.
site/index.html— pure static, no JS, tinygrad-minimal. Download buttons link to fixed stable filenames (/dl/stable-macos-arm64-miniSCP.dmg, …) so the page never changes per release. Logo/screenshot<img>slots are commented in, images live insite/assets/.site/Caddyfile— one flat/dl/*folder withfile_server browse+ range requests, and per-typeCache-Control:*.patchimmutable (unique hashed names),*update.jsonno-cache(updater must see new versions), installers (*.dmg *.zip *.tar.zst)max-age=300(stable names overwritten each release). Landing page has no SPA fallback → unknown paths 404. Path matcher gotcha: use suffix globs like*update.json, not*/*update.json.- One flat folder, no per-build dirs — this layout IS electrobun's
artifacts/feed, so it doubles as the (future) updaterbaseUrl. Publishing = drop this machine'sartifacts/stable-*into/dl/; installers overwrite in place,.patchfiles accumulate (never delete them, or older installs lose their incremental-update trail). Tradeoff: only the latest full installer is downloadable; old full versions are overwritten. site/deploy.sh— run per build machine afterscripts/release.ts; copiesartifacts/stable-*and renames the build-numbered win portable zip to the stablestable-win-x64-miniSCP-portable.zip.MINISCP_DL_DIRoverrides the target (stage locally, then rsync to the server).release.baseUrl = https://miniscp.com/dlis set inelectrobun.config.ts;miniscp.comis fronted by an external proxy for TLS (Caddy itself stays on:80behind it). The value is baked intoversion.jsonat build time, so only builds made after it was added check for updates. WithgeneratePatch(default true), release builds now need build-time network tominiscp.com/dlso electrobun can fetch the currently-published version to diff a.patch(the first build after enabling it has nothing to diff and just ships the full artifacts).
Auto-updater (app side)
Wired on top of the download host above. See the "Auto-updater" section in architecture.md for the runtime flow.
- Backend (src/bun/index.ts):
Updater.checkForUpdate()runs ~5s after launch and every 6h; onupdateAvailableit pushesrpc.send.updateStatus({phase:"available",…}).Updater.onStatusChangestreamsdownload-progress(0–100) →updateStatus{downloading}.checkForUpdateself-guards thedevchannel, sobun run devnever shows the pill. - RPC (src/shared/rpc.ts): requests
checkForUpdate/downloadUpdate/applyUpdate(handlers in rpc-handlers.ts call the electrobunUpdater); push messageupdateStatus. - UI:
UpdaterService(ui/src/app/updater.service.ts) holds signals (state/version/progress); the tab-bar "update pill" (tab-bar.ts) is one accent (bg-primary) control that announces → fills as a progress bar while downloading → "Restart to apply".stateisavailable → downloading → ready(ready derived from thedownloadUpdateresponse). App-menu "Check for Updates…" (menu.ts) routes acheck-for-updatesmenu action intoUpdaterService.check(). - Unsigned is fine:
applyUpdate()strips the macOS quarantine xattr before relaunch, so the swapped bundle isn't Gatekeeper-blocked.
Icons
scripts/gen-icons.ts (resvg, dev-time): renders assets/logo.svg into the
macOS iconset + icon.png, a multi-size assets/icon.ico (Windows installer/
taskbar, wired via electrobun.config.ts win.icon), the macOS tray template
PNG from assets/tray/tray-glyph.svg, and a small colored Windows tray PNG.
Rerun after touching either SVG; outputs are committed (build inputs).
electrobun config notes
electrobun.config.ts: app identity comes from BRANDING; build.bun.external
must keep ["cpu-features", "*.node"] (ssh2's optional native accelerators —
bundling them breaks the build; runtime falls back to pure JS);
build.copy ships the Angular dist and tray assets into views://;
bundleCEF: false everywhere (system WebKit / WebView2).