# The release of storebound: one version tag (v0.8.0) -> one Forgejo release with the Linux and Windows installers. # # push of a tag v* checks (tag = package.json version, every version copy equal), the full CI, the # AppImage + deb (Linux runner, Docker) and the NSIS installer (Windows runner, VM), the # privacy scan of every artifact, then ONE release "storebound " with short notes # (tools/forgejo-release.ts notes: the CHANGELOG summary, install line, links), the # installers and SHA256SUMS; older release entries are deleted afterwards (tags stay). # workflow_dispatch dry (default): the same jobs on this commit (or on `tag`), a DRAFT release test- # with the real files, read back, then deleted. dry off + tag: (re)make that tag's release. # # Not here: macOS. The DMG is built, signed and notarized on the owner's Mac and added to this release with # `bun tools/desktop-release.ts upload-forgejo --out --yes`; then `publish` (storebound.dev) and `npm publish` # (the owner's step). Order and details: docs/desktop-release.md. The updater signing key never reaches a runner. # Secrets (repo Settings > Actions > Secrets): RELEASE_TOKEN (token, scope write:repository: releases and assets), # FORBIDDEN_TERMS (the private terms list of the privacy scan; never printed), FORGEJO_ORIGIN (the server's IP: asset # uploads over 100 MB bypass Cloudflare). # Runners on the VDS (labels ubuntu-22.04 and windows): docs/desktop-release.md. name: release on: push: tags: ['v*'] workflow_dispatch: inputs: tag: description: Version tag to build (v0.8.0); empty = the selected branch's commit type: string default: '' dry: description: Dry run (a draft release test- with the real files, deleted at the end) type: boolean default: true windows: description: Build Windows too (always on for a tag push) type: boolean default: true env: BUN_VERSION: '1.4.0' CARGO_TERM_COLOR: never CARGO_INCREMENTAL: '0' jobs: prepare: runs-on: ubuntu-22.04 timeout-minutes: 10 outputs: ref: ${{ steps.mode.outputs.ref }} tag: ${{ steps.check.outputs.tag }} version: ${{ steps.check.outputs.version }} dry: ${{ steps.mode.outputs.dry }} steps: - name: Mode id: mode env: EVENT: ${{ github.event_name }} REF_NAME: ${{ github.ref_name }} SHA: ${{ github.sha }} IN_TAG: ${{ inputs.tag }} IN_DRY: ${{ inputs.dry }} run: | # a boolean input arrives as true / false (API: also "on" / "1"); only an explicit no is a real release case "$(echo "${IN_DRY:-true}" | tr A-Z a-z)" in false|0|off|no) want_dry=false;; *) want_dry=true;; esac echo "inputs: tag='$IN_TAG' dry='$IN_DRY'" if [ "$EVENT" = push ]; then ref="refs/tags/$REF_NAME"; dry=false elif [ -n "$IN_TAG" ]; then ref="refs/tags/$IN_TAG"; dry=$want_dry else ref="$SHA"; dry=true [ "$want_dry" = true ] || { echo "a real release needs a tag (input tag), or push the tag" >&2; exit 1; } fi echo "ref=$ref" >> "$GITHUB_OUTPUT"; echo "dry=$dry" >> "$GITHUB_OUTPUT" echo "event=$EVENT ref=$ref dry=$dry" - uses: actions/checkout@v4 with: ref: ${{ steps.mode.outputs.ref }} - name: Tag, package.json version and every version copy agree id: check env: TAG_IN: ${{ github.event_name == 'push' && github.ref_name || inputs.tag }} DRY: ${{ steps.mode.outputs.dry }} run: | curl -fsSL https://bun.sh/install | bash -s "bun-v${BUN_VERSION}" >/dev/null export PATH="$HOME/.bun/bin:$PATH" bun tools/desktop-version.ts V=$(bun tools/desktop-version.ts --print) if [ -n "$TAG_IN" ] && [ "$TAG_IN" != "v$V" ]; then echo "tag $TAG_IN is not v$V (package.json): bun tools/desktop-version.ts --set , commit, tag that commit" >&2; exit 1; fi grep -q "^## $V" CHANGELOG.md || { [ "$DRY" = true ] && echo "note: no CHANGELOG section for $V (the notes use Unreleased)"; } || { echo "CHANGELOG.md has no '## $V' section" >&2; exit 1; } if [ "$DRY" = true ]; then tag="test-$(git rev-parse --short=10 HEAD)"; else tag="$TAG_IN"; fi echo "version=$V" >> "$GITHUB_OUTPUT"; echo "tag=$tag" >> "$GITHUB_OUTPUT" echo "release $tag (version $V, dry=$DRY)" ci: needs: prepare runs-on: ubuntu-22.04 timeout-minutes: 60 env: CI: full steps: - uses: actions/checkout@v4 with: ref: ${{ needs.prepare.outputs.ref }} - uses: ./.forgejo/actions/linux-setup with: cache-key: ci rust-components: clippy - uses: ./.forgejo/actions/ci-run linux: needs: prepare # runner label: ubuntu-22.04:docker://ghcr.io/catthehacker/ubuntu:act-22.04 (glibc 2.35: the AppImage and the # deb then run on Ubuntu 22.04 and newer, Debian 12) runs-on: ubuntu-22.04 timeout-minutes: 90 env: # the path-free build keeps the target folder outside every home folder CARGO_TARGET_DIR: /tmp/storebound-target # linuxdeploy (AppImage) runs without FUSE inside the container APPIMAGE_EXTRACT_AND_RUN: '1' # linuxdeploy must not strip: the storebound sidecar is a `bun build --compile` executable whose JS payload strip # removes (it then starts as plain bun). The Rust binary is already stripped by Cargo (profile.release strip). NO_STRIP: '1' # WebKitGTK's DMABUF renderer leaves the window blank on some GPU / VM setups; the app sets this itself at # start (src-tauri/src/lib.rs linux_webkit_env) unless the user did; set here too for anything run in CI WEBKIT_DISABLE_DMABUF_RENDERER: '1' steps: - uses: actions/checkout@v4 with: ref: ${{ needs.prepare.outputs.ref }} - uses: ./.forgejo/actions/linux-setup with: cache-key: desktop packages: patchelf file squashfs-tools p7zip-full xz-utils - name: Install and check run: | bun install --frozen-lockfile bun tools/desktop-version.ts bun tools/build-sidecars.ts --target bun-linux-x64 # linuxdeploy (AppImage) sets the rpath of every executable with its own patchelf, which corrupts the bun # sidecar (segfault, then ldd fails in the gtk plugin). With the rpath already set (Ubuntu's patchelf, which # keeps it working), linuxdeploy leaves the file as it is. The sidecar needs only libc, so the rpath is inert. patchelf --set-rpath '$ORIGIN/../lib' apps/desktop/src-tauri/binaries/storebound-x86_64-unknown-linux-gnu apps/desktop/src-tauri/binaries/storebound-x86_64-unknown-linux-gnu version - name: Build AppImage and deb (path-free) working-directory: apps/desktop run: bun ../../tools/desktop-release.ts exec -- bunx tauri build --bundles appimage,deb --ci - name: Self-test the exact artifacts run: | V=$(bun tools/desktop-version.ts --print) B="$CARGO_TARGET_DIR/release/bundle" mkdir -p out && cp "$B/appimage/storebound_${V}_amd64.AppImage" "$B/deb/storebound_${V}_amd64.deb" out/ chmod +x out/*.AppImage (cd /tmp && rm -rf squashfs-root && "$GITHUB_WORKSPACE/out/storebound_${V}_amd64.AppImage" --appimage-extract >/dev/null && squashfs-root/usr/bin/storebound-desktop --self-test) rm -rf /tmp/deb && dpkg-deb -x "out/storebound_${V}_amd64.deb" /tmp/deb && /tmp/deb/usr/bin/storebound-desktop --self-test dpkg-deb -I "out/storebound_${V}_amd64.deb" | sed -n '1,20p' - name: Start the app under Xvfb (AppImage and deb) # --self-test checks only the sidecar; this opens the real window (GTK, WebKitGTK, the bundled libraries of the # AppImage) on a virtual display. "sidecar started" in host.log means the webview ran the UI and called the host. run: | sudo apt-get install -y -qq --no-install-recommends xvfb xauth dbus-x11 >/dev/null V=$(bun tools/desktop-version.ts --print) smoke() { #