Skip to content

Add a Linux desktop build target (AppImage, x64) - #1392

Merged
SawyerHood merged 3 commits into
get-bb:mainfrom
salemsayed:linux-appimage
Aug 13, 2026
Merged

Add a Linux desktop build target (AppImage, x64)#1392
SawyerHood merged 3 commits into
get-bb:mainfrom
salemsayed:linux-appimage

Conversation

@salemsayed

Copy link
Copy Markdown
Contributor

Summary

Adds a Linux x64 AppImage build target to apps/desktop. Linux is already a first-class bb host — bb-app ships "os": ["darwin", "linux"], docs/platform-support.md lists the Linux persistent host as supported, and Ubuntu is the required CI gate — but the desktop shell was macOS-arm64-only. This closes that gap with a deliberately minimal, additive change: packaging only, no CI or release-infrastructure changes.

Per CONTRIBUTING I know features normally start as an issue — happy to convert this into one and treat the branch as the linked prototype if you prefer; opening it as a PR so the concrete diff is easy to evaluate.

Work done

  • electron-builder: top-level linux block only (AppImage, x64, executableName: "bb" — the Linux default derives the binary name from the sanitized package name, yielding @bbdesktop). Existing mac/dmg/publish/npmRebuild/files config untouched; the config gatekeeper test keeps all existing assertions and gains Linux ones.
  • Scripts: desktop:build:linux (--publish never), dist:linux, package:linux, start:linux, mirroring the mac script names; a @bb/desktop#desktop:build:linux turbo task mirroring desktop:build.
  • Contract: @bb/desktop-contract platform widens z.literal("macos")z.enum(["macos", "linux"]) (info.ts only; the version-feed schema is untouched since no Linux feed exists).
  • Shell: platform/isMac are injected into pure modules as required args (matching how @bb/desktop unit tests already run on ubuntu-latest); only main.ts/preload.ts read process.platform. Linux windows use the native frame (the renderer already falls back to non-macOS chrome); the menu drops darwin-only roles; DevTools is Control+Shift+I.
  • Updates: both the electron-updater path and the JSON version-feed check are gated to darwin and log one line on Linux ("no Linux update feed yet"). Nothing Linux-related is published.
  • Native modules: the afterPack hook is platform-parameterized; better-sqlite3 fetches the electron-v145-linux-x64 prebuild (Electron stays pinned at 41.7.0); node-pty compiles at install time via node-gyp (N-API, so ABI-safe under Electron).
  • Login-shell PATH (separate commit, droppable if unwanted): ensurePackagedMacOsUserShellPathensurePackagedUserShellPath; Linux uses $SHELL (fallback /bin/bash) -ilc, so AppImages launched from a desktop entry get the user's real PATH.
  • Smoke: smoke:packaged is now cross-platform (darwin|linux) via a shared packaged-binary path resolver.
  • Docs: Linux section in apps/desktop/README.md; docs/repository-overview.md one-liner.

Testing

  • turbo run typecheck test green for @bb/desktop (241 tests) and @bb/desktop-contract (17 tests); the first commit is also independently green so the PATH commit can be dropped cleanly.
  • Built and ran on Arch-family Linux (x64, Wayland): package:linux → packaged smoke test passes (bridge reports platform: "linux"); dist:linuxbb-0.37.0-x86_64.AppImage launches with the full stack healthy (server, host-daemon, parcel-watcher child under Electron-as-Node, better-sqlite3 Electron prebuild, node-pty).
  • Repo-wide test sweep: the only failing packages fail identically on pristine main on that machine (zlib golden-byte off-by-ones and timing-sensitive UI tests) — environmental, not introduced here.

