SuperAPI AI Directory

dmg-app-porter

Coding & development

View repository

Updated 10-16 08:08

About this skill

DMG App Porter — macOS → Linux + Windows Conversion Skill

Quick Start

# Full pipeline: DMG → Linux AppImage + Windows .exe installer
dmg=app.dmg; name=MyApp; ver=1.0.0
7z x "$dmg" -odmg_raw && mount_hfs dmg_raw/*.hfs /mnt/dmg
cp -r /mnt/dmg/"$name".app ./app_source
node {baseDir}/scripts/patch-windows.js --input ./app_source --output ./app_win
node {baseDir}/scripts/patch-windows.js --input ./app_source --output ./app_linux --target linux
# Then build launchers + installers per phases 5-8

Prerequisites

Tool Linux Windows Purpose
7-Zip apt install p7zip-full choco install 7zip DMG extraction
hfsutils apt install hfsutils hfsprogs (7z handles HFS+) HFS+ mount
Node.js 18+ nodesource setup nodejs.org ASAR ops
Rust + cargo rustup.rs rustup.rs Windows launcher
cargo-zigbuild cargo install cargo-zigbuild N/A Cross-compile
NSIS apt install nsis choco install nsis Windows installer
appimagetool download from AppImage repo N/A Linux packaging

Phase 1: DMG Extraction

Step 1A: Extract with 7-Zip

7z x app.dmg -odmg_extracted -y
# Output: dmg_extracted/4.Main_Data_partition.hfs (or similar)

Step 1B: Mount HFS+ Partition (Linux)

# Find the HFS+ partition image
HFS_IMG=$(find dmg_extracted -name '*.hfs' -o -name '*partition*' | head -1)
sudo mkdir -p /mnt/dmg
sudo mount -t hfsplus -o loop,ro "$HFS_IMG" /mnt/dmg
# If read-only fails: sudo mount -t hfs -o loop,ro "$HFS_IMG" /mnt/dmg

Step 1C: Alternative — hfsutils

hmount dmg_extracted/*.hfs
hls -l                          # List root
hcd "MyApp.app"                 # Enter .app bundle
hcd Contents/MacOS
hcopy MyAppExecutable ~/output/  # Copy files
humount

Decision Point

If 7z lists .hfs file → use hfsplus mount (Step 1B)
If 7z directly extracts .app → skip mount, use extracted dir
If encrypted DMG → requires dmg2img first: dmg2img app.dmg app.img

Common Failures

Error Cause Fix
Invalid or corrupted archive APFS-only DMG Use dmg2img then mount
mount: unknown filesystem type 'hfsplus' Missing kernel module sudo modprobe hfsplus then apt install hfsprogs
Permission denied SIP on macOS Extract on Linux VM/container

Phase 2: App Analysis

Step 2A: Identify App Type

APP_DIR="./app_source/MyApp.app/Contents"
# Check for Electron
test -f "$APP_DIR/Resources/app.asar" && echo "ELECTRON" || echo "NATIVE"
test -d "$APP_DIR/Frameworks" && echo "HAS_FRAMEWORKS" || echo "NO_FRAMEWORKS"
# Parse Info.plist
/usr/libexec/PlistBuddy -c "Print CFBundleIdentifier" "$APP_DIR/Info.plist" 2>/dev/null \
  || python3 -c "import plistlib; p=plistlib.load(open('$APP_DIR/Info.plist','rb')); print(p.get('CFBundleIdentifier','unknown'))"

Decision Tree

app.asar exists?
├─ YES → Electron app (Phase 3)
│   └─ app.asar.unpacked exists?
│       ├─ YES → Has native modules, check compatibility
│       └─ NO → Pure JS, straightforward patching
└─ NO → Native Cocoa app
    └─ Requires full rewrite or Wine wrapping (out of scope, see Phase 4 mapping only)

Step 2B: Extract Metadata

BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print CFBundleIdentifier" "$APP_DIR/Info.plist")
APP_NAME=$(/usr/libexec/PlistBuddy -c "Print CFBundleName" "$APP_DIR/Info.plist")
APP_VERSION=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" "$APP_DIR/Info.plist")
MIN_MACOS=$(/usr/libexec/PlistBuddy -c "Print LSMinimumSystemVersion" "$APP_DIR/Info.plist")
ELECTRON_VER=$(cat "$APP_DIR/Frameworks/Electron Framework.framework/Versions/A/Info.plist" \
  | /usr/libexec/PlistBuddy -c "Print CFBundleVersion" /dev/stdin 2>/dev/null || echo "unknown")

Phase 3: Electron App Conversion (ASAR Patching)

Run the automated patcher or apply manually. See {baseDir}/references/asar-patching-guide.md for full details.

# Automated (recommended)
node {baseDir}/scripts/patch-windows.js --input ./app_source --output ./app_win --target win32

# Manual extraction + inspection
npx asar extract "$APP_DIR/Resources/app.asar" ./app_unpacked/
# Apply 7-category patches (see below), then:
npx asar pack ./app_patched/ ./app_win/Resources/app.asar

7-Category Patch Summary

# Category What Changes Critical Pattern
1 Window mgmt titleBarStyle:'hiddenInset' → frame:true titleBarStyle\s*[:=]\s*['"]hiddenInset['"]
2 Paths macOS-specific → process.platform guards `'/Users/
3 Single instance Unix socket → Named Mutex (Win) / flock (Linux) `.sock['")]
4 Tray .png → .ico on Windows `tray.set.*Icon
5 Auto-updater Squirrel.Mac → Squirrel.Windows/AppImage `autoUpdater
6 Desktop names CFBundleIdentifier → platform IDs `CFBundleIdentifier
7 Quit guard beforeunload preventDefault → platform shutdown beforeunload.*preventDefault

Phase 4: Platform Equivalent Mapping

Full table in {baseDir}/references/platform-equivalents.md. Key mappings:

macOS Linux Windows
Shell (/bin/bash) Bash PowerShell (pwsh.exe)
Unix socket (.sock) Unix socket / D-Bus Named Pipe (\\.\pipe\)
prctl(PR_SET_PDEATHSIG) prctl Windows Job Object
.desktop file .desktop + D-Bus Registry HKCR + URL protocol
Polkit Polkit / pkexec UAC (ShellExecute runas)
launchd systemd user unit Task Scheduler / Registry Run
Keychain libsecret Credential Manager

Phase 5: Windows Launcher (Rust)

Full guide in {baseDir}/references/rust-launcher-guide.md. Summary:

# Setup
rustup target add x86_64-pc-windows-gnu
cargo install cargo-zigbuild

# Build
cd launcher-win && cargo zigbuild --target x86_64-pc-windows-gnu --release
# Output: target/x86_64-pc-windows-gnu/release/myapp-launcher.exe (~2.2MB)

Key Implementation Points

  1. Single Instance: CreateMutexW(None, FALSE, "Global\\MyApp-SingleInstance") — check GetLastError() == ERROR_ALREADY_EXISTS
  2. Process Lifecycle: CreateJobObjectW + AssignProcessToJobObject + SetInformationJobObject(JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE)
  3. Port Allocation: Bind 127.0.0.1:0, read assigned port, pass to browser
  4. TLS: Use rustls-tls feature (NOT native-tls) to avoid Windows SDK dependency

Phase 6: Linux Launcher (Shell)

#!/usr/bin/env bash
APPDIR="$(cd "$(dirname "$0")" && pwd)"
export PATH="$APPDIR:$PATH"
export ELECTRON_RUN_AS_NODE=1

# Single instance via flock
LOCK_FILE="/tmp/${APP_NAME}.lock"
exec 9>"$LOCK_FILE"
flock -n 9 || { echo "Already running"; exit 0; }

# D-Bus activation (optional)
dbus-send --session --dest=org.freedesktop.DBus --type=method_call \
  /org/freedesktop/DBus org.freedesktop.DBus.RequestName string:"com.example.$APP_NAME" 2>/dev/null

# Launch
exec "$APPDIR/electron" "$APPDIR/resources/app.asar" "$@"

XDG .desktop File

[Desktop Entry]
Name=MyApp
Comment=My Application
Exec=/opt/myapp/launcher.sh %U
Icon=myapp
Type=Application
Categories=Development;
MimeType=x-scheme-handler/myapp;
StartupNotify=true

systemd User Unit

[Unit]
Description=MyApp
After=graphical-session.target

[Service]
Type=simple
ExecStart=/opt/myapp/launcher.sh
Restart=on-failure

[Install]
WantedBy=default.target

Phase 7: NSIS Installer (Windows)

Full guide in {baseDir}/references/nsis-installer-guide.md.

!include "MUI2.nsh"
Name "MyApp"
OutFile "MyApp-Setup-1.0.0.exe"
InstallDir "$PROGRAMFILES64\MyApp"

!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_LANGUAGE "English"

Section "MainSection" SEC01
  SetOutPath "$INSTDIR"
  File /r "dist\win\*.*"
  CreateShortCut "$DESKTOP\MyApp.lnk" "$INSTDIR\MyApp.exe"
  CreateShortCut "$SMPROGRAMS\MyApp.lnk" "$INSTDIR\MyApp.exe"
SectionEnd

Section "URLProtocol"
  WriteRegStr HKCR "myapp" "" "URL:MyApp Protocol"
  WriteRegStr HKCR "myapp" "URL Protocol" ""
  WriteRegStr HKCR "myapp\shell\open\command" "" '"$INSTDIR\MyApp.exe" "%1"'
SectionEnd

Section "Uninstall"
  Delete "$INSTDIR\*.*"
  RMDir /r "$INSTDIR"
  Delete "$DESKTOP\MyApp.lnk"
  Delete "$SMPROGRAMS\MyApp.lnk"
  DeleteRegKey HKCR "myapp"
SectionEnd

Phase 8: AppImage / dpkg (Linux)

AppImage

mkdir -p AppDir/usr/bin AppDir/usr/lib AppDir/usr/share/applications AppDir/usr/share/icons/hicolor/256x256/apps
cp -r dist/linux/* AppDir/usr/bin/
cp icons/myapp-256.png AppDir/usr/share/icons/hicolor/256x256/apps/myapp.png
cat > AppDir/myapp.desktop << 'EOF'
[Desktop Entry]
Name=MyApp;Exec=myapp;Icon=myapp;Type=Application;Categories=Development;
EOF
cat > AppDir/AppRun << 'SCRIPT'
#!/bin/bash
SELF=$(readlink -f "$0")
HERE=${SELF%/*}
exec "$HERE/usr/bin/launcher.sh" "$@"
SCRIPT
chmod +x AppDir/AppRun
appimagetool AppDir MyApp-1.0.0-x86_64.AppImage

dpkg

mkdir -p pkg/DEBIAN pkg/usr/bin pkg/usr/lib/myapp pkg/usr/share/applications pkg/usr/share/icons
# control file
cat > pkg/DEBIAN/control << EOF
Package: myapp
Version: 1.0.0
Architecture: amd64
Maintainer: dev@example.com
Description: My Application
Depends: libgtk-3-0, libnotify4, libnss3, libxss1
EOF
cp -r dist/linux/* pkg/usr/lib/myapp/
dpkg-deb --build pkg myapp_1.0.0_amd64.deb

Phase 9: Testing & Validation

Binary Verification

# Windows launcher
file target/x86_64-pc-windows-gnu/release/myapp-launcher.exe
# Expected: PE32+ executable (console) x86-64

# ASAR integrity
sha256sum app_source/Resources/app.asar > original.sha256
sha256sum app_win/Resources/app.asar > patched.sha256
# They SHOULD differ (content was patched), but verify structure:
npx asar list app_win/Resources/app.asar | wc -l  # Should match original count

Platform-Specific Tests

# Windows (in VM or Wine)
wine myapp-launcher.exe --version        # Should not crash
# Linux
./MyApp-1.0.0-x86_64.AppImage --version  # Should launch
xdg-mime default myapp.desktop x-scheme-handler/myapp  # URL protocol

Validation Checklist

  • App launches on both platforms
  • Single instance enforcement works
  • URL protocol (myapp://) triggers app
  • Tray icon appears with correct format
  • Auto-updater points to correct platform feed
  • Window controls (min/max/close) work
  • File dialogs show correct filters
  • App data stored in correct platform path
  • Uninstaller removes all files and registry entries (Windows)
  • .desktop file validates: desktop-file-validate myapp.desktop

Error Handling

Phase Error Cause Fix
1 dmg2img: no such file Missing tool apt install dmg2img
1 hfsplus mount fails Kernel module missing modprobe hfsplus; apt install hfsprogs
2 plistlib parse error Binary plist plutil -convert xml1 Info.plist first
3 asar extract ENOENT Corrupt asar Re-extract from DMG
3 patch regex no match Obfuscated code Search for functional equivalents
5 native-tls link error Windows SDK missing Switch to rustls-tls in Cargo.toml
5 zigbuild target error Target not added rustup target add x86_64-pc-windows-gnu
7 NSIS compile error Bad path with spaces Quote all paths, avoid spaces in INSTDIR
8 AppImage FUSE error Missing FUSE apt install libfuse2 or --appimage-extract

Security Considerations

  1. Never embed secrets in ASAR — it's plaintext, extractable with npx asar extract
  2. Sign Windows binaries with Authenticode (signtool) or SmartScreen will warn users
  3. Verify Electron version — old versions have known CVEs; update if < latest stable
  4. Lock mutex name — use Global\Vendor-AppName-Hash to prevent squatting
  5. Sandbox the server — launcher should drop privileges after port allocation
  6. HTTPS only — for auto-updater feeds, pin certificates if possible
  7. Review native modules — .node binaries in app.asar.unpacked must be recompiled for target OS