Files
miniSCP/docs/build-and-release.md
T
jgamboaandClaude e3a5750b01 Merge origin/main into feat/7css-skin
Brings in 6c05e39 (re-sign electrobun's CLI binary so the app launches).

Two conflicts, both resolved to keep both sides:
- package.json: start keeps scripts/start.ts (injects the Linux NVIDIA env)
  behind main's fix-electrobun-cli re-sign; build and postinstall take
  main's re-sign step (the resize watchdog is already gone).
- docs/build-and-release.md: the release usage keeps the linux-x64 target
  and gains main's fix-electrobun-cli step.

scripts/release.ts and docs/gotchas.md auto-merged with both sides intact:
the Linux target + tarball wrapper, and the arm64/x64 CLI re-sign.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-23 16:25:34 +02:00

157 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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: starts `ng serve` (HMR) and
`electrobun dev --watch`, with `DEV_SERVER_URL` pointing 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 via `bunfig.toml`, root AND ui/)
fakes a Node version the Angular CLI accepts under Bun. Both preloads are
load-bearing.
- `scripts/fix-electrobun-cli.ts` re-signs electrobun's prebuilt CLI binary,
whose upstream ad-hoc signature is invalid — macOS SIGKILLs it and the app
silently never launches, with exit code 0 (see [gotchas.md](gotchas.md)). It
runs from `postinstall` **and** at the start of `dev`/`start`/`build`/
`release`, because the CLI is downloaded lazily on first use rather than at
install time; when the binary is missing it provokes that download first, so
a fresh checkout self-heals. Equally load-bearing — removing it means
`bun run start` does nothing at all.
## Release builds
`bun scripts/release.ts [--target mac-arm64|mac-x64|win-x64|linux-x64]`
(default: host).
Steps: gen-version → fix-electrobun-cli → gen-icons → Angular build →
`electrobun build --env=stable` → copy/rename artifacts into
`release/<target>/miniSCP-Build<n>-<target>.*`.
Facts the script encodes:
- **`--env=stable` is mandatory for releases** — the CLI defaults to the dev
channel (different app name `miniSCP-dev`, no distributable artifacts).
- **No OS cross-compile.** The CLI builds for its host (its `targets` config
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-x64` on a non-Windows host and `linux-x64` on 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.gz` from the GitHub
release matching the installed version, cached at
`~/.cache/miniscp/electrobun-cli-x64-<ver>/`) and runs it directly —
Rosetta translates it, the CLI sees `process.arch === "x64"`, downloads the
`dist-macos-x64` core into the electrobun package dir on first use, and
packages a genuine x64 app. Note: Bun's `fetch`→`Bun.write` streaming hung
on the GitHub redirect chain; the script uses `curl -fsSL` deliberately.
- **Both CLI binaries need re-signing.** The x64 CLI fetched for the Rosetta
path carries the same broken upstream signature as the arm64 one, so
`x64Cli()` re-signs it after download *and* on cache reuse; the native path
is covered by the `fix-electrobun-cli.ts` step up front.
- **Core binary downloads**: the installed npm package ships mac-arm64
binaries only; other `dist-<os>-<arch>` cores are fetched from
`github.com/blackboardsh/electrobun/releases` at 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 under
`build/stable-win-x64/` (layout to confirm on the first real Windows build —
the step warns instead of failing); Linux → only
`stable-linux-x64-miniSCP.tar.zst` + `update.json`, i.e. the updater feed and
nothing installable, so the script's **`.tar.gz` of the assembled app folder**
under `build/stable-linux-x64/` IS the release artifact (same best-effort
warn). No AppImage/.deb/.desktop — unpack and run the `miniSCP.sh` wrapper the
script writes beside `bin/launcher` (it applies the NVIDIA/DMABUF workaround,
which the app cannot set for itself — see gotchas.md).
- **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 in `site/assets/`.
- `site/Caddyfile` — one flat `/dl/*` folder with `file_server browse` + range requests, and
per-type `Cache-Control`: `*.patch` immutable (unique hashed names), `*update.json` `no-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) updater `baseUrl`. Publishing = drop this machine's `artifacts/stable-*`
into `/dl/`; installers overwrite in place, `.patch` files 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 after `scripts/release.ts`; copies `artifacts/stable-*`
and renames the build-numbered win portable zip to the stable `stable-win-x64-miniSCP-portable.zip`.
`MINISCP_DL_DIR` overrides the target (stage locally, then rsync to the server).
- `release.baseUrl = https://miniscp.com/dl` is set in `electrobun.config.ts`; `miniscp.com` is
fronted by an external proxy for TLS (Caddy itself stays on `:80` behind it). The value is baked
into `version.json` at build time, so only builds made after it was added check for updates.
With `generatePatch` (default true), release builds now need **build-time network to
`miniscp.com/dl`** so 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](architecture.md) for the runtime flow.
- **Backend** ([src/bun/index.ts](../src/bun/index.ts)): `Updater.checkForUpdate()` runs ~5s after launch and
every 6h; on `updateAvailable` it pushes `rpc.send.updateStatus({phase:"available",…})`.
`Updater.onStatusChange` streams `download-progress` (0–100) → `updateStatus{downloading}`.
`checkForUpdate` self-guards the `dev` channel, so `bun run dev` never shows the pill.
- **RPC** ([src/shared/rpc.ts](../src/shared/rpc.ts)): requests `checkForUpdate` / `downloadUpdate` /
`applyUpdate` (handlers in [rpc-handlers.ts](../src/bun/rpc-handlers.ts) call the electrobun `Updater`);
push message `updateStatus`.
- **UI**: `UpdaterService` ([ui/src/app/core/updater.service.ts](../ui/src/app/core/updater.service.ts)) holds signals
(`state`/`version`/`progress`); the tab-bar "update pill" ([tab-bar.ts](../ui/src/app/skins/shadlike/tab-bar/tab-bar.ts))
is one accent (`bg-primary`) control that announces → fills as a progress bar while downloading →
"Restart to apply". `state` is `available → downloading → ready` (ready derived from the
`downloadUpdate` response). App-menu "Check for Updates…" ([menu.ts](../src/bun/menu.ts)) routes a
`check-for-updates` menu action into `UpdaterService.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).