# 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://.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://.supabase.co/storage/v1/object/public/app-releases/my-app/windows/stable", "update_check_interval_hours": 24, "silent_install": false, "supabase": { "project_url": "https://.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 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 CheckDownloadAndRestartAsync( IProgress? 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 1.2.3 ``` ### 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 = "" ``` 通用上传脚本示例: ```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://.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://.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)