Lewati ke konten

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.

Window updater default dalam state Update Ready — ikon aware state, pill versi (v1.0.0 → v2.0.1 · 8.8 MB), release notes yang di-render Markdown termasuk tabel GFM, dan satu aksi primary.
main.go
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.

app.Updater adalah state machine dengan state berikut (updater.State):

StateKapan
unconfiguredSebelum Init dipanggil
idleSetelah Init, sebelum pemeriksaan apa pun
checkingCheck sedang berjalan
up-to-dateRespons provider terbaru mengatakan caller sudah current
availableRelease baru ditemukan; belum mengunduh
downloadingByte sedang streaming dari provider
verifyingDownload selesai, signature/digest sedang diperiksa
installingByte terverifikasi sedang di-unpack dan di-rename ke staging dir
readyUpdate di-stage; panggil Restart untuk menerapkan
errorLangkah 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:

Window updater default dalam state Up-to-Date — centang hijau, heading 'You're Up to Date', tombol Close tunggal.

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”
github provider
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 provider
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”
appcast provider
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).

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
},
})

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.

Release diautentikasi oleh verifier framework menggunakan Config.PublicKey sebagai trust root:

//go:embed publickey.pem
var publicKey []byte
app.Updater.Init(updater.Config{
CurrentVersion: "1.0.0",
Providers: []updater.Provider{...},
PublicKey: publicKey,
})

Algoritma yang didukung (Release.Verification.SignatureAlgo):

AlgoritmaYang ditandatanganiCatatan
ed25519Digest SHA-256 artifactDipakai Sparkle EdDSA
ed25519phArtifact penuh via pre-hash Ed25519ph (SHA-512 internal)Dipakai keygen.sh
ecdsa-p256Digest SHA-256 artifactSignature 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.

Terminal window
# Generate keypair Ed25519 baru untuk signing
ssh-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:embed

Atau 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.

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 .app macOS 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).

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.

app.Updater.Init(updater.Config{
// …
Window: &updater.BuiltinWindow{
CSS: `:root { --accent: #ff6f00; --bg: #1a1a1a; --fg: #fafafa; }`,
},
})

Stylesheet default mengekspos variabel ini — override mana pun:

VariabelDefault (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
--radius10px
--fontsystem stack

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:

  1. Tulis HTML saja. Framework auto-inject shim window.wails.Events minimal ke window apa pun yang dibuka dengan WebviewWindowOptions.AllowSimpleEventEmit = true dan HTML diset — persis yang dilakukan path builtin dan BYO updater. Tanpa build step. Ini jalur yang dipakai contoh di bawah.
  2. Bundle @wailsio/runtime dengan bundler pilihan Anda (Vite, esbuild, Rollup) dan import ke custom HTML saat build time. Events.On berfungsi out of the box karena purely client-side; Events.Emit melalui fetch transport runtime, yang null origin rusak — jadi pasang transport postMessage kecil via hook setTransport runtime yang route melalui window._wails.invoke("wails:event:emit:<name>"). Injection framework no-op jika window.wails.Events sudah 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.

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,
},
},

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:

Window updater 'Bring your own' dengan background gradient pink-orange, satu kartu putih rounded, tipografi kustom, dan events updater yang sama mengendalikan state yang terlihat. Menunjukkan seberapa lengkap UI default dapat diganti.
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.

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.

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.

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:

main.go
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:

KonstantaWire string
updater.EventCheckStartedwails:updater:check-started
updater.EventUpdateAvailablewails:updater:update-available
updater.EventNoUpdatewails:updater:no-update
updater.EventDownloadStartedwails:updater:download-started
updater.EventDownloadProgresswails:updater:download-progress
updater.EventDownloadCompletewails:updater:download-complete
updater.EventVerifyingwails:updater:verifying
updater.EventInstallingwails:updater:installing
updater.EventUpdateReadywails:updater:update-ready
updater.EventErrorwails:updater:error
updater.EventMetawails:updater:meta
updater.EventWindowReadywails:updater:window:ready
updater.EventUserInstallwails:updater:user:install
updater.EventUserSkipwails:updater:user:skip
updater.EventUserRemindwails:updater:user:remind
updater.EventUserCancelwails:updater:user:cancel
updater.EventUserRestartwails:updater:user:restart

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);
});

Sisi subscribe (host → page):

Konstanta (Go)Konstanta (JS)PayloadKapan
updater.EventCheckStartedUpdater.Events.CheckStartednoneSebelum setiap round-trip Check
updater.EventUpdateAvailableUpdater.Events.UpdateAvailable*ReleaseCheck menemukan release lebih baru
updater.EventNoUpdateUpdater.Events.NoUpdatenoneCheck mengonfirmasi up-to-date
updater.EventDownloadStartedUpdater.Events.DownloadStarted*ReleaseByte mulai streaming
updater.EventDownloadProgressUpdater.Events.DownloadProgressProgress~10 Hz saat download
updater.EventDownloadCompleteUpdater.Events.DownloadComplete*ReleaseSemua byte ditulis, sebelum verify
updater.EventVerifyingUpdater.Events.Verifying*ReleasePemeriksaan signature/digest dimulai
updater.EventInstallingUpdater.Events.Installing*ReleaseUnpack + staging dimulai
updater.EventUpdateReadyUpdater.Events.UpdateReady*ReleaseRestart menunggu
updater.EventErrorUpdater.Events.ErrorErrorInfoTahap apa pun gagal
updater.EventMetaUpdater.Events.MetaMetaSekali per sesi sebelum snapshot replay

