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

12 KiB

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 按安装包大小设置 例如 512MB1GB 或更高
Allowed MIME types 留空或允许二进制 Velopack 产物包含 .exe.nupkg.json

Dashboard 路径通常是:

Supabase Dashboard -> Storage -> New bucket

也可以用 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. 目录规划

建议每个应用、平台和发布通道使用独立目录:

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 更新源根地址为:

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 角色配置 INSERTUPDATEDELETE 权限。
  • 不在客户端配置、日志、提交信息或文档中写入真实 key。

Supabase 的 public bucket 只代表对象可以通过公开 URL 读取;上传、覆盖、删除仍受访问控制影响。使用 service_role key 上传时会绕过 RLS适合发布流程。

如果必须用非 service role 的登录用户上传,需要在 storage.objects 上创建受限策略。下面示例只允许已登录用户写入指定目录,实际项目应再结合用户、角色或后端校验收紧权限:

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

覆盖已有文件需要 SELECTUPDATE 权限。发布流程更推荐使用 service role而不是给普通客户端发放写权限。

应用配置文件

建议在应用中放一个独立配置文件,例如 Config/update_config.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 包

dotnet add <YourApp.csproj> package Velopack

2. 在程序入口尽早初始化

Velopack 初始化应尽量放在应用启动最早位置:

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. 检查、下载并应用更新

下面是最小可复用逻辑:

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

dotnet tool install -g vpk

2. 提升版本号

更新项目文件中的版本号,确保新版本高于线上版本:

<Version>1.2.3</Version>

3. 发布应用目录

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 包

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

输出目录应至少包含:

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_urlsupabase.bucket_namesupabase.directory,并从环境变量读取服务端 key

$env:SUPABASE_SERVICE_ROLE_KEY = "<service-role-key>"

通用上传脚本示例:

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

上传命令:

.\UploadReleaseToSupabase.ps1 `
  -ConfigPath .\Config\update_config.json `
  -ReleasePath .\artifacts\releases\my-app

发布后验证

1. 验证 feed 可访问

$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

curl.exe -I "https://<project-ref>.supabase.co/storage/v1/object/public/app-releases/my-app/windows/stable/MyApp-win-Setup.exe"

期望结果:

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.jsonSetup.exe.nupkg
  • 上传后 releases.win.json 可通过公开 URL 访问。
  • 安装包 URL 返回 200 OK
  • 至少用旧版本安装包做一次真实升级测试。

参考文档