From d8980713847ae7f6920fab33eda8b82393f3de2e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 21:56:14 +0000 Subject: [PATCH 1/4] Initial plan From 2d0a8684ed0a3f8d28ea7f12d93d3b61bfce0803 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 22:10:34 +0000 Subject: [PATCH 2/4] Fix macOS build: replace segmented 3-step build with single electron-builder pass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous workflow used: 1. electron-builder --mac --dir (build unpacked .app) 2. codesign.sh (ad-hoc sign) 3. electron-builder --mac dmg --prepackaged (create DMG) This produced a working DMG but a broken app — Python failed at runtime. The --prepackaged step may re-process extraResources and overwrite the signed Python environment, and the custom codesign.sh step with --options runtime --deep can conflict with macOS library loading for the embedded Python. Replace all three steps with a single `electron-builder --mac` call that builds the .app and creates the DMG in one pass. This correctly embeds python-embed/ via extraResources without interference. Signing/notarization disabled (CSC_IDENTITY_AUTO_DISCOVERY=false, --config.mac.notarize=false) — produces an unsigned DMG for testing. Co-authored-by: lmangani <1423657+lmangani@users.noreply.github.com> --- .github/workflows/build-mac-dmg.yml | 47 ++++++----------------------- 1 file changed, 9 insertions(+), 38 deletions(-) diff --git a/.github/workflows/build-mac-dmg.yml b/.github/workflows/build-mac-dmg.yml index 665cf5b6..a395294e 100644 --- a/.github/workflows/build-mac-dmg.yml +++ b/.github/workflows/build-mac-dmg.yml @@ -54,48 +54,19 @@ jobs: - name: Build frontend run: pnpm run build:frontend - - name: Build macOS app bundle (unsigned) - # Build the unpacked .app only — electron-builder signing is disabled entirely. - # Ad-hoc signing is applied in the next step via build/macos/codesign.sh. - id: build-app + - name: Build macOS DMG + # Build the .app and DMG in a single electron-builder invocation. + # The previous segmented approach (electron-builder --dir → codesign.sh → + # electron-builder --prepackaged) produced a broken Python environment at + # runtime. A single electron-builder pass correctly embeds python-embed/ + # via extraResources without any post-signing interference. + # Signing and notarization are disabled; this produces an unsigned DMG + # suitable for testing (bypass Gatekeeper with: xattr -d com.apple.quarantine ). env: CSC_IDENTITY_AUTO_DISCOVERY: "false" run: | - pnpm exec electron-builder --mac --dir \ + pnpm exec electron-builder --mac \ --config.mac.notarize=false \ - --config.publish.owner=${{ github.repository_owner }} \ - --config.publish.repo=${{ github.event.repository.name }} - - APP_PATH=$(find release/mac-* -name "*.app" -maxdepth 1 | head -1) - if [ -z "$APP_PATH" ]; then - echo "Error: No .app bundle found in release/" - exit 1 - fi - echo "app_path=$APP_PATH" >> "$GITHUB_OUTPUT" - - - name: Ad-hoc code sign the app bundle - # Signs all Mach-O binaries (Python natives, Electron frameworks, executables) - # and the app bundle using identity "-" (ad-hoc / self-signed). - # No Apple ID, no certificate, no notarization required. - # To use a real Developer ID certificate, set the MACOS_SIGNING_IDENTITY secret. - run: | - chmod +x build/macos/codesign.sh - ./build/macos/codesign.sh "${{ steps.build-app.outputs.app_path }}" - env: - MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY || '-' }} - - - name: Create DMG from signed app - # Packages the signed .app into a DMG using the electron-builder.yml layout. - # --prepackaged skips the build phase; electron-builder only creates the DMG. - # CSC_IDENTITY_AUTO_DISCOVERY=false prevents any re-signing attempt. - env: - CSC_IDENTITY_AUTO_DISCOVERY: "false" - run: | - pnpm exec electron-builder --mac dmg \ - --prepackaged "${{ steps.build-app.outputs.app_path }}" \ - --config.mac.notarize=false \ - --config.publish.owner=${{ github.repository_owner }} \ - --config.publish.repo=${{ github.event.repository.name }} \ --publish never - name: Upload DMG to GitHub Release From 554ca22bbfe1e9a678b280c144efa2e100f59bdc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 22:39:19 +0000 Subject: [PATCH 3/4] Enable Open Anyway: fix ad-hoc signing to exclude hardened runtime MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous build produced either an unsigned app (requiring xattr -dr to clear quarantine from all files including the embedded python3 binary) or an ad-hoc signed app with --options runtime (hardened runtime) which blocked Python from loading pip-installed unsigned .so extensions. Fix: apply ad-hoc signing WITHOUT --options runtime. Without hardened runtime, macOS does not enforce library validation — Python loads its extensions freely. Because the whole bundle is consistently signed (all executables with the same ad-hoc identity), Gatekeeper's "Open Anyway" button approves the entire bundle including the embedded python3 binary. No shell commands (no xattr) are required. Changes: - build/macos/codesign.sh: remove --options runtime from ad-hoc path; keep --options runtime + --timestamp only for real Developer ID certs - .github/workflows/build-mac-dmg.yml: restore 3-step build (build app → codesign.sh → create DMG); fix find -maxdepth ordering - docs/INSTALLER.md: replace xattr -dr instructions with Open Anyway steps - README.md: add macOS Gatekeeper note to Install section Co-authored-by: lmangani <1423657+lmangani@users.noreply.github.com> --- .github/workflows/build-mac-dmg.yml | 49 +++++++++++++++++++++++------ README.md | 2 ++ build/macos/codesign.sh | 11 +++++-- docs/INSTALLER.md | 15 ++++++--- 4 files changed, 61 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build-mac-dmg.yml b/.github/workflows/build-mac-dmg.yml index a395294e..fd1d131d 100644 --- a/.github/workflows/build-mac-dmg.yml +++ b/.github/workflows/build-mac-dmg.yml @@ -54,18 +54,49 @@ jobs: - name: Build frontend run: pnpm run build:frontend - - name: Build macOS DMG - # Build the .app and DMG in a single electron-builder invocation. - # The previous segmented approach (electron-builder --dir → codesign.sh → - # electron-builder --prepackaged) produced a broken Python environment at - # runtime. A single electron-builder pass correctly embeds python-embed/ - # via extraResources without any post-signing interference. - # Signing and notarization are disabled; this produces an unsigned DMG - # suitable for testing (bypass Gatekeeper with: xattr -d com.apple.quarantine ). + - name: Build macOS app bundle (unsigned) + # Build the unpacked .app with python-embed embedded via extraResources. + # Signing is disabled here; ad-hoc signing is applied in the next step + # via build/macos/codesign.sh (without --options runtime, so no hardened + # runtime and no library validation that would block Python .so loading). + id: build-app env: CSC_IDENTITY_AUTO_DISCOVERY: "false" run: | - pnpm exec electron-builder --mac \ + pnpm exec electron-builder --mac --dir \ + --config.mac.notarize=false \ + --publish never + + APP_PATH=$(find release/mac-* -maxdepth 1 -name "*.app" | head -1) + if [ -z "$APP_PATH" ]; then + echo "Error: No .app bundle found in release/" + exit 1 + fi + echo "app_path=$APP_PATH" >> "$GITHUB_OUTPUT" + + - name: Ad-hoc code sign the app bundle + # Signs the entire bundle with ad-hoc identity ("-") WITHOUT hardened + # runtime so macOS does not enforce library validation. Python can then + # load its pip-installed .so extensions. Because the whole bundle is + # consistently signed, "Open Anyway" in Privacy & Security approves + # the entire bundle including the embedded python3 binary — users do + # not need to run any shell commands (no xattr required). + # To use a real Developer ID certificate, set MACOS_SIGNING_IDENTITY. + run: | + chmod +x build/macos/codesign.sh + ./build/macos/codesign.sh "${{ steps.build-app.outputs.app_path }}" + env: + MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY || '-' }} + + - name: Create DMG from signed app + # Packages the signed .app into a DMG using the electron-builder.yml layout. + # --prepackaged skips the app-build phase; electron-builder only creates the DMG. + # CSC_IDENTITY_AUTO_DISCOVERY=false prevents any re-signing attempt. + env: + CSC_IDENTITY_AUTO_DISCOVERY: "false" + run: | + pnpm exec electron-builder --mac dmg \ + --prepackaged "${{ steps.build-app.outputs.app_path }}" \ --config.mac.notarize=false \ --publish never diff --git a/README.md b/README.md index ff8da4f2..a1e84a71 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,8 @@ In API-only mode, available resolutions/durations may be limited to what the API 2. Install and launch **LTX Desktop** 3. Complete first-run setup +> **macOS users:** The DMG is ad-hoc signed (no Apple Developer account). On first launch macOS Gatekeeper will show "cannot be opened because the developer cannot be verified." Go to **System Settings → Privacy & Security** and click **Open Anyway** — no shell commands required. See [INSTALLER.md](docs/INSTALLER.md) for step-by-step instructions. + ## First run & data locations LTX Desktop stores app data (settings, models, logs) in: diff --git a/build/macos/codesign.sh b/build/macos/codesign.sh index 735877a3..1c9b598d 100644 --- a/build/macos/codesign.sh +++ b/build/macos/codesign.sh @@ -42,20 +42,27 @@ if [ ! -f "$ENTITLEMENTS_PATH" ]; then fi # ── Signing helper ──────────────────────────────────────────────────────────── -# Ad-hoc signing ("-") does not support --timestamp (requires a CA). +# Ad-hoc signing ("-") does not support --timestamp (requires a CA) and must +# NOT use --options runtime. Hardened Runtime ("runtime" option) enables library +# validation, which prevents Python from loading unsigned pip-installed .so +# extensions. Without hardened runtime the signed bundle works correctly after +# the user clicks "Open Anyway" in Privacy & Security — no shell commands needed. sign_target() { local target="$1" echo " Signing: $(basename "$target")" if [ "$SIGNING_IDENTITY" = "-" ]; then + # Ad-hoc: no hardened runtime so macOS does not enforce library validation. xcrun codesign \ --sign "$SIGNING_IDENTITY" \ --force \ - --options runtime \ --entitlements "$ENTITLEMENTS_PATH" \ --deep \ "$target" else + # Real Developer ID certificate: hardened runtime + timestamp required for + # notarization. The disable-library-validation entitlement in the plist + # allows Python to load unsigned pip extensions even under hardened runtime. xcrun codesign \ --sign "$SIGNING_IDENTITY" \ --force \ diff --git a/docs/INSTALLER.md b/docs/INSTALLER.md index f6601e39..a91886d5 100644 --- a/docs/INSTALLER.md +++ b/docs/INSTALLER.md @@ -128,11 +128,16 @@ Ensure you have internet access. The script downloads Python automatically. ### Build fails with CUDA errors The build doesn't require a GPU. CUDA packages are pre-built binaries. -### macOS: "App is damaged" or Gatekeeper warning -On unsigned builds, macOS Gatekeeper may block the app. Right-click the app and select "Open", or run: -```bash -xattr -dr com.apple.quarantine /Applications/LTX\ Desktop.app -``` +### macOS: Gatekeeper "unidentified developer" warning +The distributed DMG is ad-hoc signed (no Apple Developer account), so Gatekeeper will +block the first launch. No shell commands are required: + +1. Open the DMG and drag **LTX Desktop** to `/Applications`. +2. Double-click the app — macOS will show "cannot be opened because the developer cannot be verified." Click **Cancel** (or **OK**) to dismiss. +3. Open **System Settings → Privacy & Security**. Scroll down to find the blocked app and click **Open Anyway**. +4. Confirm by clicking **Open** in the follow-up dialog. + +The app will launch normally from this point on. ### Installer is too large Expected sizes: From 8438b601e6db48029f77c044b1649e0330777e67 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 23:14:30 +0000 Subject: [PATCH 4/4] Fix startup crash at line 221 and icon path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three focused fixes for the crash at ltx2_server.py:221: 1. zit_image_generation_pipeline.py: make ZImagePipeline import lazy - Moved `from diffusers.pipelines.auto_pipeline import ZImagePipeline` from module level into __init__, consistent with how every other pipeline defers its heavy imports. - Previously this import ran as part of build_default_service_bundle() at startup — the exact call site of the line-221 crash. If diffusers doesn't expose ZImagePipeline (version mismatch, partial install, etc.) the whole backend would refuse to start even before any ZIT model is requested. 2. electron/window.ts: fix production icon path - getCurrentDir() returns path.dirname(app.getPath('exe')) = Contents/MacOS on macOS, so the old path looked for the icon at Contents/MacOS/resources/ which never exists. Use process.resourcesPath (= Contents/Resources/) in production builds. 3. electron-builder.yml: add icon.png to mac extraResources - Ensures icon.png is present at Contents/Resources/icon.png (= process.resourcesPath) so the corrected path actually finds it. Co-authored-by: lmangani <1423657+lmangani@users.noreply.github.com> --- .../zit_image_generation_pipeline.py | 2 +- electron-builder.yml | 2 ++ electron/window.ts | 8 ++++++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/backend/services/image_generation_pipeline/zit_image_generation_pipeline.py b/backend/services/image_generation_pipeline/zit_image_generation_pipeline.py index 7e5f0bb6..db684e8d 100644 --- a/backend/services/image_generation_pipeline/zit_image_generation_pipeline.py +++ b/backend/services/image_generation_pipeline/zit_image_generation_pipeline.py @@ -8,7 +8,6 @@ from typing import Any, cast import torch -from diffusers.pipelines.auto_pipeline import ZImagePipeline # type: ignore[reportUnknownVariableType] from PIL.Image import Image as PILImage from services.services_utils import ImagePipelineOutputLike, PILImageType, get_device_type @@ -30,6 +29,7 @@ def create( return ZitImageGenerationPipeline(model_path=model_path, device=device) def __init__(self, model_path: str, device: str | None = None) -> None: + from diffusers.pipelines.auto_pipeline import ZImagePipeline # type: ignore[reportUnknownVariableType] self._device: str | None = None self._cpu_offload_active = False self.pipeline = ZImagePipeline.from_pretrained( # type: ignore[reportUnknownMemberType] diff --git a/electron-builder.yml b/electron-builder.yml index db211187..53d121bb 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -69,6 +69,8 @@ mac: - "/python/.*\\.(py|pyc|pyi|pxd|pyx|h|hpp|cpp|c|cuh|cu|tcl|txt|json|yaml|yml|toml|cfg|md|rst|html|css|enc|msg|cmake|gif|png|jpg|svg|xml|jinja|typed|al)$" - "/python/.*\\.dist-info/" extraResources: + - from: resources/icon.png + to: icon.png - from: python-embed to: python filter: diff --git a/electron/window.ts b/electron/window.ts index 19836520..113b1806 100644 --- a/electron/window.ts +++ b/electron/window.ts @@ -12,9 +12,13 @@ export function createWindow(): BrowserWindow { ? path.join(getCurrentDir(), 'dist-electron', 'preload.js') : path.join(app.getAppPath(), 'dist-electron', 'preload.js') - // App icon — use .ico on Windows, .png elsewhere + // App icon — use .ico on Windows, .png elsewhere. + // In production the icon is placed in the app's resources directory via + // extraResources; in dev it lives in the project's resources/ folder. const iconExt = process.platform === 'win32' ? 'icon.ico' : 'icon.png' - const iconPath = path.join(getCurrentDir(), 'resources', iconExt) + const iconPath = isDev + ? path.join(getCurrentDir(), 'resources', iconExt) + : path.join(process.resourcesPath, iconExt) logger.info(`[icon] Loading app icon from: ${iconPath} | exists: ${fs.existsSync(iconPath)}`) const appIcon = fs.existsSync(iconPath) ? nativeImage.createFromPath(iconPath) : undefined