Work still missing (intentionally deferred — your calls)

  1. Linux auto-update feed — publishing latest-linux.yml + AppImage to desktop-latest (the release job force-resets all assets, so a Linux artifact needs coordination there).
  2. CI — a Linux job in build-desktop.yml (ubuntu-latest, no signing). A published AppImage should also build against an older glibc baseline than a dev machine.
  3. Web download surface — a Linux entry alongside the macOS download route/CTA.
  4. arm64 and the Linux nightly channel (nightly's bb Nightly productName needs an executableName decision).
  5. AppImage desktop integration polish (e.g. AppStream metadata) if you want launcher-first UX.

AGENT GENERATED: by Claude Fable 5 (orchestrating a GPT-5.6 worker), reviewed and tested by @salemsayed

🤖 Generated with Claude Code

https://claude.ai/code/session_01JyDzQ6R3oZGzpii6UA5vAW

@SawyerHood

Copy link
Copy Markdown
Collaborator

TY @salemsayed! I will work on getting this merged this week. I think I will probably keep this as an "alpha" (so not mention it on the website). And let a few people test it (including yourself) so that we can iron out kinks.

salemsayed and others added 3 commits August 13, 2026 00:13
Linux is already a supported bb runtime host; this adds the desktop
shell packaging for it. The electron-builder config gains a linux block
(AppImage, x64, executableName pinned to "bb" since the Linux default
derives from the sanitized package name), with desktop:build:linux,
dist:linux, package:linux, and start:linux scripts mirroring the mac
ones. The desktop-contract platform widens to macos|linux, the shell
injects platform/isMac into pure modules, Linux windows use the native
frame and a menu without darwin-only roles, the afterPack native-module
hook is platform-parameterized, and the packaged smoke test now runs on
darwin and linux. Update checks and auto-update stay darwin-only and log
a single line on Linux, where no update feed exists yet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JyDzQ6R3oZGzpii6UA5vAW
Generalize ensurePackagedMacOsUserShellPath to ensurePackagedUserShellPath:
darwin keeps /bin/zsh -ilc, Linux uses $SHELL (fallback /bin/bash) -ilc.
AppImages launched from a desktop entry inherit a minimal PATH, and the
desktop app's job is spawning agent CLIs from the user's real PATH.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JyDzQ6R3oZGzpii6UA5vAW
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JyDzQ6R3oZGzpii6UA5vAW
@SawyerHood

Copy link
Copy Markdown
Collaborator

Tested using WSL and it works great! Merging

@SawyerHood
SawyerHood merged commit d833a25 into get-bb:main Aug 13, 2026
9 checks passed
SawyerHood added a commit that referenced this pull request Aug 13, 2026
…annels (#1501)

## Summary

The Linux AppImage target landed in #1392 as packaging only — it
deferred CI, the update feed, and the nightly channel. This closes those
three gaps, so Linux ships on every stable release, gets a nightly
channel, and has a working update stream.

## Work done

**Release (`build-desktop.yml`)** — split into `macos`, `linux` (x64
AppImage), and one `publish` job that needs both.

**Nightly (`publish-bb-app.yml`)** — the `nightly-desktop` job became
`nightly-desktop-macos` + `nightly-desktop-linux` +
`nightly-desktop-publish`, same shape. Both platform jobs derive the
nightly version from the run ID, so they agree without sharing state.

The single publisher is load-bearing. `desktop-latest` and
`desktop-nightly` are moving releases whose assets are fully reset on
each publish, so two platform jobs publishing independently would delete
each other's binaries. Building in parallel and publishing once removes
that race. A failure in either platform job stops the publish, so a
release can never ship one platform's binaries against the other's stale
feed.

**Update stream** — each platform gets its own feed file inside the same
release tag:

| Platform | Artifacts | electron-updater metadata | Version feed |
| --- | --- | --- | --- |
| macOS | `.dmg`, `.zip` (2 arch) | `latest-mac.yml` |
`desktop-version.json` |
| Linux | `.AppImage` (x64) | `latest-linux.yml` |
`desktop-version-linux.json` |

macOS keeps the unsuffixed name deliberately: shipped macOS builds
already request it, and renaming would strand every installed app on its
current version. The feed contract's `platform` widened from
`z.literal("macos")` to an enum, and the file-name mapping lives in
`@bb/desktop-contract` so the build script and the app read one source
of truth.

**Linux updates ungated** in `main.ts`, split into two capabilities
rather than one flag:

- Version checks run on every Linux install and report new releases.
- Self-installing auto-update runs only inside an AppImage (detected via
`APPIMAGE`). An extracted directory or a distro package has no single
file to replace, so it would fail every download rather than quietly
doing nothing.

**Channel separation** — nightly Linux builds use `bb-nightly` as the
executable name, so stable and nightly can be installed side by side
without one shadowing the other on PATH.

Linux artifacts are unsigned; only the macOS binaries wait on the Apple
signing secrets, since AppImages have no notarization equivalent.

## Testing

Verified locally:

- `turbo run typecheck test` green for `@bb/desktop` (223 tests),
`@bb/desktop-contract` (19), and `@bb/app` typecheck.
- Both workflows parse as YAML; job graph and `needs` wiring confirmed.
- The new asset-assembly shell helper exercised directly: required globs
exit 1 when unmatched, optional ones skip. This matters because an
unmatched glob would otherwise reach `gh` as a literal pattern and fail
the upload.
- The AppImage packaging assumption checked against the installed
electron-builder source — `AppImageTarget` dispatches with
`isWriteUpdateInfo: true`, and update-info files are written whenever a
publish config exists, independent of `--publish`. So `latest-linux.yml`
is produced under the existing `--publish never`.

Four `@bb/desktop` suites cannot run in the dev worktree because the
Electron binary is not installed; they fail identically on pristine
`main`.

## Verification

`Build Desktop` was dispatched against this branch before merge with
`publish=false, release_channel=qa` — safe by construction, since
publishing requires `publish=true` **and** `release_channel=stable`
**and** a non-prerelease version. [Run
31683115574](https://github.com/get-bb/bb/actions/runs/31683115574)
passed all three jobs:

- **Linux** — real AppImage build on `ubuntu-22.04`, `latest-linux.yml`,
the version-feed generator, and the headless `xvfb` smoke test. The feed
step reads `latest-linux.yml` and would have thrown if it were missing,
so both it and `desktop-version-linux.json` are confirmed produced.
- **macOS** — unchanged path, still green.
- **Publish** — planned first, then correctly skipped both downloads and
the release step, avoiding ~953 MB of artifact transfer it would never
use.

Locally: 228 tests plus 19 in `@bb/desktop-contract`, typechecks clean
for `@bb/desktop`, `@bb/desktop-contract`, and `@bb/app`. Four
`@bb/desktop` suites cannot run in the dev worktree because the Electron
binary is not installed; they fail identically on pristine `main`.

Still unexercised by any dry run:

- **The release mutation.** Force-pushing the moving tag and resetting
assets only happens on a real stable publish. The failure mode is
recoverable: the immutable `desktop-v<version>` release is created first
and refuses to overwrite.
- **The nightly desktop path.** Its jobs are gated on `dry_run == false`
(pre-existing), so they cannot run from a branch. They are structurally
identical to the jobs above; what is nightly-specific — `bb-nightly` as
executable name, `nightly-linux.yml`, the nightly icon — is locally
testable with `BB_DESKTOP_RELEASE_CHANNEL=nightly pnpm --filter
@bb/desktop run dist:linux`.

## Deliberately out of scope

The web download surface has no Linux entry —
`apps/web/src/landing/site.ts` still exposes only
`DOWNLOAD_MACOS_VERSION_FEED_URL`. Nothing is broken, since macOS kept
its feed name, but there is no Linux download CTA yet. Also still
deferred from #1392: Linux arm64 and AppStream metadata.

The `ubuntu-22.04` runner pin is intentional. The AppImage links against
the build machine's glibc, so that pin sets the oldest distro that can
run a published build. It is the oldest hosted image GitHub offers, and
worth revisiting when it is retired.

> AGENT GENERATED: by Claude Opus 5

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants