Files
tailscale-custom/docs/VELOPACK.md
T

12 KiB

Đóng gói & Auto-Update với Velopack

Tài liệu này mô tả cách build, phát hành và cập nhật tự động cho Tailscale-Custom trên Windows bằng Velopack.

Phạm vi: chỉ phần tray app (tailscale-tray.exe) — đây là main exe Velopack quản lý. Service tailscaled.exe chạy ở C:\Program Files (x86)\Tailscale-Custom và được tray copy/đăng ký lại qua một worker UAC (--ts-apply-service-update).


1. Tổng quan kiến trúc

┌─────────────────────────────────────────────────────────────┐
│ %LocalAppData%\TailscaleCustom\        (per-user, Velopack) │
│   ├── Update.exe                       (Velopack updater)   │
│   ├── tailscale-tray.exe               (launcher stub)      │
│   ├── packages\                        (downloaded nupkgs)  │
│   └── current\                         (active version)     │
│        ├── sq.version                                       │
│        ├── tailscale-tray.exe          (main app)           │
│        ├── tailscaled.exe                                   │
│        ├── tailscale.exe                                    │
│        └── wintun.dll                                       │
└─────────────────────────────────────────────────────────────┘
                       │
                       │ on update -> elevated worker
                       ▼
┌─────────────────────────────────────────────────────────────┐
│ C:\Program Files (x86)\Tailscale-Custom\  (machine-wide)    │
│   ├── tailscaled.exe   (Windows service: Tailscale-Custom)  │
│   ├── tailscale.exe                                         │
│   └── wintun.dll                                            │
└─────────────────────────────────────────────────────────────┘

Vì sao tách 2 thư mục?

  • Velopack swap thư mục current\ không cần admin → cập nhật tray mượt.
  • Service phải chạy LocalSystem từ thư mục không ghi được bởi user để tránh privilege escalation.

2. Layout package (nuspec)

Metadata trong lib/app/sq.version / TailscaleCustom.nuspec:

<package>
  <metadata>
    <id>TailscaleCustom</id>
    <title>Tailscale Custom</title>
    <authors>SoftsBusiness</authors>
    <version>1.0.2</version>
    <channel>win</channel>
    <mainExe>tailscale-tray.exe</mainExe>
    <os>win</os>
    <rid>win-x64</rid>
    <shortcutLocations>Desktop,StartMenuRoot</shortcutLocations>
    <shortcutAumid>velopack.TailscaleCustom</shortcutAumid>
    <machineArchitecture>x64</machineArchitecture>
  </metadata>
</package>

Các trường bắt buộc giữ ổn định giữa các bản: id, channel, mainExe, rid. Đổi version mỗi lần release.


3. Quy trình build

3.1. Yêu cầu

  • Windows 10/11 x64
  • Go toolchain (repo đã có sẵn ở tool\go.exe, Go 1.26.x)
  • .NET 8 SDK + dotnet tool install -g vpk (Velopack CLI)
  • Code signing cert (tùy chọn) — repo dùng cert self-signed CN=Tailscale-Custom, O=SoftsBusiness, C=VN

3.2. Build các binary

Tất cả binary đầu ra để ở dist/:

$env:GOOS='windows'; $env:GOARCH='amd64'; $env:CGO_ENABLED='0'
.\tool\go.exe build -ldflags "-H=windowsgui -s -w" `
  -o .\dist\tailscale-tray.exe ./cmd/tailscale-tray
.\tool\go.exe build -ldflags "-s -w" -o .\dist\tailscaled.exe ./cmd/tailscaled
.\tool\go.exe build -ldflags "-s -w" -o .\dist\tailscale.exe  ./cmd/tailscale
# wintun.dll: copy bản x64 chính thức vào dist\

Lưu ý -H=windowsgui cho tray để không bật console window.

3.3. Pack bằng vpk

$ver  = '1.0.3'   # bump mỗi lần release
$pkg  = 'TailscaleCustom'
$src  = '.\dist'
$out  = '.\velopack-releases'

vpk pack `
  --packId       $pkg `
  --packVersion  $ver `
  --packDir      $src `
  --mainExe      tailscale-tray.exe `
  --packAuthors  SoftsBusiness `
  --packTitle    'Tailscale Custom' `
  --channel      win `
  --shortcuts    'Desktop,StartMenuRoot' `
  --outputDir    $out

Đầu ra trong velopack-releases\:

File Mô tả
TailscaleCustom-<ver>-full.nupkg Full package (dùng để cập nhật)
TailscaleCustom-win-Setup.exe Installer cho user mới
TailscaleCustom-win-Portable.zip Bản portable (không cần cài)
releases.win.json Feed metadata (Update.exe dùng)
RELEASES Manifest legacy (Squirrel format)
assets.win.json Liệt kê asset của channel

3.4. Ký số (tùy chọn nhưng khuyến nghị)

$cert = Get-ChildItem Cert:\LocalMachine\My |
  Where-Object { $_.Subject -match 'Tailscale-Custom' -and $_.HasPrivateKey } |
  Sort-Object NotAfter -Descending | Select-Object -First 1

Set-AuthenticodeSignature -FilePath .\dist\tailscale-tray.exe `
  -Certificate $cert -TimestampServer 'http://timestamp.digicert.com' `
  -HashAlgorithm SHA256

Ký TRƯỚC khi vpk pack để chữ ký nằm trong nupkg.


4. Phát hành (release feed)

Tray sẽ poll feed tại URL cấu hình trong cmd/tailscale-tray/update.go:

const (
    updateFeedURL = "https://vpn.softs.business/updates/"
    updateChannel = "win"
)

4.1. Cấu trúc feed trên server

https://vpn.softs.business/updates/
  ├── releases.win.json
  ├── RELEASES
  ├── TailscaleCustom-1.0.2-full.nupkg
  ├── TailscaleCustom-1.0.3-full.nupkg
  └── ...

Upload bằng SCP / SFTP / S3 sync — ví dụ:

scp .\velopack-releases\* user@vpn.softs.business:/var/www/updates/

4.2. Reverse proxy

Bất kỳ HTTP server tĩnh nào cũng được. Yêu cầu:

  • HTTPS (tray dùng http.Client mặc định)
  • Content-Type application/json cho releases.win.json
  • application/octet-stream cho .nupkg
  • KHÔNG cache releases.win.json (hoặc TTL ngắn) để client thấy bản mới ngay

5. Luồng auto-update (client side)

Logic đầy đủ ở cmd/tailscale-tray/update.go.

sequenceDiagram
  autonumber
  participant Tray as tailscale-tray
  participant Feed as Update server
  participant Upd as Update.exe
  participant UAC as Elevated worker
  participant Svc as Tailscale-Custom service

  Tray->>Tray: startAutoUpdater (delay 90s, loop 6h)
  Tray->>Feed: GET releases.win.json
  Feed-->>Tray: latest version + filename + SHA256
  alt latest > installed
    Tray->>Feed: GET *-full.nupkg
    Tray->>Upd: Update.exe apply --silent --package ...
    Tray-->>Tray: os.Exit(0) (Update.exe waits then swaps current\)
    Upd->>Tray: relaunch new tailscale-tray.exe
    Tray->>Tray: --veloapp-updated hook
    Tray->>UAC: ShellExecuteEx runas --ts-apply-service-update
    UAC->>Svc: sc stop
    UAC->>UAC: copy service bins to Program Files (x86)
    UAC->>Svc: sc start
  else up to date
    Tray->>Tray: log "up to date"
  end

5.1. Lifecycle hooks Velopack invoke

Tray xử lý trong handleStartupArgs():

Arg Khi nào Hành động
--veloapp-install Lần đầu cài syncServiceBinaries(allowElevate=true)
--veloapp-updated Sau update syncServiceBinaries(allowElevate=true)
--veloapp-obsolete Bản cũ bị retire no-op, exit
--veloapp-uninstall Gỡ cài stop + xoá service
--ts-apply-service-update Worker elevated nội bộ copy bins + (re)register service

5.2. So sánh hash trước khi swap service

serviceBinsUpToDate() so SHA-256 của %LocalAppData%\TailscaleCustom\current\<bin> với C:\Program Files (x86)\Tailscale-Custom\<bin>. Nếu trùng hết → bỏ qua UAC prompt.

5.3. Phiên bản hiện tại

