--- name: electrobun-distribution description: Packaging, code signing, notarization, and distribution for Electrobun desktop applications. This skill covers building production bundles, creating installers and distributable packages, code signing for Windows and macOS, Apple notarization for Gatekeeper, auto-updater implementation, delta updates, update servers, cross-platform build processes, CI/CD integration, app icons and resources, version management, release workflows, Windows SmartScreen requirements, macOS DMG creation, Linux package formats (deb, rpm, AppImage), and distribution best practices. Use when preparing app for production, implementing auto-updates, setting up code signing certificates, troubleshooting distribution issues, creating installers, configuring update servers, building for multiple platforms, or releasing new versions. Triggers include "build", "package", "distribute", "code sign", "notarize", "installer", "auto-update", "release", "production build", "DMG", "updater", "delta update", or "certificate". license: MIT metadata: author: Blackboard version: "1.0.0" --- # Electrobun Distribution Complete guide to packaging, signing, and distributing Electrobun applications. ## Production Build ### Basic Build Configuration **electrobun.config.ts:** ```ts import { defineConfig } from "electrobun"; export default defineConfig({ app: { name: "My Application", version: "1.0.0", identifier: "com.mycompany.myapp", description: "My desktop application", author: "My Company", }, build: { main: "src/bun/main.ts", views: { mainview: "src/views/mainview/index.ts", settings: "src/views/settings/index.ts", }, output: "dist", }, icons: { mac: "assets/icon.icns", // macOS: 1024x1024 .icns win: "assets/icon.ico", // Windows: .ico with multiple sizes linux: "assets/icon.png", // Linux: 512x512 .png }, updates: { provider: "generic", url: "https://updates.myapp.com", }, }); ``` ### Build Commands ```bash # Development build bun run dev # Production build bun run build # Build for specific platform bun run build --platform=mac bun run build --platform=win bun run build --platform=linux # Build for all platforms bun run build --all ``` ## Code Signing ### macOS Code Signing #### Setup Certificates ```bash # List available certificates security find-identity -v -p codesigning # Import Developer ID certificate # Get certificate from Apple Developer Portal # Double-click .p12 file or: security import cert.p12 -k ~/Library/Keychains/login.keychain ``` #### Sign Application **electrobun.config.ts:** ```ts export default defineConfig({ // ... mac: { identity: "Developer ID Application: Your Name (TEAM_ID)", entitlements: "entitlements.mac.plist", entitlementsInherit: "entitlements.mac.plist", hardenedRuntime: true, gatekeeperAssess: false, }, }); ``` **entitlements.mac.plist:** ```xml com.apple.security.cs.allow-jit com.apple.security.cs.allow-unsigned-executable-memory com.apple.security.cs.disable-library-validation com.apple.security.network.client com.apple.security.network.server com.apple.security.device.camera com.apple.security.device.audio-input ``` #### Apple Notarization ```bash # Notarize the app xcrun notarytool submit dist/MyApp.dmg \ --apple-id "your@email.com" \ --team-id "TEAM_ID" \ --password "app-specific-password" \ --wait # Check notarization status xcrun notarytool log \ --apple-id "your@email.com" \ --team-id "TEAM_ID" \ --password "app-specific-password" # Staple notarization to DMG xcrun stapler staple dist/MyApp.dmg # Verify stapling xcrun stapler validate dist/MyApp.dmg ``` **Automated notarization:** ```ts // In electrobun.config.ts export default defineConfig({ // ... mac: { identity: "Developer ID Application: Your Name (TEAM_ID)", notarize: { teamId: process.env.APPLE_TEAM_ID, appleId: process.env.APPLE_ID, appleIdPassword: process.env.APPLE_APP_PASSWORD, }, }, }); ``` ### Windows Code Signing #### Get Certificate ```powershell # Import certificate certutil -importpfx cert.pfx # Or specify password certutil -f -p PASSWORD -importpfx cert.pfx ``` #### Sign Application **electrobun.config.ts:** ```ts export default defineConfig({ // ... win: { certificateFile: "cert.pfx", certificatePassword: process.env.WIN_CERT_PASSWORD, signWithParams: "/tr http://timestamp.digicert.com /td sha256 /fd sha256", }, }); ``` **Manual signing:** ```powershell # Sign executable signtool sign /f cert.pfx /p PASSWORD /tr http://timestamp.digicert.com /td sha256 /fd sha256 MyApp.exe # Verify signature signtool verify /pa MyApp.exe ``` ## Auto-Updater ### Update Server Setup **updates.json:** ```json { "version": "1.0.1", "releaseDate": "2026-02-21T00:00:00Z", "platforms": { "darwin": { "url": "https://updates.myapp.com/MyApp-1.0.1-mac.zip", "signature": "MC0CFQCf...", "size": 15728640 }, "win32": { "url": "https://updates.myapp.com/MyApp-1.0.1-win.exe", "signature": "30820...", "size": 12582912 }, "linux": { "url": "https://updates.myapp.com/MyApp-1.0.1-linux.AppImage", "signature": "30440...", "size": 18874368 } }, "releaseNotes": "Bug fixes and performance improvements" } ``` ### Client Implementation **src/bun/main.ts:** ```ts import { Updater, dialog } from "electrobun/bun"; class UpdateManager { private updater: Updater; private updateAvailable = false; constructor() { this.updater = new Updater({ url: "https://updates.myapp.com/updates.json", autoCheck: true, interval: 4 * 60 * 60 * 1000, // Check every 4 hours }); this.setupHandlers(); } private setupHandlers() { this.updater.on("update-available", async (info) => { this.updateAvailable = true; const result = await dialog.showMessageBox({ type: "info", title: "Update Available", message: `Version ${info.version} is available`, detail: info.releaseNotes, buttons: ["Download Now", "Later"], defaultId: 0, }); if (result.response === 0) { this.updater.downloadAndInstall(); } }); this.updater.on("update-not-available", () => { console.log("App is up to date"); }); this.updater.on("download-progress", (progress) => { console.log(`Download progress: ${progress.percent}%`); // Update UI with progress mainWindow.rpc.updateDownloadProgress(progress); }); this.updater.on("update-downloaded", async () => { const result = await dialog.showMessageBox({ type: "info", title: "Update Ready", message: "Update has been downloaded", detail: "The app will restart to apply the update", buttons: ["Restart Now", "Later"], defaultId: 0, }); if (result.response === 0) { this.updater.quitAndInstall(); } }); this.updater.on("error", (error) => { console.error("Update error:", error); dialog.showMessageBox({ type: "error", title: "Update Error", message: "Failed to check for updates", detail: error.message, }); }); } checkForUpdates() { this.updater.checkForUpdates(); } isUpdateAvailable() { return this.updateAvailable; } } const updateManager = new UpdateManager(); // Expose to window RPC win.defineRpc({ handlers: { checkForUpdates: async () => { updateManager.checkForUpdates(); }, isUpdateAvailable: async () => { return updateManager.isUpdateAvailable(); } } }); ``` ### Delta Updates Electrobun uses bsdiff for tiny delta updates (~14KB): ```ts // Automatically handled by Updater // Just ensure update server provides: // - Full package for new installs // - Delta patches for updates // Update server structure: // /updates.json // /v1.0.0/MyApp-mac.zip (full) // /v1.0.1/MyApp-mac.zip (full) // /v1.0.1/delta-1.0.0-to-1.0.1-mac.patch (delta) ``` ## Creating Installers ### macOS DMG ```bash # Create DMG with background and app arrangement create-dmg dist/MyApp.app dist/ \ --overwrite \ --window-size 660 400 \ --icon-size 160 \ --icon "MyApp.app" 180 170 \ --hide-extension "MyApp.app" \ --app-drop-link 480 170 \ --background "installer-background.png" # Result: dist/MyApp-1.0.0.dmg ``` ### Windows Installer **Using NSIS:** ```nsis ; installer.nsi !include "MUI2.nsh" Name "My Application" OutFile "MyApp-Setup.exe" InstallDir "$PROGRAMFILES\My Application" !insertmacro MUI_PAGE_WELCOME !insertmacro MUI_PAGE_DIRECTORY !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_PAGE_FINISH !insertmacro MUI_LANGUAGE "English" Section "Install" SetOutPath "$INSTDIR" File /r "dist\*.*" CreateDirectory "$SMPROGRAMS\My Application" CreateShortcut "$SMPROGRAMS\My Application\My Application.lnk" "$INSTDIR\MyApp.exe" CreateShortcut "$DESKTOP\My Application.lnk" "$INSTDIR\MyApp.exe" WriteUninstaller "$INSTDIR\Uninstall.exe" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\MyApp" "DisplayName" "My Application" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\MyApp" "UninstallString" "$INSTDIR\Uninstall.exe" SectionEnd Section "Uninstall" Delete "$INSTDIR\*.*" RMDir /r "$INSTDIR" Delete "$SMPROGRAMS\My Application\*.*" RMDir "$SMPROGRAMS\My Application" Delete "$DESKTOP\My Application.lnk" DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\MyApp" SectionEnd ``` ### Linux Packages **AppImage (recommended):** ```bash # Create AppImage appimagetool dist/MyApp-linux-x64 dist/MyApp.AppImage # Make executable chmod +x dist/MyApp.AppImage ``` **Debian package:** ```bash # Create .deb structure mkdir -p myapp_1.0.0/DEBIAN mkdir -p myapp_1.0.0/usr/bin mkdir -p myapp_1.0.0/usr/share/applications mkdir -p myapp_1.0.0/usr/share/icons/hicolor/512x512/apps # Copy files cp dist/MyApp myapp_1.0.0/usr/bin/ cp myapp.desktop myapp_1.0.0/usr/share/applications/ cp icon.png myapp_1.0.0/usr/share/icons/hicolor/512x512/apps/ # Create control file cat > myapp_1.0.0/DEBIAN/control << EOF Package: myapp Version: 1.0.0 Section: utils Priority: optional Architecture: amd64 Maintainer: Your Name Description: My Application A desktop application built with Electrobun EOF # Build .deb dpkg-deb --build myapp_1.0.0 ``` ## CI/CD Integration ### GitHub Actions **.github/workflows/build.yml:** ```yaml name: Build and Release on: push: tags: - 'v*' jobs: build-mac: runs-on: macos-latest steps: - uses: actions/checkout@v3 - name: Setup Bun uses: oven-sh/setup-bun@v1 - name: Install dependencies run: bun install - name: Import signing certificate env: CERTIFICATE_BASE64: ${{ secrets.MAC_CERTIFICATE }} CERTIFICATE_PASSWORD: ${{ secrets.MAC_CERT_PASSWORD }} run: | echo $CERTIFICATE_BASE64 | base64 --decode > certificate.p12 security import certificate.p12 -P $CERTIFICATE_PASSWORD - name: Build app env: APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} run: bun run build --platform=mac - name: Upload artifact uses: actions/upload-artifact@v3 with: name: mac-build path: dist/*.dmg build-windows: runs-on: windows-latest steps: - uses: actions/checkout@v3 - name: Setup Bun uses: oven-sh/setup-bun@v1 - name: Install dependencies run: bun install - name: Build app env: WIN_CERT_PASSWORD: ${{ secrets.WIN_CERT_PASSWORD }} run: bun run build --platform=win - name: Upload artifact uses: actions/upload-artifact@v3 with: name: win-build path: dist/*.exe release: needs: [build-mac, build-windows] runs-on: ubuntu-latest steps: - name: Download artifacts uses: actions/download-artifact@v3 - name: Create Release uses: softprops/action-gh-release@v1 with: files: | mac-build/* win-build/* draft: false prerelease: false env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ``` ## Best Practices ### Version Management ```ts // Use semantic versioning const version = "1.2.3"; // MAJOR.MINOR.PATCH // Update package.json and electrobun.config.ts together // Automate with script: import { writeFileSync, readFileSync } from "fs"; function updateVersion(newVersion: string) { // Update package.json const pkg = JSON.parse(readFileSync("package.json", "utf-8")); pkg.version = newVersion; writeFileSync("package.json", JSON.stringify(pkg, null, 2)); // Update config const config = readFileSync("electrobun.config.ts", "utf-8"); const updated = config.replace( /version:\s*"[^"]+"/, `version: "${newVersion}"` ); writeFileSync("electrobun.config.ts", updated); } ``` ### Testing Before Release ```ts // Pre-release checklist const checklist = [ "All tests passing", "No console errors", "Auto-updater tested", "Code signed and notarized", "Installer tested on clean system", "Release notes written", "Documentation updated", ]; ``` ## Resources For more on Electrobun: - **Core skill**: `electrobun` - Basic build setup - **Debugging**: `electrobun-debugging` - Build troubleshooting - **Auto-updater docs**: https://blackboard.sh/electrobun/docs/guides/auto-updates