Files
tailscale-custom/docs/VELOPACK_KNOWLEDGE.md
T

15 KiB
Raw Blame History

Velopack — Knowledge Base

Tài liệu kiến thức tổng quát về Velopack — framework đóng gói & auto-update cross-platform (Windows / macOS / Linux), kế thừa từ Squirrel. Dùng làm tham chiếu áp dụng cho mọi project (không gắn với codebase nào cụ thể).


1. Velopack là gì?

  • Installer + Auto-updater all-in-one cho desktop app.
  • Hỗ trợ: .NET, native (C/C++/Rust), Go, Python (PyInstaller), Electron, JS… bất cứ app nào ra được file thực thi.
  • Output: Setup.exe, Portable.zip, .nupkg (delta/full), feed JSON.
  • Cập nhật không cần admin trên Windows (per-user, vào %LocalAppData%).
  • CLI: vpk (cài qua dotnet tool install -g vpk).

So với các giải pháp khác

Velopack Squirrel.Windows MSIX electron-updater Inno/NSIS
Cross-platform Win/Mac/Linux chỉ Win chỉ Win (Electron)
Per-user, no admin ⚠️
Delta update
Code signing tích hợp ⚠️ ⚠️
Bundle runtime (.NET) ⚠️ N/A ⚠️

2. Khái niệm cốt lõi

Khái niệm Giải thích
Package Một .nupkg chứa toàn bộ app cho một version
Channel Tag phân nhánh (vd win, win-beta, osx); mỗi channel có feed riêng
Full / Delta Full = toàn bộ app; Delta = diff giữa 2 version (nhỏ hơn nhiều)
Feed URL HTTP(S) chứa releases.<channel>.json + các .nupkg
Update.exe Stub binary do Velopack sinh ra, xử lý swap version
Main exe Binary chính được launch sau cài/khởi động (mainExe trong metadata)
Lifecycle hooks App được gọi với args --veloapp-* ở các thời điểm install/update/uninstall

3. Layout sau cài đặt (Windows)

%LocalAppData%\<AppId>\
  ├── Update.exe                 ← Velopack updater (giữ nguyên giữa các version)
  ├── <MainExe>.exe              ← launcher stub (chạy file trong current\)
  ├── packages\                  ← các .nupkg đã tải (full + delta + cũ để rollback)
  │     ├── <AppId>-1.0.0-full.nupkg
  │     └── <AppId>-1.0.1-full.nupkg
  └── current\                   ← version đang active (Update.exe swap thư mục này)
        ├── sq.version           ← metadata version hiện tại
        └── <toàn bộ file app>

Quy tắc vàng:

  • Code app không bao giờ ghi vào current\ (sẽ mất khi update).
  • Lưu state ở %AppData%\<AppName>\ hoặc %LocalAppData%\<AppName>\Data\.
  • Đường dẫn binary là động — luôn dùng os.Executable() / Process.MainModule.

4. Cài đặt CLI

dotnet tool install -g vpk
vpk --version

Yêu cầu .NET 8 SDK trở lên. Trên CI dùng dotnet tool install -g vpk --version <pin> để build reproducible.


5. Lệnh vpk pack — tham số quan trọng