Đọc từ current\sq.version:

<version>1.0.2</version>

So sánh dạng dotted-numeric (compareVersions) — không hỗ trợ pre-release tag.


6. Cài đặt lần đầu

6.1. Bằng Setup.exe (khuyến nghị)

.\TailscaleCustom-win-Setup.exe --silent

Kết quả:

  • App cài vào %LocalAppData%\TailscaleCustom\
  • Shortcut tạo ở Desktop + Start Menu (theo shortcutLocations)
  • Hook --veloapp-install chạy → elevate → tạo service Windows

6.2. Portable

Bung TailscaleCustom-win-Portable.zip ra bất kỳ thư mục nào, chạy Tailscale Custom.exe. Service sẽ KHÔNG được cài tự động.


7. Phát hành bản mới — checklist

  1. Bump version (Go) + ghi changelog
  2. Build binary (tool\go.exe build)
  3. Ký số dist\*.exe
  4. vpk pack --packVersion <new>
  5. (Tùy chọn) test local bằng:
    $upd = "$env:LOCALAPPDATA\TailscaleCustom\Update.exe"
    & $upd apply --silent --package .\velopack-releases\TailscaleCustom-<new>-full.nupkg
    
  6. Upload velopack-releases\* lên feed URL
  7. Tray các máy sẽ tự update trong vòng updateInitialDelay + updateCheckInterval (90s + 6h)

8. Hành vi tray (close vs quit)

  • Đóng menu / Esc → tray vẫn chạy ở khay hệ thống.
  • Right-click → Quit → dialog xác nhận, default = No.
  • Chỉ chọn Yes mới thực sự exit; service vẫn chạy nền và giữ login.

Auto-start sau login: shortcut ở %AppData%\Microsoft\Windows\Start Menu\Programs\Startup\.


9. Troubleshooting

Triệu chứng Nguyên nhân thường gặp Cách xử lý
Tray không thấy update releases.win.json bị cache CDN Tắt cache hoặc PURGE URL
Update apply xong nhưng version cũ SHA256 trong feed lệch nupkg Re-pack & re-upload
UAC prompt mỗi lần khởi động Service bins không match bundle Chạy thủ công --ts-apply-service-update rồi kiểm tra hash
Update.exe apply exit 0 nhưng không đổi Cùng version, swap bị skip Tăng <version> rồi pack lại
Tray mất icon sau update Crash sớm trước systray.Run Xem %ProgramData%\Tailscale-Custom\Logs\tray.log

9.1. Vị trí log

  • Tray: %ProgramData%\Tailscale-Custom\Logs\tray.log (fallback %LocalAppData%\Tailscale-Custom\Logs\)
  • Velopack: %LocalAppData%\TailscaleCustom\SquirrelClowdTemp\*.log

9.2. Force re-check ngay

Stop-Process -Name tailscale-tray -Force
Start-Process "$env:LOCALAPPDATA\TailscaleCustom\tailscale-tray.exe"
# tray sẽ check feed sau ~90s

9.3. Rollback

Velopack giữ bản cũ trong packages\. Apply package cũ:

$upd = "$env:LOCALAPPDATA\TailscaleCustom\Update.exe"
& $upd apply --silent --package "$env:LOCALAPPDATA\TailscaleCustom\packages\TailscaleCustom-1.0.1-full.nupkg"

10. Bảo mật

  • Luôn HTTPS cho feed (tránh MITM nhét nupkg độc).
  • Ký số mọi .exe trước khi pack.
  • Velopack tự verify SHA256 của nupkg khớp releases.win.json trước khi apply.
  • Tray KHÔNG nâng quyền nếu hash service bins đã trùng → giảm UAC prompt thừa.
  • Service chạy ở Program Files (x86)\Tailscale-Custom, ACL mặc định (SYSTEM + Administrators full control, Users read-only) là đủ an toàn.

11. Tham khảo file trong repo

File Vai trò
cmd/tailscale-tray/update.go Auto-update + lifecycle hooks
cmd/tailscale-tray/main.go Entry point + Quit confirm
velopack-releases/ Output vpk pack
sign-release.ps1 Tạo/ký bằng self-signed cert
Setup.wxs MSI cũ (không dùng với Velopack)