Sisi page (page → host) — kode Anda subscribe jika menulis template kustom:

Konstanta (Go)Konstanta (JS)Kapan
updater.EventWindowReadyUpdater.Events.Window.ReadyWindow selesai loading; host replay state saat ini
updater.EventUserInstallUpdater.Events.User.InstallAksi primary di state available
updater.EventUserRestartUpdater.Events.User.RestartAksi primary di state ready
updater.EventUserSkipUpdater.Events.User.Skip”Skip This Version”
updater.EventUserRemindUpdater.Events.User.Remind”Remind Me Later”
updater.EventUserCancelUpdater.Events.User.CancelTombol Close
FieldTipeCatatan
CurrentVersionstringWajib. String yang sama dengan tag release (tanpa prefix v)
Providers[]updater.ProviderWajib. Fallback chain berurutan
PublicKey[]bytePEM atau raw bytes. Opsional tapi release bertanda tangan gagal closed tanpanya
CheckIntervaltime.DurationNon-zero memulai loop poll background yang memanggil CheckAndInstall
PlatformstringOverride runtime.GOOS untuk pemilihan asset
ArchstringOverride runtime.GOARCH untuk pemilihan asset
ChannelstringSaat ini informatif; filter channel spesifik provider
Windowupdater.WindowOptionnil (default builtin), &BuiltinWindow{…}, BYOWindow(handle), atau WindowNone
SignatureTujuan
Init(cfg Config) errorKonfigurasi. Mengembalikan ErrAlreadyConfigured pada panggilan kedua
State() StateFase lifecycle saat ini
CurrentVersion() stringVersi 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) errorStream, verify, extract (jika archive), stage. Memerlukan Check sebelumnya
CheckAndInstall(ctx) errorConvenience: buka window, Check, lalu DownloadAndInstall jika ditemukan
Restart(ctx) errorSpawn helper, panggil Host.Quit, keluar; helper swap + relaunch
DownloadedPath() stringLokasi update staged on-disk, atau "" jika tidak ada
SkipVersion(v string)Catat v sebagai skipped; Check berikutnya memperlakukannya sebagai up-to-date
SkippedVersion() stringBaca versi yang saat ini di-skip
StopPeriodicCheck()Batalkan timer yang dimulai Config.CheckInterval dan tunggu loop kembali
SentinelDikembalikan oleh
ErrAlreadyConfiguredInit setelah sukses pertama
ErrNotConfiguredOperasi apa pun sebelum Init
ErrNoPendingReleaseDownloadAndInstall tanpa Check sebelumnya
ErrDownloadInProgressDownloadAndInstall dipanggil saat yang lain berjalan
ErrNotReadyRestart tanpa update staged

Restart re-exec binary saat ini dengan environment variable sentinel diset. application.New mendeteksinya saat startup dan beralih ke helper-mode:

  1. Helper menunggu hingga 30 dtk parent PID exit (platformIsAlive poll via syscall.OpenProcess + GetExitCodeProcess di Windows, os.FindProcess + proc.Signal(syscall.Signal(0)) di Unix).
  2. Helper backup target (copy untuk file, recursive copy untuk direktori bundle .app macOS).
  3. Helper mengganti target dengan artifact staged, retry hingga 20 kali dengan backoff 500 ms antar percobaan:
    • Unixos.RemoveAll(target) + os.Rename(newPath, target). File descriptor terbuka terhadap inode lama tetap valid.
    • Windowsos.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.
  4. Helper memulihkan mode executable asli pada binary baru (file unduhan dibuat dengan umask default, yang menghilangkan +x di Unix; di Windows ini no-op).
  5. Helper membersihkan env var helper-mode dan re-launch binary (yang sudah diganti).
  6. 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.

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.

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("")

Sebelum mempublikasikan release yang akan di-install updater:

  1. Pilih format archive yang tepat. macOS: .zip bundle .app. Linux: single binary atau .tar.gz. Windows: single .exe atau .zip. .dmg / .msi / .pkg tidak didukung.
  2. 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 dengan ChecksumAsset, generate file SHA256SUMS dengan sha256sum / shasum -a 256.
  3. Cocokkan string versi. Config.CurrentVersion dan tag versi release harus cocok persis (mis. 1.0.0 ↔ tag v1.0.0; prefix v di-strip di sisi provider).
  4. Uji swap di platform target minimal sekali sebelum ship — codesigning, notarization, dan penanganan Gatekeeper spesifik platform dan tidak dicakup updater.

“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.