{"id":32278232,"url":"https://github.com/marlonjd/flutter_desktop_updater","last_synced_at":"2026-06-12T00:01:04.860Z","repository":{"id":271181393,"uuid":"912627649","full_name":"MarlonJD/flutter_desktop_updater","owner":"MarlonJD","description":"Flutter desktop updater plugin, supports Windows, MacOS and Linux. It can download and install updates for your desktop application. Github, Gitlab and local file systems, s3 are supported","archived":false,"fork":false,"pushed_at":"2026-06-11T20:15:02.000Z","size":722,"stargazers_count":46,"open_issues_count":22,"forks_count":28,"subscribers_count":2,"default_branch":"main","last_synced_at":"2026-06-11T21:05:33.579Z","etag":null,"topics":["flutter","flutter-desktop","ota","updater"],"latest_commit_sha":null,"homepage":"https://pub.dev/packages/desktop_updater","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MarlonJD.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-01-06T04:11:42.000Z","updated_at":"2026-06-11T20:15:25.000Z","dependencies_parsed_at":"2025-06-02T16:14:55.906Z","dependency_job_id":"22d9cc54-5177-416c-b339-de369bc1c8df","html_url":"https://github.com/MarlonJD/flutter_desktop_updater","commit_stats":null,"previous_names":["marlonjd/flutter_desktop_updater"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/MarlonJD/flutter_desktop_updater","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarlonJD%2Fflutter_desktop_updater","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarlonJD%2Fflutter_desktop_updater/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarlonJD%2Fflutter_desktop_updater/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarlonJD%2Fflutter_desktop_updater/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MarlonJD","download_url":"https://codeload.github.com/MarlonJD/flutter_desktop_updater/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarlonJD%2Fflutter_desktop_updater/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34222709,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-11T02:00:06.485Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["flutter","flutter-desktop","ota","updater"],"created_at":"2025-10-23T00:18:28.096Z","updated_at":"2026-06-12T00:01:04.765Z","avatar_url":"https://github.com/MarlonJD.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"# desktop_updater\n\nFlutter desktop updater plugin for macOS, Windows, and Linux.\n\nThe 2.0 line uses a zip-first release contract:\n\n```text\napp-archive.json -\u003e release.json -\u003e one verified zip artifact\n```\n\nThis avoids public folder listing, works with signed URLs and private buckets, and lets the updater verify the exact artifact length and SHA-256 before extraction or installation.\n\n![flutter_desktop_updater](https://github.com/user-attachments/assets/b05d9a13-0f44-4213-b3bd-58e07c18226d)\n\n- [2.0 roadmap](https://github.com/MarlonJD/flutter_desktop_updater/blob/main/docs/2.0-roadmap.md)\n- [1.x to 2.0 migration guide](https://github.com/MarlonJD/flutter_desktop_updater/blob/main/docs/migration/1.x-to-2.0.md)\n- [GitHub Actions CI/CD guide](https://github.com/MarlonJD/flutter_desktop_updater/blob/main/docs/github-actions-ci-cd.md)\n- [Agent migration prompt](https://github.com/MarlonJD/flutter_desktop_updater/blob/main/docs/migration/agent-prompt.md)\n\n## Version Lines\n\n- `1.x`: stable maintenance line for the legacy folder-based update contract.\n- `2.x`: current stable line for the zip-first release contract.\n\nApps already shipping with 1.x should keep their existing release contract until their app code and publishing pipeline have both migrated to 2.0.\n\n## Why 2.0 Was Rewritten\n\nThe 1.x updater expected a public or fetchable update folder. That worked for simple static hosting, but it made modern production setups awkward:\n\n- private buckets and signed URLs do not naturally expose folder listings;\n- CDN/proxy behavior can change directory-style publishing assumptions;\n- macOS `.app` bundles are easy to damage when uploaded as raw directory trees;\n- update clients need one exact artifact to hash, verify, stage, and install safely.\n\n2.0 changes the release model to one descriptor and one zip artifact per platform release. The client never needs S3 bucket listing, public folder hosting, or directory traversal on the server.\n\n## Install\n\n```yaml\ndependencies:\n  desktop_updater: ^2.0.0\n```\n\nInstall the CLI:\n\n```sh\ndart pub global activate desktop_updater\n```\n\n## Runtime Usage\n\nCreate a controller with your hosted `app-archive.json` URL:\n\n```dart\nimport \"package:desktop_updater/desktop_updater.dart\";\nimport \"package:desktop_updater/updater_controller.dart\";\n\nlate final DesktopUpdaterController controller;\n\n@override\nvoid initState() {\n  super.initState();\n  controller = DesktopUpdaterController(\n    appArchiveUrl: Uri.parse(\"https://updates.example.com/app-archive.json\"),\n  );\n}\n```\n\nWrap your UI with one of the update widgets:\n\n```dart\nDesktopUpdateWidget(\n  controller: controller,\n  child: const YourHomePage(),\n)\n```\n\nUse `skipInitialVersionCheck` when you want to control the first check yourself:\n\n```dart\ncontroller = DesktopUpdaterController(\n  appArchiveUrl: Uri.parse(\"https://updates.example.com/app-archive.json\"),\n  skipInitialVersionCheck: true,\n);\n\nawait controller.checkVersion();\n```\n\nmacOS signing and notarization gates are enabled by default for Release\nupdates. Owners who deliberately support an unsigned direct-distribution lane\ncan opt out, but that lane is only release-mechanics ready and is not\nproduction-trusted:\n\n```dart\ncontroller = DesktopUpdaterController(\n  appArchiveUrl: Uri.parse(\"https://updates.example.com/app-archive.json\"),\n  allowUnsignedMacOSUpdates: true,\n);\n```\n\nThe same opt-out is available for manual installs:\n\n```dart\nawait DesktopUpdater().installUpdate(\n  stagingPath: \"/path/to/staged/Example.app\",\n  allowUnsignedMacOSUpdates: true,\n);\n```\n\nPrefer the typed 2.0 state API for new code:\n\n```dart\nswitch (controller.state) {\n  case UpdateAvailable(:final descriptor, :final mandatory):\n    print(\"Update ${descriptor.version} is available. Mandatory: $mandatory\");\n  case UpdateDownloading(:final receivedBytes, :final totalBytes):\n    print(\"Downloaded $receivedBytes of $totalBytes bytes\");\n  case UpdateReadyToInstall(:final stagingPath):\n    print(\"Ready to install from $stagingPath\");\n  case UpdateFailed(:final error):\n    print(\"Update failed: $error\");\n  default:\n    break;\n}\n```\n\nThe legacy boolean getters such as `needUpdate`, `isDownloading`, `isDownloaded`, and `downloadProgress` remain available as compatibility helpers during migration.\n\n## Release Contract\n\n`app-archive.json` selects the best release for the current platform, channel, and installed version.\n\n```json\n{\n  \"schemaVersion\": 3,\n  \"appName\": \"Example App\",\n  \"items\": [\n    {\n      \"version\": \"2.0.0\",\n      \"buildNumber\": 200,\n      \"platform\": \"macos\",\n      \"channel\": \"stable\",\n      \"mandatory\": false,\n      \"release\": \"https://updates.example.com/releases/example/2.0.0/macos/release.json\"\n    }\n  ]\n}\n```\n\n`release.json` describes exactly one zip artifact.\n\n```json\n{\n  \"schemaVersion\": 3,\n  \"packageId\": \"com.example.app\",\n  \"appName\": \"Example.app\",\n  \"version\": \"2.0.0\",\n  \"buildNumber\": 200,\n  \"platform\": \"macos\",\n  \"channel\": \"stable\",\n  \"artifact\": {\n    \"kind\": \"zip\",\n    \"url\": \"https://cdn.example.com/releases/example/2.0.0/macos/Example-2.0.0-macos.zip\",\n    \"sha256\": \"64-lowercase-hex-characters\",\n    \"length\": 12345678\n  },\n  \"install\": {\n    \"strategy\": \"wholeBundleReplace\"\n  },\n  \"minimumUpdaterVersion\": \"2.0.0\",\n  \"generatedAt\": \"2026-06-11T00:00:00Z\"\n}\n```\n\n`buildNumber` is optional in both `app-archive.json` items and `release.json`.\nInclude it when your app exposes a monotonic build number. Omit it when the\ninstalled app only exposes a semantic version such as `1.2.3`; in that case\n2.0 uses semantic version ordering and will not treat a remote build number as\nnewer than an equal installed semantic version with no build metadata.\n\nSupported install strategies:\n\n- `wholeBundleReplace`: macOS `.app` bundle replacement.\n- `wholeDirectoryReplace`: Windows and Linux app directory replacement.\n\nThe optional `signature` field is reserved for production authenticity policies. The built-in verifier currently validates descriptor shape, exact URL support, artifact length, SHA-256, and zip safety. Projects that require signed descriptors should wire `ArtifactVerificationPolicy.signatureVerifier` or wait for the planned first-party signing gate before calling a direct-zip Linux or Windows build fully production signed.\n\n## Package A Release\n\nBuild your app first, then package the exact release artifact.\n\nmacOS:\n\n```sh\nflutter build macos --release\n\n# Sign, notarize, and staple the .app before packaging.\n\ndart run desktop_updater:package \\\n  --input build/macos/Build/Products/Release/Example.app \\\n  --output dist/2.0.0/macos \\\n  --package-id com.example.app \\\n  --app-name Example.app \\\n  --version 2.0.0 \\\n  --build-number 200 \\\n  --platform macos \\\n  --channel stable \\\n  --install-strategy wholeBundleReplace \\\n  --artifact-url https://cdn.example.com/releases/example/2.0.0/macos/Example.app.zip\n```\n\nWindows:\n\n```sh\nflutter build windows --release\n\ndart run desktop_updater:package \\\n  --input build/windows/x64/runner/Release \\\n  --output dist/2.0.0/windows \\\n  --package-id com.example.app \\\n  --app-name Example \\\n  --version 2.0.0 \\\n  --build-number 200 \\\n  --platform windows \\\n  --channel stable \\\n  --install-strategy wholeDirectoryReplace \\\n  --artifact-url https://cdn.example.com/releases/example/2.0.0/windows/Example-windows.zip\n```\n\nLinux:\n\n```sh\nflutter build linux --release\n\ndart run desktop_updater:package \\\n  --input build/linux/x64/release/bundle \\\n  --output dist/2.0.0/linux \\\n  --package-id com.example.app \\\n  --app-name Example \\\n  --version 2.0.0 \\\n  --build-number 200 \\\n  --platform linux \\\n  --channel stable \\\n  --install-strategy wholeDirectoryReplace \\\n  --artifact-url https://cdn.example.com/releases/example/2.0.0/linux/Example-linux.zip\n```\n\nVerify a packaged release:\n\n```sh\ndart run desktop_updater:verify --release dist/2.0.0/macos/release.json\n```\n\n`--build-number` is optional. Omit it when your app does not expose build\nmetadata and you want update checks to rely only on semantic versions.\n\nPublish:\n\n- `app-archive.json`\n- `release.json`\n- the zip artifact referenced by `release.json`\n\nDo not publish or rely on public update folders for the 2.0 contract.\n\n## GitHub Actions CI/CD\n\nThe package repository runs a public, secretless GitHub Actions CI matrix for\nDart, Linux, and Windows. It verifies formatting, analysis, tests, CLI\nentrypoints, pub dry-runs, native tests, integration tests, and debug/release\nupdate smoke tests.\n\nReal automatic update publishing belongs in your app repository, not in this\npackage repository. Your app owns the bundle ID, signing certificate,\nnotarization credentials, update hosting, release approval policy, and platform\npublisher-trust gates.\n\nUse the CI/CD guide when wiring an app release workflow:\n\n- [GitHub Actions CI/CD for automatic updates](https://github.com/MarlonJD/flutter_desktop_updater/blob/main/docs/github-actions-ci-cd.md)\n- Build the app in Release mode.\n- Sign, notarize, staple, and Gatekeeper-check macOS artifacts before zipping.\n- Sign Windows artifacts when publisher trust is required.\n- Add a descriptor authenticity layer for production-trusted Linux direct zip distribution.\n- Run `dart run desktop_updater:package` to generate the zip and `release.json`.\n- Upload versioned artifacts first, verify the hosted `release.json`, then publish `app-archive.json` last.\n\n## Hosting Requirements\n\n- Serve exact URLs for `app-archive.json`, `release.json`, and the zip artifact.\n- Do not require bucket listing.\n- Signed URLs are supported when they remain valid for the full check and download flow.\n- CDN/proxy transformations that change bytes will fail SHA-256 verification by design.\n- Use HTTPS for production update metadata and artifacts.\n\n## Readiness And Platform Trust\n\ndesktop_updater separates update mechanics from platform publisher trust:\n\n- `release-mechanics ready`: a Release build can check, download, verify,\n  stage, install, relaunch, and roll back through the zip-first flow.\n- `production-trusted ready`: the Release artifact also satisfies the\n  platform's publisher-authenticity expectations.\n\nUnsigned Windows and Linux Release builds can be accepted as\nrelease-mechanics ready for this Flutter package. Most Flutter desktop apps\nwill not have Windows Authenticode certificates or a Linux-specific signing\npolicy. Those apps may still use the direct zip updater, but their users can\nsee operating-system or distribution trust warnings, and the package does not\nclaim platform trust for them.\n\nmacOS is stricter by default. Public direct distribution should be\nDeveloper ID signed, hardened-runtime enabled, notarized, stapled, and\nGatekeeper accepted before being called production-trusted. Apps that need an\nunsigned macOS lane can set `allowUnsignedMacOSUpdates: true`; this bypasses\nthe native codesign, Gatekeeper, stapler, and Team ID gates, while still\nrequiring a complete `.app` bundle with the same `CFBundleIdentifier`. A macOS\nupdate installed with this opt-out remains release-mechanics only and may be\nblocked or warned on by Gatekeeper.\n\n## Platform Behavior\n\n### macOS\n\nmacOS stages a complete `.app` and replaces the installed bundle only after native gates pass.\n\nProduction-trusted requirements:\n\n- Build a Release `.app`.\n- Sign with a `Developer ID Application` identity.\n- Enable hardened runtime.\n- Notarize the signed app.\n- Staple the notarization ticket.\n- Keep `CFBundleIdentifier` and Team ID stable across releases.\n- Keep App Sandbox disabled for this whole-app replacement strategy.\n- Ensure production entitlements do not include `get-task-allow`.\n\nNotarization credentials are stored as a local Keychain profile. When creating\nthe profile, keep the exact `--keychain-profile` and `--keychain` values printed\nby `notarytool`; use both values again for `history`, `submit`, and any\nautomation that notarizes release artifacts:\n\n```sh\nxcrun notarytool store-credentials desktop-updater-notary \\\n  --key \"$HOME/Developer/secrets/AuthKey_XXXXXXXXXX.p8\" \\\n  --key-id \"XXXXXXXXXX\" \\\n  --issuer \"00000000-0000-0000-0000-000000000000\" \\\n  --keychain \"$HOME/Library/Keychains/login.keychain-db\" \\\n  --validate\n\nxcrun notarytool history \\\n  --keychain-profile desktop-updater-notary \\\n  --keychain \"$HOME/Library/Keychains/login.keychain-db\" \\\n  --output-format json\n```\n\nIf `store-credentials` says the credentials were saved but a later command says\n`No Keychain password item found`, the profile was not read from the same\nKeychain. Re-run the command with an explicit `--keychain`, or copy the exact\n`--keychain-profile ... --keychain ...` pair from the success message. When\ncopying multi-line shell commands, the trailing `\\` must be the final character\non the line; a space after it breaks argument continuation.\n\nThe macOS Release helper requires these gates by default. Set\n`allowUnsignedMacOSUpdates: true` only when the app owner intentionally accepts\nunsigned release mechanics, such as an internal lab, a local enterprise flow,\nor a user-controlled distribution channel. That opt-out does not make the app\nproduction-trusted.\n\nValidation commands:\n\n```sh\ncodesign --verify --deep --strict --verbose=2 Example.app\nspctl --assess --type execute --verbose=2 Example.app\nxcrun stapler validate Example.app\ncodesign -dvvv --entitlements :- Example.app\n```\n\nMac App Store or sandboxed apps should use the store update channel instead of this direct self-updater.\n\n### Windows\n\nWindows schedules a detached PowerShell helper so locked `.exe` and `.dll` files are replaced only after the running app exits. The helper backs up the current app directory and rolls back if replacement fails.\n\nUnsigned Release builds can be accepted as release-mechanics ready. Production-trusted direct distribution should additionally sign `.exe` and `.dll` files with Authenticode and verify them with `signtool`.\n\n### Linux\n\nLinux schedules a detached Bash helper that resolves the running executable, replaces the app directory without relying on the current working directory, rejects removed paths outside the app root, and rolls back on failure.\n\nLinux has no single OS-level Developer ID equivalent. Unsigned Release builds can be accepted as release-mechanics ready. Direct zip distribution should use release descriptor signing or another publisher-authenticity layer before being treated as production-trusted. Flatpak, Snap, deb, rpm, or distro repositories should normally use their own update channels.\n\n## SwiftPM And CocoaPods On macOS\n\nSwift Package Manager is the primary macOS plugin integration path for 2.0. CocoaPods remains supported as a fallback for apps that disable SwiftPM or still run older Flutter tooling.\n\nSwiftPM lane:\n\n```sh\nflutter config --enable-swift-package-manager\ncd example\nflutter test integration_test -d macos\n```\n\nCocoaPods fallback lane:\n\n```sh\nflutter config --no-enable-swift-package-manager\ncd example\nflutter test integration_test -d macos\n```\n\nDo not commit a `Podfile` in the SwiftPM-first example app. When SwiftPM is disabled, Flutter creates the CocoaPods files needed for the fallback lane and then runs `pod install`.\n\n## Testing\n\nDart/package checks:\n\n```sh\ndart format --set-exit-if-changed .\nflutter analyze --no-fatal-infos\nflutter test --no-pub\ndart pub publish --dry-run\n```\n\nmacOS native smoke:\n\n```sh\ncd example\nflutter build macos --debug\ndart run tool/updater_smoke.dart\n```\n\nmacOS production smoke requires a signed, notarized, stapled staged `.app` that already contains the smoke sentinel before signing:\n\n```sh\ncd example\ndart run tool/updater_smoke.dart \\\n  --production-gates \\\n  --app /path/to/installed/Example.app \\\n  --staged-app /path/to/notarized/update/Example.app\n```\n\nWindows smoke:\n\n```sh\ncd example\nflutter build windows --debug\ndart run tool/updater_smoke.dart\n```\n\nLinux smoke:\n\n```sh\ncd example\nflutter build linux --debug\ndart run tool/updater_smoke.dart\n```\n\nBy default the smoke runner skips relaunch so CI does not leave an app open. Add `--relaunch` when you want to test the close-copy-reopen flow manually.\n\n## Migration\n\nRead [Migrating From 1.x To 2.0](https://github.com/MarlonJD/flutter_desktop_updater/blob/main/docs/migration/1.x-to-2.0.md) before changing a shipped app. The migration must update both sides of the system:\n\n- app code: prefer typed `UpdateState` and keep compatibility getters only during migration;\n- release publishing: replace folder uploads with `app-archive.json -\u003e release.json -\u003e zip`;\n- platform validation: add macOS signing/notarization/stapling for production-trusted distribution, Windows signing when publisher trust is required, and Linux descriptor authenticity when direct zip publisher trust is required.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarlonjd%2Fflutter_desktop_updater","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarlonjd%2Fflutter_desktop_updater","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarlonjd%2Fflutter_desktop_updater/lists"}