You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Txgy.EWS.Client/docs/VELOPACK_SUPABASE_AUTO_UPDA...

453 lines
12 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Velopack + Supabase Storage 自动更新复用指南
本文档用于在桌面程序中复用“Velopack 生成安装包和更新包Supabase Storage 托管更新源”的自动更新流程。示例均使用占位符,不绑定任何具体项目。
## 方案概览
更新链路分为两部分:
1. 发布端使用 Velopack CLI 将程序发布目录打包成安装包、全量包、差分包和 `releases.win.json`
2. 上传端将这些产物放到 Supabase Storage 的公开桶目录中。
3. 客户端运行时只读取公开更新源 URL调用 Velopack 检查、下载并应用更新。
客户端不需要 Supabase key。Supabase key 只应出现在发布脚本、CI/CD 或受信任的维护机器中。
## Supabase 配置
### 1. 创建 Storage 桶
推荐使用一个专门存放发布包的公开 File bucket
| 配置项 | 推荐值 | 说明 |
| --- | --- | --- |
| Bucket name | `app-releases` | 可按组织统一命名 |
| Public bucket | `true` | 客户端需要匿名下载 `releases.win.json` 和安装包 |
| File size limit | 按安装包大小设置 | 例如 `512MB`、`1GB` 或更高 |
| Allowed MIME types | 留空或允许二进制 | Velopack 产物包含 `.exe`、`.nupkg`、`.json` 等 |
Dashboard 路径通常是:
```text
Supabase Dashboard -> Storage -> New bucket
```
也可以用 SQL 创建桶:
```sql
insert into storage.buckets (
id,
name,
public,
file_size_limit,
allowed_mime_types
)
values (
'app-releases',
'app-releases',
true,
1073741824,
null
)
on conflict (id) do update
set
public = excluded.public,
file_size_limit = excluded.file_size_limit,
allowed_mime_types = excluded.allowed_mime_types;
```
### 2. 目录规划
建议每个应用、平台和发布通道使用独立目录:
```text
app-releases/
my-app/
windows/
stable/
releases.win.json
RELEASES
MyApp-win-Setup.exe
MyApp-1.2.3-full.nupkg
MyApp-1.2.3-delta.nupkg
beta/
releases.win.json
...
```
对应的 Velopack 更新源根地址为:
```text
https://<project-ref>.supabase.co/storage/v1/object/public/app-releases/my-app/windows/stable
```
Velopack 会在该根地址下读取 `releases.win.json`,所以不要把 `releases.win.json` 再放到额外子目录里。
### 3. 权限策略
推荐做法:
- 桶设为 public只开放匿名读取。
- 发布上传使用 `service_role` key并且只在 CI/CD 或维护机器环境变量中保存。
- 不给 `anon` 角色配置 `INSERT`、`UPDATE`、`DELETE` 权限。
- 不在客户端配置、日志、提交信息或文档中写入真实 key。
Supabase 的 public bucket 只代表对象可以通过公开 URL 读取;上传、覆盖、删除仍受访问控制影响。使用 `service_role` key 上传时会绕过 RLS适合发布流程。
如果必须用非 service role 的登录用户上传,需要在 `storage.objects` 上创建受限策略。下面示例只允许已登录用户写入指定目录,实际项目应再结合用户、角色或后端校验收紧权限:
```sql
create policy "release upload insert"
on storage.objects
for insert
to authenticated
with check (
bucket_id = 'app-releases'
and (storage.foldername(name))[1] = 'my-app'
);
create policy "release upload select"
on storage.objects
for select
to authenticated
using (
bucket_id = 'app-releases'
and (storage.foldername(name))[1] = 'my-app'
);
create policy "release upload update"
on storage.objects
for update
to authenticated
using (
bucket_id = 'app-releases'
and (storage.foldername(name))[1] = 'my-app'
)
with check (
bucket_id = 'app-releases'
and (storage.foldername(name))[1] = 'my-app'
);
```
覆盖已有文件需要 `SELECT``UPDATE` 权限。发布流程更推荐使用 service role而不是给普通客户端发放写权限。
## 应用配置文件
建议在应用中放一个独立配置文件,例如 `Config/update_config.json`
```json
{
"enable_auto_update": true,
"update_server_type": "supabase",
"update_server_url": "https://<project-ref>.supabase.co/storage/v1/object/public/app-releases/my-app/windows/stable",
"update_check_interval_hours": 24,
"silent_install": false,
"supabase": {
"project_url": "https://<project-ref>.supabase.co",
"bucket_name": "app-releases",
"directory": "my-app/windows/stable"
}
}
```
字段说明:
| 字段 | 用途 |
| --- | --- |
| `enable_auto_update` | 是否启用自动更新 |
| `update_server_type` | 可固定为 `supabase`,用于程序内部区分更新源 |
| `update_server_url` | 客户端 Velopack 更新源根地址 |
| `update_check_interval_hours` | 自动检查间隔;手动检查可忽略该间隔 |
| `silent_install` | 是否下载后延迟到下次启动自动应用 |
| `supabase.project_url` | 发布脚本上传时使用的 Supabase 项目 URL |
| `supabase.bucket_name` | 发布包所在 bucket |
| `supabase.directory` | bucket 内目录 |
`supabase` 节只给发布脚本使用。客户端运行时检查更新只需要 `update_server_url`
## 客户端集成
### 1. 安装 NuGet 包
```powershell
dotnet add <YourApp.csproj> package Velopack
```
### 2. 在程序入口尽早初始化
Velopack 初始化应尽量放在应用启动最早位置:
```csharp
using Velopack;
internal static class Program
{
[STAThread]
public static void Main(string[] args)
{
VelopackApp.Build()
.SetArgs(args)
.SetAutoApplyOnStartup(true)
.Run();
StartApplication(args);
}
}
```
`SetAutoApplyOnStartup(true)` 用于在下次启动时自动应用已下载但尚未安装的更新。
### 3. 检查、下载并应用更新
下面是最小可复用逻辑:
```csharp
using Velopack;
public sealed class AppUpdateService
{
private readonly string _updateServerUrl;
public AppUpdateService(string updateServerUrl)
{
_updateServerUrl = updateServerUrl;
}
public async Task<bool> CheckDownloadAndRestartAsync(
IProgress<int>? progress = null,
CancellationToken cancellationToken = default)
{
var manager = new UpdateManager(_updateServerUrl);
if (!manager.IsInstalled)
{
return false;
}
var updateInfo = await manager.CheckForUpdatesAsync().ConfigureAwait(false);
if (updateInfo?.TargetFullRelease is null)
{
return false;
}
await manager.DownloadUpdatesAsync(
updateInfo,
value => progress?.Report(Math.Clamp(value, 0, 100)),
cancellationToken).ConfigureAwait(false);
manager.ApplyUpdatesAndRestart(updateInfo.TargetFullRelease);
return true;
}
}
```
建议在 UI 层:
- 启动后延迟 1 到 3 秒检查,避免影响首屏。
- 发现新版本后先提示用户,再下载并重启。
- 手动“检查更新”按钮忽略检查间隔。
- 开发环境或直接运行 `dotnet run` 时,`manager.IsInstalled` 通常为 `false`,应跳过更新检查。
## 打包发布
### 1. 安装 Velopack CLI
```powershell
dotnet tool install -g vpk
```
### 2. 提升版本号
更新项目文件中的版本号,确保新版本高于线上版本:
```xml
<Version>1.2.3</Version>
```
### 3. 发布应用目录
```powershell
dotnet publish .\src\MyApp\MyApp.csproj `
-c Release `
-r win-x64 `
--self-contained false `
-o .\artifacts\publish\my-app
```
如果目标机器不保证安装 .NET Runtime可以改用 self-contained 发布,或按 Velopack 文档为安装包配置所需 framework。
### 4. 生成 Velopack 包
```powershell
vpk pack `
--packId MyCompany.MyApp `
--packVersion 1.2.3 `
--packDir .\artifacts\publish\my-app `
--mainExe MyApp.exe `
--packTitle "My App" `
--packAuthors "MyCompany" `
--outputDir .\artifacts\releases\my-app
```
输出目录应至少包含:
```text
MyApp-win-Setup.exe
MyCompany.MyApp-1.2.3-full.nupkg
MyCompany.MyApp-1.2.3-delta.nupkg
releases.win.json
RELEASES
```
首次发布可能没有 delta 包;从第二次发布开始应包含 delta 包。
## 上传到 Supabase Storage
发布脚本应读取配置文件中的 `supabase.project_url`、`supabase.bucket_name` 和 `supabase.directory`,并从环境变量读取服务端 key
```powershell
$env:SUPABASE_SERVICE_ROLE_KEY = "<service-role-key>"
```
通用上传脚本示例:
```powershell
param(
[Parameter(Mandatory = $true)]
[string]$ConfigPath,
[Parameter(Mandatory = $true)]
[string]$ReleasePath,
[string]$ServiceRoleKey = $env:SUPABASE_SERVICE_ROLE_KEY
)
$ErrorActionPreference = "Stop"
function Join-UrlPath {
param(
[string]$Left,
[string]$Right
)
if ([string]::IsNullOrWhiteSpace($Left)) {
return $Right
}
if ([string]::IsNullOrWhiteSpace($Right)) {
return $Left.TrimEnd("/")
}
return $Left.TrimEnd("/") + "/" + $Right.TrimStart("/")
}
if ([string]::IsNullOrWhiteSpace($ServiceRoleKey)) {
throw "SUPABASE_SERVICE_ROLE_KEY is empty."
}
$config = Get-Content -Encoding UTF8 $ConfigPath -Raw | ConvertFrom-Json
$baseUrl = $config.supabase.project_url.TrimEnd("/")
$bucketName = $config.supabase.bucket_name
$directory = $config.supabase.directory
if ([string]::IsNullOrWhiteSpace($baseUrl) -or
[string]::IsNullOrWhiteSpace($bucketName) -or
[string]::IsNullOrWhiteSpace($directory)) {
throw "Supabase project_url, bucket_name, or directory is missing."
}
$files = Get-ChildItem -Path $ReleasePath -File | Sort-Object Name
if (-not $files) {
throw "No release files were found."
}
$headers = @{
"apikey" = $ServiceRoleKey
"Authorization" = "Bearer $ServiceRoleKey"
}
foreach ($file in $files) {
$objectPath = Join-UrlPath $directory $file.Name
$uploadUrl = Join-UrlPath "$baseUrl/storage/v1/object/$bucketName" $objectPath
$uploadHeaders = $headers.Clone()
$uploadHeaders["Content-Type"] = "application/octet-stream"
$uploadHeaders["x-upsert"] = "true"
Invoke-RestMethod `
-Uri $uploadUrl `
-Method Post `
-Headers $uploadHeaders `
-InFile $file.FullName
}
$publicBaseUrl = Join-UrlPath "$baseUrl/storage/v1/object/public/$bucketName" $directory
Write-Host "Public feed root: $publicBaseUrl"
Write-Host "Velopack feed: $(Join-UrlPath $publicBaseUrl 'releases.win.json')"
```
上传命令:
```powershell
.\UploadReleaseToSupabase.ps1 `
-ConfigPath .\Config\update_config.json `
-ReleasePath .\artifacts\releases\my-app
```
## 发布后验证
### 1. 验证 feed 可访问
```powershell
$feedUrl = "https://<project-ref>.supabase.co/storage/v1/object/public/app-releases/my-app/windows/stable/releases.win.json"
$feed = Invoke-RestMethod -Uri $feedUrl -Method Get
$feed.Assets | Sort-Object {[version]$_.Version} -Descending | Select-Object -First 5 PackageId,Version,Type,FileName,Size
```
确认:
- 最新版本等于本次发布版本。
- 最新版本至少有 `Full` 资产。
- 非首次发布时应有 `Delta` 资产。
### 2. 验证安装包 URL
```powershell
curl.exe -I "https://<project-ref>.supabase.co/storage/v1/object/public/app-releases/my-app/windows/stable/MyApp-win-Setup.exe"
```
期望结果:
```text
HTTP/1.1 200 OK
```
### 3. 验证客户端更新
建议用两个版本验证:
1. 安装旧版本。
2. 上传新版本发布包。
3. 启动旧版本并触发检查更新。
4. 确认能发现新版本、下载进度正常、重启后版本号变为新版本。
## 复用检查清单
- Supabase Storage 已创建 public bucket。
- 更新包目录规划稳定,不同应用和通道互不覆盖。
- 客户端配置的 `update_server_url` 指向包含 `releases.win.json` 的目录。
- 发布脚本只在受信环境使用 `SUPABASE_SERVICE_ROLE_KEY`
- 客户端没有保存 Supabase key。
- 每次发布前提高应用版本号。
- `vpk pack` 输出包含 `releases.win.json`、`Setup.exe` 和 `.nupkg`
- 上传后 `releases.win.json` 可通过公开 URL 访问。
- 安装包 URL 返回 `200 OK`
- 至少用旧版本安装包做一次真实升级测试。
## 参考文档
- [Velopack C# Getting Started](https://docs.velopack.io/getting-started/csharp)
- [Velopack Integrating Overview](https://docs.velopack.io/integrating/overview)
- [Supabase Storage Buckets](https://supabase.com/docs/guides/storage/buckets/fundamentals)
- [Supabase Creating Buckets](https://supabase.com/docs/guides/storage/buckets/creating-buckets)
- [Supabase Storage Access Control](https://supabase.com/docs/guides/storage/security/access-control)
- [Supabase Standard Uploads](https://supabase.com/docs/guides/storage/uploads/standard-uploads)