| 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:
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",
win: "assets/icon.ico",
linux: "assets/icon.png",
},
updates: {
provider: "generic",
url: "https://updates.myapp.com",
},
});
Build Commands
bun run dev
bun run build
bun run build --platform=mac
bun run build --platform=win
bun run build --platform=linux
bun run build --all
Code Signing
macOS Code Signing
Setup Certificates
security find-identity -v -p codesigning
security import cert.p12 -k ~/Library/Keychains/login.keychain
Sign Application
electrobun.config.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 version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.device.camera</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
</dict>
</plist>
Apple Notarization
xcrun notarytool submit dist/MyApp.dmg \
--apple-id "your@email.com" \
--team-id "TEAM_ID" \
--password "app-specific-password" \
--wait
xcrun notarytool log <submission-id> \
--apple-id "your@email.com" \
--team-id "TEAM_ID" \
--password "app-specific-password"
xcrun stapler staple dist/MyApp.dmg
xcrun stapler validate dist/MyApp.dmg
Automated notarization:
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
# Import certificate
certutil -importpfx cert.pfx
# Or specify password
certutil -f -p PASSWORD -importpfx cert.pfx
Sign Application
electrobun.config.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:
# 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:
{
"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:
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,
});
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}%`);
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();
win.defineRpc({
handlers: {
checkForUpdates: async () => {
updateManager.checkForUpdates();
},
isUpdateAvailable: async () => {
return updateManager.isUpdateAvailable();
}
}
});
Delta Updates
Electrobun uses bsdiff for tiny delta updates (~14KB):
Creating Installers
macOS DMG
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"
Windows Installer
Using 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):
appimagetool dist/MyApp-linux-x64 dist/MyApp.AppImage
chmod +x dist/MyApp.AppImage
Debian package:
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
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/
cat > myapp_1.0.0/DEBIAN/control << EOF
Package: myapp
Version: 1.0.0
Section: utils
Priority: optional
Architecture: amd64
Maintainer: Your Name <you@example.com>
Description: My Application
A desktop application built with Electrobun
EOF
dpkg-deb --build myapp_1.0.0
CI/CD Integration
GitHub Actions
.github/workflows/build.yml:
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
const version = "1.2.3";
import { writeFileSync, readFileSync } from "fs";
function updateVersion(newVersion: string) {
const pkg = JSON.parse(readFileSync("package.json", "utf-8"));
pkg.version = newVersion;
writeFileSync("package.json", JSON.stringify(pkg, null, 2));
const config = readFileSync("electrobun.config.ts", "utf-8");
const updated = config.replace(
/version:\s*"[^"]+"/,
`version: "${newVersion}"`
);
writeFileSync("electrobun.config.ts", updated);
}
Testing Before Release
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: