Updater
Updater menyediakan pembaruan perangkat lunak dalam aplikasi tanpa memaksa Anda membangun pipeline download / verify / swap sendiri. Updater duduk di atas app.Updater, menerima satu atau lebih Provider yang dapat di-plug (GitHub Releases, keygen.sh, Sparkle AppCast, atau milik Anda sendiri), mengautentikasi download terhadap public key yang dikonfigurasi, menukar binary yang sedang berjalan dengan aman, dan menampilkan setiap transisi melalui event bus Wails standar.

Quick start
Section titled “Quick start”package main
import ( "context" "log"
"github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/updater" "github.com/wailsapp/wails/v3/pkg/updater/providers/github")
func main() { app := application.New(application.Options{Name: "Demo"})
gh, _ := github.New(github.Config{Repository: "myorg/myapp"}) if err := app.Updater.Init(updater.Config{ CurrentVersion: "1.0.0", Providers: []updater.Provider{gh}, }); err != nil { log.Fatal(err) }
if err := app.Updater.CheckAndInstall(context.Background()); err != nil { log.Printf("update: %v", err) }
_ = app.Run()}Itu membuka window update framework, memeriksa GitHub, mengunduh asset platform, memverifikasinya, menukar binary, dan menunggu pengguna restart.
Lifecycle
Section titled “Lifecycle”app.Updater adalah state machine dengan state berikut (updater.State):
| State | Kapan |
|---|---|
unconfigured | Sebelum Init dipanggil |
idle | Setelah Init, sebelum pemeriksaan apa pun |
checking | Check sedang berjalan |
up-to-date | Respons provider terbaru mengatakan caller sudah current |
available | Release baru ditemukan; belum mengunduh |
downloading | Byte sedang streaming dari provider |
verifying | Download selesai, signature/digest sedang diperiksa |
installing | Byte terverifikasi sedang di-unpack dan di-rename ke staging dir |
ready | Update di-stage; panggil Restart untuk menerapkan |
error | Langkah sebelumnya gagal |
Anda dapat membaca state saat ini dengan app.Updater.State() kapan saja. Setiap transisi juga mengirim Wails event (lihat Events).
Window default mencerminkan state saat ini secara otomatis — misalnya, saat Check tidak menemukan upgrade, pengguna melihat ini dan menutup dengan Close:

Providers
Section titled “Providers”Provider adalah apa pun yang memenuhi interface ini:
type Provider interface { Name() string Check(ctx context.Context, req CheckRequest) (*Release, error) Download(ctx context.Context, r *Release, dst io.Writer, onProgress func(written, total int64)) error}Tiga implementasi tersedia in-tree.
GitHub Releases — updater/providers/github
Section titled “GitHub Releases — updater/providers/github”gh, err := github.New(github.Config{ Repository: "myorg/myapp", // owner/repo Anda (wajib) Token: "ghp_…", // opsional; meningkatkan rate limit + repo private Prerelease: false, // sertakan pre-release dalam lookup latest ChecksumAsset: "SHA256SUMS", // asset sibling opsional untuk verifikasi digest BaseURL: "", // override opsional (mis. GitHub Enterprise) AssetMatcher: nil, // asset-picker kustom opsional; nil memakai DefaultAssetMatcher HTTPClient: nil, // override client opsional})Default asset matcher memilih berdasarkan substring GOOS + GOARCH pada nama file, mengenali alias umum (amd64 / x86_64 / x64, arm64 / aarch64, 386 / i386 / x86 / ia32). Untuk skema penamaan kustom:
gh, _ := github.New(github.Config{ Repository: "myorg/myapp", AssetMatcher: func(req updater.CheckRequest, assets []github.ReleaseAsset) int { for i, a := range assets { if strings.Contains(a.Name, "my-naming-convention") && strings.Contains(a.Name, req.Platform) { return i } } return -1 // tidak ada kecocokan },})ChecksumAsset adalah nama asset release sibling yang kontennya baris <sha256> <filename> (format yang dihasilkan sha256sum dan shasum -a 256). Provider mengambilnya saat Check, menemukan baris yang cocok dengan artifact yang dipilih, dan mengisi Release.Verification.Digest agar framework memverifikasi download.
keygen.sh — updater/providers/keygen
Section titled “keygen.sh — updater/providers/keygen”kg, err := keygen.New(keygen.Config{ Account: "your-account-slug", // wajib Product: "product-uuid", // opsional tapi direkomendasikan jika account punya banyak product Package: "", // penyempitan lebih lanjut opsional Channel: "stable", // "stable" / "rc" / "beta" / "alpha" / "dev" Filetype: "", // filter filetype artifact opsional ("dmg", "exe", …) Token: "prod-…", // product / environment / user / admin token; menang atas LicenseKey LicenseKey: "", // auth license key (hanya dipakai saat Token kosong) BaseURL: "", // override base API opsional HTTPClient: nil, // override client opsional; redirect-strip wrapper tetap diterapkan})Provider secara otomatis memetakan checksum SHA-512 per-artifact dan signature Ed25519ph keygen.sh ke blok Release.Verification framework — tanpa wiring tambahan.
Format token: token keygen.sh membawa prefix role (admi- / prod- / envi- / user-). UUID mentah yang Anda lihat di dashboard adalah identifier token, bukan nilai secret — secret hanya terlihat saat token dibuat. Lihat auth docs keygen.sh untuk detail.
Sparkle AppCast — updater/providers/appcast
Section titled “Sparkle AppCast — updater/providers/appcast”ac, err := appcast.New(appcast.Config{ URL: "https://your.app/appcast.xml", // wajib Channel: "stable", // filter sparkle:channel opsional HTTPClient: nil, // override client opsional})Drop-in ke infrastruktur Sparkle / WinSparkle yang sudah ada tanpa perubahan. Membaca sparkle:shortVersionString, <enclosure url type length sparkle:os sparkle:edSignature>, dan sparkle:channel dari feed.
Signature DSA Sparkle 1 (sparkle:dsaSignature) tidak didukung — proyek pada skema signing itu harus beralih ke EdDSA (Sparkle 2).
Fallback chain
Section titled “Fallback chain”Config.Providers berurutan. Updater menelusurinya secara berurutan: provider pertama yang mengembalikan release menang, yang pertama melaporkan “up to date” mempersingkat chain (fallback untuk “primary unreachable”, bukan “provider tidak setuju”). Error lanjut ke provider berikutnya.
app.Updater.Init(updater.Config{ CurrentVersion: "1.0.0", Providers: []updater.Provider{ kg, // primary: pelanggan berlisensi gh, // fallback: mirror publik },})Menulis provider sendiri
Section titled “Menulis provider sendiri”Tiga method, ~150 baris untuk implementasi tipikal. Updater memiliki verifikasi, atomic staging, swap, dan window — kode provider menyelesaikan release berikutnya dan streaming byte:
type CustomProvider struct { /* config */ }
func (p *CustomProvider) Name() string { return "custom" }
func (p *CustomProvider) Check(ctx context.Context, req updater.CheckRequest) (*updater.Release, error) { // Hit endpoint update Anda, tentukan apakah req.CurrentVersion sudah current, // dan kembalikan nil (tanpa upgrade) atau *Release dengan Artifact + optional // Verification terisi. // Error di sini lanjut ke provider berikutnya di Config.Providers.}
func (p *CustomProvider) Download(ctx context.Context, r *updater.Release, dst io.Writer, onProgress func(int64, int64)) error { // Stream byte artifact ke dst. Panggil onProgress(written, total) saat // byte mengalir; Updater debounce emit ke ~10 Hz di event bus.}Gunakan provider in-tree sebagai referensi — masing-masing satu file Go.
Verifikasi kriptografis
Section titled “Verifikasi kriptografis”Release diautentikasi oleh verifier framework menggunakan Config.PublicKey sebagai trust root:
//go:embed publickey.pemvar publicKey []byte
app.Updater.Init(updater.Config{ CurrentVersion: "1.0.0", Providers: []updater.Provider{...}, PublicKey: publicKey,})Algoritma yang didukung (Release.Verification.SignatureAlgo):
| Algoritma | Yang ditandatangani | Catatan |
|---|---|---|
ed25519 | Digest SHA-256 artifact | Dipakai Sparkle EdDSA |
ed25519ph | Artifact penuh via pre-hash Ed25519ph (SHA-512 internal) | Dipakai keygen.sh |
ecdsa-p256 | Digest SHA-256 artifact | Signature raw r∥s dan DER diterima |
Plus digest-only (DigestAlgo: sha256 / sha512) saat release mengirim hash tanpa signature.
Config.PublicKey adalah SATU-SATUNYA trust anchor untuk verifikasi signature — sumber release tidak bisa mengganti key sendiri. Release yang membawa Signature tanpa Config.PublicKey yang dikonfigurasi gagal closed. Verifier menghitung digest dalam pass streaming saat download, jadi bahkan pada update multi-GB verifikasi tidak menambah pass disk ekstra.
Menghasilkan signing key
Section titled “Menghasilkan signing key”# Generate keypair Ed25519 baru untuk signingssh-keygen -t ed25519 -f updater-key -N "" -C "wails-updater"# updater-key — rahasiakan, pakai untuk sign release# updater-key.pub — bundle di app via go:embedAtau di Go:
import "crypto/ed25519"import "crypto/rand"
pub, priv, _ := ed25519.GenerateKey(rand.Reader)// Persist `priv` dengan aman (HSM, signing CI, dll.); embed `pub` di binary Anda.Format artifact
Section titled “Format artifact”Provider streaming byte untuk file apa pun yang Anda publikasikan; framework lalu unpack sebelum swap:
- Single binary (mis.
myapp-linux-amd64) — dipakai apa adanya. Umum di Linux. .zip— diekstrak in-place. Archive harus berisi tepat satu entry top-level (biasanya bundle.appmacOS atau single binary). Packaging yang direkomendasikan untuk macOS..tar.gz/.tgz— diekstrak in-place dengan aturan single-top-level-entry yang sama. Berguna untuk distribusi Linux yang mengirim runtime tree bersama binary.
Archive dengan lebih dari satu entry top-level ditolak: framework menukar satu target on-disk, jadi “swap archive ini ke tempat” ambigu saat archive berisi banyak hal. .dmg dan .pkg (macOS) serta .msi (Windows) tidak didukung di v1 — distribusikan .zip bundle sebagai gantinya. Ekstraksi menerapkan proteksi zip-slip, menolak symlink yang keluar dari root archive, dan membatasi total ukuran uncompressed (2 GiB) dan jumlah entry (50 000).
Window default
Section titled “Window default”app.Updater.CheckAndInstall(ctx) membuka window 520×540 milik framework dengan:
- Ikon hero berdasarkan state (biru ↓ untuk available/downloading, hijau ✓ untuk ready/up-to-date, merah ! untuk error)
- Pill versi:
v1.0.0 → v2.0.1 · 8.8 MB - Panel release-notes scrollable dengan Markdown yang di-render (paragraf, bold/italic, list, tabel GFM, inline code, fenced code block, h1–h3, link)
- Satu aksi primary per state (Install / Restart & Apply / Try Again)
- Aksi secondary bergaya ghost (Skip This Version / Remind Me Later)
- Dark/light mode via
prefers-color-scheme - Shimmer progress indeterminate saat total ukuran tidak diketahui
Window mendengarkan events updater:* di event bus Wails dan mengirim aksi updater:user:* kembali ke Go.
Theme via CSS variables
Section titled “Theme via CSS variables”app.Updater.Init(updater.Config{ // … Window: &updater.BuiltinWindow{ CSS: `:root { --accent: #ff6f00; --bg: #1a1a1a; --fg: #fafafa; }`, },})Stylesheet default mengekspos variabel ini — override mana pun:
| Variabel | Default (light) | Default (dark) |
|---|---|---|
--bg | #f8f8fa | #1a1a1c |
--surface | #ffffff | #232326 |
--surface-2 | #f0f0f3 | #2c2c30 |
--fg | #1d1d1f | #f5f5f7 |
--fg-dim | #6b6b73 | #b0b0b8 |
--fg-faint | #99999f | #7a7a82 |
--border | #d6d6dc | #3a3a3e |
--accent | #0a84ff | #0a84ff |
--accent-fg | #ffffff | — |
--success | #34c759 | — |
--error | #ff3b30 | — |
--radius | 10px | — |
--font | system stack | — |
Ganti template
Section titled “Ganti template”Sediakan HTML Anda sendiri; cukup dengarkan events wails:updater:* dan kirim aksi wails:updater:user:*:
app.Updater.Init(updater.Config{ // … Window: &updater.BuiltinWindow{ HTML: myCustomTemplate, },})Window InitialHTML dimuat tanpa origin asset-server, jadi tidak bisa fetch /wails/runtime.js secara dinamis. Ada dua cara berkomunikasi dengan host:
- Tulis HTML saja. Framework auto-inject shim
window.wails.Eventsminimal ke window apa pun yang dibuka denganWebviewWindowOptions.AllowSimpleEventEmit = truedanHTMLdiset — persis yang dilakukan path builtin dan BYO updater. Tanpa build step. Ini jalur yang dipakai contoh di bawah. - Bundle
@wailsio/runtimedengan bundler pilihan Anda (Vite, esbuild, Rollup) dan import ke custom HTML saat build time.Events.Onberfungsi out of the box karena purely client-side;Events.Emitmelalui fetch transport runtime, yang null origin rusak — jadi pasang transport postMessage kecil via hooksetTransportruntime yang route melaluiwindow._wails.invoke("wails:event:emit:<name>"). Injection framework no-op jikawindow.wails.Eventssudah ada, jadi kedua pendekatan tidak bentrok.
Bagaimanapun, JS di custom HTML memanggil API Events.On / Events.Emit yang sama:
<script>const { On, Emit } = window.wails.Events;
On("wails:updater:update-available", (e) => { const rel = e.data ?? e; document.getElementById("ver").textContent = rel.version;});
document.getElementById("install").addEventListener("click", () => Emit("wails:updater:user:install"));
// Minta host replay state saat ini agar kita paint benar saat (re)open.Emit("wails:updater:window:ready");</script>Shim mengekspos subset runtime modern yang dibutuhkan bare-name events: Events.On(name, cb) mengembalikan fungsi unsubscribe, dan Events.Emit(nameOrEventObject) route ke host via jalur postMessage wails:event:emit: yang gated. Terpasang sekali saat page-load sebelum inline script Anda jalan.
Jika Anda ingin override shim (atau memuat full runtime cara lain), set window.wails.Events sebelum tag <script> pertama halaman dieksekusi dan injection melewatinya.
Window chrome
Section titled “Window chrome”Override opsi window (ukuran, frameless, always-on-top) tanpa menyentuh HTML:
Window: &updater.BuiltinWindow{ Options: updater.WindowOptions{ Title: "My App Updater", Width: 640, Height: 480, Frameless: true, AlwaysOnTop: true, DisableResize: false, },},Bring your own window
Section titled “Bring your own window”Kendalikan alur update terhadap *application.WebviewWindow yang Anda buat sendiri. Updater memanggil Show() / Close() / EmitEvent() pada window Anda — HTML Anda yang memutuskan apa yang di-render:

myWin := app.Window.NewWithOptions(application.WebviewWindowOptions{ Title: "My Updater", Width: 520, Height: 460, HTML: myCustomHTML, AllowSimpleEventEmit: true, // lihat catatan keamanan di bawah})app.Updater.Init(updater.Config{ // … Window: updater.BYOWindow(myWin.AsUpdaterWindow()),})HTML Anda memakai window.wails.Events.On / Events.Emit seperti template builtin — shim auto-inject framework masuk ke window apa pun dengan AllowSimpleEventEmit: true terlepas dari window milik framework atau milik Anda. Lihat Ganti template untuk API.
Headless
Section titled “Headless”app.Updater.Init(updater.Config{ // … Window: updater.WindowNone,})Tidak ada window yang pernah dibuka. Subscribe ke events updater:* dari UI Anda sendiri (atau main window yang sudah ada) dan panggil app.Updater.CheckAndInstall(ctx) dari button handler. Berguna untuk pemeriksaan background berkala yang hanya muncul saat sesuatu ditemukan, atau app yang mengintegrasikan alur update ke panel settings kustom.
Events
Section titled “Events”Go dan JavaScript subscribe melalui event bus Wails standar. Jangan ketik wire string manual — gunakan konstanta yang diekspor dari paket updater (Go) atau paket runtime (JS). Kedua lapisan berbagi set nama yang sama, disinkronkan oleh regression test.
Dari Go
Section titled “Dari Go”Konstanta ada di github.com/wailsapp/wails/v3/pkg/updater. Subscribe via app.Event.On(name, fn); callback menerima *application.CustomEvent yang field Data-nya adalah payload bertipe yang tercantum di referensi event — type-assert, jangan JSON-decode:
import ( "log"
"github.com/wailsapp/wails/v3/pkg/application" "github.com/wailsapp/wails/v3/pkg/updater")
// …
app.Event.On(updater.EventUpdateAvailable, func(e *application.CustomEvent) { rel, ok := e.Data.(*updater.Release) if !ok { return } log.Printf("update found: %s", rel.Version)})
app.Event.On(updater.EventDownloadProgress, func(e *application.CustomEvent) { p, ok := e.Data.(updater.Progress) if !ok { return } log.Printf("%d / %d bytes (%.0f KB/s)", p.Written, p.Total, p.Rate/1024)})
app.Event.On(updater.EventError, func(e *application.CustomEvent) { info, ok := e.Data.(updater.ErrorInfo) if !ok { return } log.Printf("update failed during %s: %s", info.Stage, info.Message)})Semua konstanta Go yang tersedia:
| Konstanta | Wire string |
|---|---|
updater.EventCheckStarted | wails:updater:check-started |
updater.EventUpdateAvailable | wails:updater:update-available |
updater.EventNoUpdate | wails:updater:no-update |
updater.EventDownloadStarted | wails:updater:download-started |
updater.EventDownloadProgress | wails:updater:download-progress |
updater.EventDownloadComplete | wails:updater:download-complete |
updater.EventVerifying | wails:updater:verifying |
updater.EventInstalling | wails:updater:installing |
updater.EventUpdateReady | wails:updater:update-ready |
updater.EventError | wails:updater:error |
updater.EventMeta | wails:updater:meta |
updater.EventWindowReady | wails:updater:window:ready |
updater.EventUserInstall | wails:updater:user:install |
updater.EventUserSkip | wails:updater:user:skip |
updater.EventUserRemind | wails:updater:user:remind |
updater.EventUserCancel | wails:updater:user:cancel |
updater.EventUserRestart | wails:updater:user:restart |
Dari JavaScript
Section titled “Dari JavaScript”Konstanta ada di bawah Updater.Events di @wailsio/runtime. Nama sama dengan Go, diorganisir per sub-namespace (User.*, Window.*) untuk discoverability autocomplete:
import { Events, Updater } from "@wailsio/runtime";
Events.On(Updater.Events.UpdateAvailable, (e) => { console.log("update found:", e.data.version);});
Events.On(Updater.Events.DownloadProgress, (e) => { const p = e.data; console.log(`${p.written} / ${p.total} bytes (${(p.rate/1024).toFixed(1)} KB/s)`);});
Events.On(Updater.Events.Error, (e) => { const info = e.data; console.error(`update failed during ${info.stage}: ${info.message}`);});Events aksi pengguna yang custom HTML Anda kirim kembali ke host ada di bawah Updater.Events.User:
import { Updater } from "@wailsio/runtime";
document.getElementById("install-btn").addEventListener("click", () => { // Window framework melakukan ini secara internal via postMessage shim; // ditampilkan di sini untuk template BYO yang perlu mengendalikan alur sendiri. window._wails.invoke("wails:event:emit:" + Updater.Events.User.Install);});Referensi event
Section titled “Referensi event”Sisi subscribe (host → page):
| Konstanta (Go) | Konstanta (JS) | Payload | Kapan |
|---|---|---|---|
updater.EventCheckStarted | Updater.Events.CheckStarted | none | Sebelum setiap round-trip Check |
updater.EventUpdateAvailable | Updater.Events.UpdateAvailable | *Release | Check menemukan release lebih baru |
updater.EventNoUpdate | Updater.Events.NoUpdate | none | Check mengonfirmasi up-to-date |
updater.EventDownloadStarted | Updater.Events.DownloadStarted | *Release | Byte mulai streaming |
updater.EventDownloadProgress | Updater.Events.DownloadProgress | Progress | ~10 Hz saat download |
updater.EventDownloadComplete | Updater.Events.DownloadComplete | *Release | Semua byte ditulis, sebelum verify |
updater.EventVerifying | Updater.Events.Verifying | *Release | Pemeriksaan signature/digest dimulai |
updater.EventInstalling | Updater.Events.Installing | *Release | Unpack + staging dimulai |
updater.EventUpdateReady | Updater.Events.UpdateReady | *Release | Restart menunggu |
updater.EventError | Updater.Events.Error | ErrorInfo | Tahap apa pun gagal |
updater.EventMeta | Updater.Events.Meta | Meta | Sekali per sesi sebelum snapshot replay |
Sisi page (page → host) — kode Anda subscribe jika menulis template kustom:
| Konstanta (Go) | Konstanta (JS) | Kapan |
|---|---|---|
updater.EventWindowReady | Updater.Events.Window.Ready | Window selesai loading; host replay state saat ini |
updater.EventUserInstall | Updater.Events.User.Install | Aksi primary di state available |
updater.EventUserRestart | Updater.Events.User.Restart | Aksi primary di state ready |
updater.EventUserSkip | Updater.Events.User.Skip | ”Skip This Version” |
updater.EventUserRemind | Updater.Events.User.Remind | ”Remind Me Later” |
updater.EventUserCancel | Updater.Events.User.Cancel | Tombol Close |
Referensi API
Section titled “Referensi API”updater.Config
Section titled “updater.Config”| Field | Tipe | Catatan |
|---|---|---|
CurrentVersion | string | Wajib. String yang sama dengan tag release (tanpa prefix v) |
Providers | []updater.Provider | Wajib. Fallback chain berurutan |
PublicKey | []byte | PEM atau raw bytes. Opsional tapi release bertanda tangan gagal closed tanpanya |
CheckInterval | time.Duration | Non-zero memulai loop poll background yang memanggil CheckAndInstall |
Platform | string | Override runtime.GOOS untuk pemilihan asset |
Arch | string | Override runtime.GOARCH untuk pemilihan asset |
Channel | string | Saat ini informatif; filter channel spesifik provider |
Window | updater.WindowOption | nil (default builtin), &BuiltinWindow{…}, BYOWindow(handle), atau WindowNone |
Method pada *updater.Updater
Section titled “Method pada *updater.Updater”| Signature | Tujuan |
|---|---|
Init(cfg Config) error | Konfigurasi. Mengembalikan ErrAlreadyConfigured pada panggilan kedua |
State() State | Fase lifecycle saat ini |
CurrentVersion() string | Versi yang diteruskan ke Init |
Check(ctx) (*Release, error) | Telusuri provider chain. (rel, nil) = ditemukan, (nil, nil) = up-to-date, (nil, err) = semua gagal |
DownloadAndInstall(ctx) error | Stream, verify, extract (jika archive), stage. Memerlukan Check sebelumnya |
CheckAndInstall(ctx) error | Convenience: buka window, Check, lalu DownloadAndInstall jika ditemukan |
Restart(ctx) error | Spawn helper, panggil Host.Quit, keluar; helper swap + relaunch |
DownloadedPath() string | Lokasi update staged on-disk, atau "" jika tidak ada |
SkipVersion(v string) | Catat v sebagai skipped; Check berikutnya memperlakukannya sebagai up-to-date |
SkippedVersion() string | Baca versi yang saat ini di-skip |
StopPeriodicCheck() | Batalkan timer yang dimulai Config.CheckInterval dan tunggu loop kembali |
Errors
Section titled “Errors”| Sentinel | Dikembalikan oleh |
|---|---|
ErrAlreadyConfigured | Init setelah sukses pertama |
ErrNotConfigured | Operasi apa pun sebelum Init |
ErrNoPendingRelease | DownloadAndInstall tanpa Check sebelumnya |
ErrDownloadInProgress | DownloadAndInstall dipanggil saat yang lain berjalan |
ErrNotReady | Restart tanpa update staged |
Cara swap bekerja
Section titled “Cara swap bekerja”Restart re-exec binary saat ini dengan environment variable sentinel diset. application.New mendeteksinya saat startup dan beralih ke helper-mode:
- Helper menunggu hingga 30 dtk parent PID exit (
platformIsAlivepoll viasyscall.OpenProcess+GetExitCodeProcessdi Windows,os.FindProcess+proc.Signal(syscall.Signal(0))di Unix). - Helper backup target (copy untuk file, recursive copy untuk direktori bundle
.appmacOS). - Helper mengganti target dengan artifact staged, retry hingga 20 kali dengan backoff 500 ms antar percobaan:
- Unix —
os.RemoveAll(target)+os.Rename(newPath, target). File descriptor terbuka terhadap inode lama tetap valid. - Windows —
os.Rename(target, target.old.<nanos>)+os.Rename(newPath, target). Windows mengizinkan rename file yang image-nya masih mapped, tapi tidak delete; pada update berikutnya helper sweep sibling.old.*yang mapping kernel-nya sudah dilepas.
- Unix —
- Helper memulihkan mode executable asli pada binary baru (file unduhan dibuat dengan umask default, yang menghilangkan
+xdi Unix; di Windows ini no-op). - Helper membersihkan env var helper-mode dan re-launch binary (yang sudah diganti).
- Helper exit.
Jika launch gagal, helper memulihkan backup. Jika parent tidak exit dalam 30 dtk, helper abort sebelum menyentuh target (pengguna tetap punya app yang berfungsi meski dialog shutdown memblokir Quit).
Untuk bundle .app macOS yang didistribusikan sebagai .zip (packaging yang direkomendasikan), archive di-unpack antara verify dan ready agar helper punya direktori nyata untuk di-swap.
Pemeriksaan berkala
Section titled “Pemeriksaan berkala”app.Updater.Init(updater.Config{ // … CheckInterval: 6 * time.Hour,})Saat CheckInterval > 0, goroutine background memanggil CheckAndInstall pada interval yang dikonfigurasi. Tick yang tiba saat alur lain sedang berjalan (checking / downloading / verifying / installing) di-drop — state machine konkuren tidak didukung.
Untuk polling background silent yang hanya muncul saat sesuatu ditemukan, set Window: updater.WindowNone dan bereaksi terhadap EventUpdateAvailable dari UI Anda sendiri.
Skip & remind
Section titled “Skip & remind”Tombol “Skip This Version” window default mencatat versi tersedia via SkipVersion(rel.Version). Check berikutnya menemukan versi yang sama dan memperlakukannya sebagai up-to-date (sampai pengguna memperbarui CurrentVersion, yang terjadi otomatis setelah Restart sukses). “Remind Me Later” hanya menutup window tanpa mencatat apa pun.
// Membaca apa yang di-skip pengguna (mis. untuk ditampilkan di app settings)if v := app.Updater.SkippedVersion(); v != "" { log.Printf("user skipped %s", v)}
// Menghapus skip secara programatik:app.Updater.SkipVersion("")Checklist distribusi
Section titled “Checklist distribusi”Sebelum mempublikasikan release yang akan di-install updater:
- Pilih format archive yang tepat. macOS:
.zipbundle.app. Linux: single binary atau.tar.gz. Windows: single.exeatau.zip..dmg/.msi/.pkgtidak didukung. - Sign artifact dengan private key yang cocok dengan
Config.PublicKey. Untuk feed yang dipublikasikan provider (keygen.sh, AppCast), ikuti workflow signing masing-masing provider. Untuk GitHub Releases denganChecksumAsset, generate fileSHA256SUMSdengansha256sum/shasum -a 256. - Cocokkan string versi.
Config.CurrentVersiondan tag versi release harus cocok persis (mis.1.0.0↔ tagv1.0.0; prefixvdi-strip di sisi provider). - Uji swap di platform target minimal sekali sebelum ship — codesigning, notarization, dan penanganan Gatekeeper spesifik platform dan tidak dicakup updater.
Troubleshooting
Section titled “Troubleshooting”“signature requires a public key but none configured” — release punya field Signature tapi Config.PublicKey kosong. Set public key atau ubah pipeline release agar tidak menyertakan signature.
“digest mismatch” — byte unduhan tidak cocok dengan yang dijanjikan provider. Biasanya unduhan partial (gangguan jaringan) atau artifact corrupt. Menjalankan ulang sering memperbaikinya.
Window terbuka lalu langsung hilang, tanpa markdown, tanpa progress — custom HTML tidak memanggil wails:runtime:ready. Lihat shim Ganti template.
Update Windows tidak pernah selesai; log helper “remove old (attempt N): Access is denied” — hanya terjadi pada versi pre-de764fb PR ini; implementasi saat ini memakai rename-aside yang tidak mengalami ini. Upgrade.
macOS Gatekeeper memblokir binary yang di-swap — code-signing harus dipertahankan end-to-end. Sign .app asli dan re-sign binary yang di-relaunch jika pipeline build Anda mengubah entitlements saat update.
Lihat juga
Section titled “Lihat juga”- Contoh yang dapat dijalankan:
v3/examples/updater - Repo demo test:
wailsapp/updater-demo - Tutorial: Menambahkan self-update ke aplikasi Wails