Skip to main content

Overview

AppUpdater handles the complete update installation workflow: downloading DMG files from GitHub releases, mounting the DMG, copying the new app version, and replacing the running app. It provides observable state for UI progress tracking and implements safety checks to prevent installation errors.
Located at: Fiaxe/Services/AppUpdater.swift:9
Update WorkflowThe updater works in conjunction with UpdateService which fetches release metadata from GitHub. AppUpdater handles the actual download and installation.

Type Definition

Main actor-isolated singleton that manages download and installation state.

Singleton Instance

Access the shared updater instance from the main actor.

State Management

The updater tracks installation progress through an observable state enum:
State
No update operation in progress.
State
Downloading DMG from GitHub. Associated value is progress (0.0 to 1.0).
State
DMG downloaded and cached, ready to install. Contains local DMG URL.
State
Installing update (mounting DMG, copying files, replacing app).
State
Update failed. Associated value contains error message.
The @Observable macro makes state changes automatically update SwiftUI views.

Methods

install()

Starts the update installation process.
GitHubRelease
required
GitHub release to install. Must contain a DMG asset.
Internal implementation calls download(release:).

download()

Downloads the DMG file from the release.
GitHubRelease
required
Release containing the DMG download URL.

Implementation Details

The method:
  1. Extracts DMG URL from release assets
  2. Sets state to .downloading(0)
  3. Creates a URLSession with self as delegate for progress tracking
  4. Starts the download task

cancel()

Cancels an in-progress download.
Sets state back to .idle and cancels the download task.

installDownloaded()

Installs a previously downloaded DMG.
Only works when state is .downloaded(URL). Calls mountAndInstall(dmg:) internally.

Update Capability Checks

canSelfUpdate

Whether the app can update itself in place.

updateBlockReason

Reason why self-update is blocked, or nil if allowed.

Implementation Details

Returns an error message if:
  • Running from Xcode build (/DerivedData/ or /Build/Products/)
  • App isn’t in a writable location (e.g., inside a mounted DMG)

URLSessionDownloadDelegate

The updater implements URLSessionDownloadDelegate for progress tracking.

Progress Updates

Called periodically during download:
Updates state with download progress (0.0 to 1.0).

Download Completion

Moves downloaded file to cache directory:
Cached DMG path:

Installation Process

The mountAndInstall(dmg:) method handles the complex installation workflow:
1

Mount DMG

Uses hdiutil attach to mount the DMG and parse the mount point:
Parses plist output to find mount point (e.g., /Volumes/R2Vault).
2

Find .app Bundle

Searches mounted volume for the .app bundle:
3

Copy to Temp Location

Copies the new app to temp to avoid modifying the running app:
4

Unmount DMG

Detaches the DMG after copying:
5

Schedule Post-Quit Replacement

Launches a background shell script that:
  1. Waits for the current app to quit
  2. Replaces the old app with the new one
  3. Removes quarantine attributes
  4. Relaunches the app
6

Quit App

Terminates the running app:

Error Handling

UpdateError
Couldn’t find a .app bundle inside the mounted DMG.
UpdateError
Failed to determine the running app’s file path.
UpdateError
hdiutil attach failed or didn’t return a mount point.
UpdateError
A shell command exited with non-zero status. Includes command, exit code, and stderr.
UpdateError
A command exceeded its timeout duration.
UpdateError
The app’s location isn’t writable (e.g., inside a read-only DMG).

Logging

The updater logs all operations to a file:
Log format:
Useful for debugging installation failures.

Usage Example

Security Considerations

Update SecurityThe current implementation:
  • Uses HTTPS for GitHub downloads (transport security)
  • Removes quarantine attributes (com.apple.quarantine)
  • Does NOT verify code signatures
  • Does NOT verify DMG checksums
For production apps, consider:
  • Verifying DMG checksums from release notes
  • Checking code signatures before installation
  • Using Apple’s Sparkle framework
  • Implementing delta updates

App Store Distribution

If distributing via the Mac App Store:
  • Remove the auto-update system entirely
  • App Store handles all updates automatically
  • Self-updating violates App Store guidelines
  • Can still check GitHub API to notify users