vpk pack `
  --packId        MyApp `
  --packVersion   1.2.3 `
  --packDir       .\publish `
  --mainExe       MyApp.exe `
  --packTitle     "My App" `
  --packAuthors   "Acme Inc" `
  --channel       win `
  --shortcuts     "Desktop,StartMenuRoot" `
  --icon          .\assets\icon.ico `
  --signParams    "/fd SHA256 /a /tr http://timestamp.digicert.com /td SHA256" `
  --outputDir     .\releases
Flag Mô tả
--packId ID duy nhất (PascalCase, không space). Không bao giờ đổi
--packVersion SemVer 3 phần, monotonic tăng
--packDir Thư mục chứa output build (sẽ pack toàn bộ)
--mainExe Tên file exe chính trong --packDir
--channel Phân nhánh release (default win/osx/linux)
--shortcuts Desktop, StartMenuRoot, StartMenu, Startup, None
--icon Icon cho Setup.exe + shortcut
--signParams Tham số truyền vào signtool sign
--signTemplate Lệnh ký custom (vd dùng azuresigntool)
--delta BestSpeed / BestSize / None
--noPortable Bỏ tạo Portable.zip
--noInst Bỏ tạo Setup.exe
--releaseNotes File markdown cho changelog

Output --outputDir

releases\
  ├── MyApp-1.2.3-full.nupkg
  ├── MyApp-1.2.3-delta.nupkg          (nếu có bản trước)
  ├── MyApp-win-Setup.exe
  ├── MyApp-win-Portable.zip
  ├── releases.win.json                 ← feed
  ├── RELEASES                          ← Squirrel legacy manifest
  └── assets.win.json

6. Delta update

Velopack tự tạo delta nếu output dir đã có bản trước.

Best practice:

  1. Trước khi pack version N, sync <outputDir> từ server (tải về full + delta cũ).
  2. Pack version N → sinh N-full.nupkg + N-delta.nupkg so với N-1.
  3. Push toàn bộ <outputDir> lên server.
# 1. download existing releases
vpk download github --repoUrl https://github.com/acme/myapp --outputDir .\releases
# hoặc rsync/scp/aws s3 sync

# 2. pack
vpk pack --packId MyApp --packVersion 1.2.3 ...

# 3. upload
vpk upload github --repoUrl https://github.com/acme/myapp --outputDir .\releases
# hoặc tự upload bằng scp/aws/azcopy

Backend vpk upload có sẵn: github, s3, azure, gitea, http.


7. Feed server

7.1. Cấu trúc URL

https://updates.acme.com/myapp/
  ├── releases.win.json          (Content-Type: application/json)
  ├── RELEASES
  ├── MyApp-1.2.2-full.nupkg     (Content-Type: application/octet-stream)
  ├── MyApp-1.2.3-full.nupkg
  └── MyApp-1.2.3-delta.nupkg

7.2. Headers khuyến nghị

Header Giá trị
Cache-Control (cho releases.*.json) no-cache, max-age=60
Cache-Control (cho .nupkg) public, max-age=31536000, immutable
Content-Type (json) application/json
Content-Type (nupkg) application/octet-stream

Nupkg là immutable (hash trong tên file → đổi nội dung = đổi tên).

7.3. CDN

OK dùng CloudFront / Cloudflare / Bunny. Bắt buộc: purge releases.*.json sau mỗi release. Nếu không, client sẽ thấy version cũ hàng giờ.


8. Tích hợp auto-update vào app

Có 2 lựa chọn:

8.1. Velopack SDK (.NET / Rust / JS)

// .NET
VelopackApp.Build().Run();   // gọi đầu Main(), xử lý hooks
var mgr = new UpdateManager("https://updates.acme.com/myapp/");
var info = await mgr.CheckForUpdatesAsync();
if (info != null) {
    await mgr.DownloadUpdatesAsync(info);
    mgr.ApplyUpdatesAndRestart(info);
}
// Rust crate: velopack
let app = VelopackApp::build().run();
let um = UpdateManager::new("https://updates.acme.com/myapp/", None, None)?;
if let UpdateCheck::UpdateAvailable(u) = um.check_for_updates()? {
    um.download_updates(&u, None)?;
    um.apply_updates_and_restart(Some(&u))?;
}

8.2. Tự gọi Update.exe (cho Go, Python, C++…)

App tự fetch feed, tải nupkg, rồi exec Update.exe:

// 1. Đọc version đang chạy
version := readSqVersion(filepath.Join(exeDir(), "sq.version"))

// 2. GET https://updates.acme.com/myapp/releases.win.json
//    so sánh với version

// 3. Tải nupkg về %LocalAppData%\MyApp\packages\

// 4. Gọi updater
exec.Command(
    filepath.Join(rootDir(), "Update.exe"),
    "apply", "--silent",
    "--waitPid", strconv.Itoa(os.Getpid()),
    "--package", pkgPath,
).Start()

os.Exit(0)   // Update.exe sẽ swap rồi restart main exe

Update.exe apply flags hữu dụng:

Flag Ý nghĩa
--silent Không hiện UI tiến trình
--package <path> Dùng nupkg cụ thể (skip download từ feed)
--waitPid <pid> Chờ pid này thoát mới swap
--restart (default on) Restart main exe sau khi apply
--norestart Không restart

9. Lifecycle hooks

Velopack invoke main exe với args đặc biệt. App phải xử lý sớm trong main()exit(0) ngay sau khi xong (đừng vẽ UI cho hook).

Arg Khi nào Mục đích thường dùng
--veloapp-install <ver> Lần đầu cài Migrate cấu hình, đăng ký service
--veloapp-updated <ver> Sau mỗi update Migrate schema DB, copy file out-of-bundle
--veloapp-obsolete <ver> Version vừa bị thay Cleanup tạm (hiếm dùng)
--veloapp-uninstall <ver> Trước khi gỡ Unregister service, xoá protocol handler

Mẫu Go:

func handleStartupArgs() (exit bool) {
    if len(os.Args) < 2 { return false }
    switch os.Args[1] {
    case "--veloapp-install", "--veloapp-updated":
        runPostInstallSteps()
        return true
    case "--veloapp-uninstall":
        runCleanupSteps()
        return true
    case "--veloapp-obsolete":
        return true
    }
    return false
}

func main() {
    if handleStartupArgs() { return }
    // ... app bình thường ...
}

Quan trọng: hooks chạy SONG SONG với instance app khác có thể đang chạy. Đặt trước single-instance mutex.


10. Code signing

10.1. Windows (Authenticode)

vpk pack ... `
  --signParams "/sha1 <THUMBPRINT> /fd SHA256 /tr http://timestamp.digicert.com /td SHA256"

Hoặc dùng template để gọi azuresigntool (Azure Key Vault):

vpk pack ... `
  --signTemplate "azuresigntool sign -kvu https://kv.vault.azure.net -kvi $env:AZ_ID -kvs $env:AZ_SECRET -kvc cert-name -tr http://timestamp.digicert.com -td sha256 {{file}}"

{{file}} được Velopack thay bằng đường dẫn từng file cần ký.

10.2. macOS (codesign + notarization)

vpk pack \
  --packId MyApp --packVersion 1.2.3 --packDir ./publish --mainExe MyApp \
  --icon icon.icns \
  --signAppIdentity "Developer ID Application: Acme (TEAMID)" \
  --notaryProfile "AC_PASSWORD"   # đã lưu bằng `xcrun notarytool store-credentials`

10.3. Linux (AppImage)

vpk pack --packId myapp --mainExe myapp --packDir ./out --channel linux
# output: myapp-1.2.3.AppImage + nupkg + releases.linux.json

11. Bundle runtime / prerequisites

vpk pack ... --runtimes "net8,vcredist140-x64"

Velopack tải runtime installer tương ứng nhúng vào Setup.exe, prompt user cài nếu thiếu. Danh sách: vpk runtime list.


12. Versioning rules

  • SemVer 3 phần: MAJOR.MINOR.PATCH. Không dùng pre-release tag (-beta1) trừ khi tách channel riêng — Velopack so sánh dạng numeric.
  • Mỗi release version phải lớn hơn version trước trên cùng channel.
  • Downgrade không được hỗ trợ trực tiếp; muốn rollback phải:
    1. Bump version mới > latest chứa code cũ, hoặc
    2. Apply thủ công nupkg cũ bằng Update.exe apply --package <old.nupkg>.

13. CI/CD pattern (GitHub Actions)

name: release
on:
  push:
    tags: ['v*']

jobs:
  build:
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with: { dotnet-version: '8.x' }
      - run: dotnet tool install -g vpk
      - name: Build app
        run: dotnet publish src/MyApp -c Release -r win-x64 --self-contained -o publish
      - name: Download previous releases (for delta)
        run: vpk download github --repoUrl ${{ github.server_url }}/${{ github.repository }} --outputDir releases
        continue-on-error: true
      - name: Pack
        run: |
          $ver = "${{ github.ref_name }}".TrimStart('v')
          vpk pack --packId MyApp --packVersion $ver --packDir publish `
                   --mainExe MyApp.exe --channel win --outputDir releases
      - name: Upload
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: vpk upload github --repoUrl ${{ github.server_url }}/${{ github.repository }} --outputDir releases --publish --releaseName "v$ver" --tag "v$ver"

14. Best practices

14.1. App design

  • Đặt VelopackApp.Build().Run() (hoặc handleStartupArgs()) dòng đầu của main.
  • Tách state khỏi current\ (sẽ bị wipe khi update).
  • Single-instance check sau khi xử lý hooks (hooks là one-shot, không xung đột).
  • Auto-update check: delay 3090s sau khởi động (tránh slow start), repeat 412h.

14.2. Operations

  • Luôn HTTPS cho feed.
  • Ký số tất cả .exe trước khi pack.
  • Theo dõi telemetry update success rate (tỉ lệ apply / check).
  • Giữ ≥ 3 bản gần nhất trên feed để delta có "anchor".
  • Bump version ngay cả khi chỉ fix metadata — không bao giờ re-upload nupkg cùng tên với nội dung khác.

14.3. Bảo mật

  • Velopack verify SHA256 của nupkg trước khi apply (so với feed JSON).
  • Nếu app cần admin (vd service Windows), elevate chỉ phần đó qua một worker riêng (ShellExecuteEx "runas"), đừng chạy cả app dưới UAC.
  • Không bao giờ download nupkg từ HTTP plaintext; tránh mixed-content nếu chạy trong WebView.

15. Troubleshooting

Triệu chứng Nguyên nhân Cách xử lý
Client không thấy update Cache releases.<channel>.json Purge CDN / set no-cache
Update.exe apply exit 0, không đổi version Cùng version hoặc nupkg hỏng Bump version, pack lại, verify SHA256
"Hash mismatch" Nupkg trên server khác hash trong feed Re-upload đồng bộ cả 2
App không restart sau update Main exe crash sớm Bọc panic recovery, xem log Velopack
Setup.exe đòi admin Không phải Velopack issue — do AV / SmartScreen Ký số EV, build reputation
Delta apply fail rồi tự fallback full Bình thường, log thôi Không cần fix

Log paths (Windows)

  • App: tự define (vd %ProgramData%\<App>\Logs\)
  • Velopack: %LocalAppData%\<AppId>\SquirrelClowdTemp\*.log
  • Setup: %TEMP%\Velopack.*.log

Force update ngay (debug)

& "$env:LOCALAPPDATA\<AppId>\Update.exe" apply --silent `
  --package "$env:LOCALAPPDATA\<AppId>\packages\<AppId>-x.y.z-full.nupkg"

Rollback

& "$env:LOCALAPPDATA\<AppId>\Update.exe" apply --silent `
  --package "$env:LOCALAPPDATA\<AppId>\packages\<AppId>-<old>-full.nupkg"

16. Checklist release nhanh

  1. ☐ Bump version (MAJOR.MINOR.PATCH) trong source + CI tag
  2. ☐ Build release binary, output vào publish\
  3. ☐ Ký số các .exe (hoặc dùng --signParams)
  4. vpk download bản trước về releases\
  5. vpk pack --packVersion <new>
  6. ☐ Smoke test local: Update.exe apply --package <new>-full.nupkg
  7. ☐ Upload releases\* lên feed
  8. ☐ Purge cache releases.<channel>.json
  9. ☐ Verify từ máy khác: tray/app tự update sau interval

17. Tham khảo