feat(release): publish desktop updates from a tag through one feed

Desktop clients had two competing update sources: the runtime pointed at a
legacy GitLab registry while the Forgejo packages were filled in by
hardcoded, version-pinned scripts. Operators could not tell which feed was
authoritative, and no release could be reproduced from a tag.

Auto-update now reads a single canonical Forgejo registry feed, updated by
a version-agnostic publisher that runs from the tag on Forgejo, GitLab, and
GitHub CI alike. Channel, minimum supported version, forced install,
full-versus-delta thresholds, staged rollout, and a remote kill switch come
from one policy file the client fetches alongside the feed. Tag creation is
gated on a clean tree, matching version surfaces, and a changelog section.
This commit is contained in:
Yun Chan 2026-09-16 23:23:00 +09:00
parent 65ecc7aabc
commit 7953706142
21 changed files with 1619 additions and 90 deletions

View file

@ -1,15 +1,42 @@
// src/main/update-feed.ts
// 자동 업데이트 feed URL SSOT.
//
// release-create CI 잡이 GitLab Generic Package Registry의
// `d3ro-voice/latest` 패키지에 latest.yml + 설치파일을 게시하며,
// electron-updater가 이 URL에서 latest.yml을 읽어 업데이트를 감지한다.
// canonical feed는 Forgejo Generic Package Registry의 `d3ro-voice/latest`다.
// `publish-forgejo-release.mjs`가 latest.yml + 설치파일 + update-policy.json을
// 게시하면 electron-updater가 이 URL에서 latest.yml을 읽어 업데이트를 감지한다.
//
// 전제: 프로젝트 설정에서 "Allow anyone to pull from Package Registry" 활성화
// 전제: Forgejo에서 해당 owner/package의 무인증 pull이 허용돼야 한다
// (비활성 시 무인증 다운로드가 401 — 앱은 조용히 업데이트 체크를 건너뜀).
//
// 빈 문자열이면 UpdateService가 비활성 상태로 동작한다.
// d3r0/voice 프로젝트 ID = 1172. electron-builder.yml publish.url과 동일 값 유지.
// 버전 없는 `latest` 패키지를 가리켜야 기존 설치본이 새 릴리스를 계속 찾을 수 있다.
export const UPDATE_FEED_URL =
'https://git.chanpaca.net/api/packages/yunchan/generic/d3ro-voice/latest'
// GitLab Generic Registry legacy mirror. 2026-08 이전 설치본(0.2.1-alpha)은
// 이 feed를 폴링하므로, 새 설치자가 Forgejo feed를 내장할 때까지 publisher가
// 함께 게시한다. 마이그레이션 완료 후 제거 가능. 런타임은 참조하지 않는다.
export const LEGACY_UPDATE_FEED_URL =
'https://gitlab.twentyoz.kr:8443/api/v4/projects/1172/packages/generic/d3ro-voice/latest'
/** 업데이트 채널. `latest`=stable, `beta`, `alpha` 순으로 불안정. */
export type UpdateChannel = 'latest' | 'beta' | 'alpha'
export const UPDATE_CHANNELS: readonly UpdateChannel[] = ['latest', 'beta', 'alpha']
/** 채널별 electron-builder update metadata 파일명. */
export function channelMetadataName(channel: UpdateChannel): string {
return channel === 'latest' ? 'latest.yml' : `${channel}.yml`
}
/** 원격 업데이트 정책 파일명. feed 루트에서 내려받는다. */
export const UPDATE_POLICY_FILENAME = 'update-policy.json'
/** 원격 업데이트 정책 URL (없으면 앱 내장 기본 정책으로 폴백). */
export const UPDATE_POLICY_URL = UPDATE_FEED_URL
? `${UPDATE_FEED_URL}/${UPDATE_POLICY_FILENAME}`
: ''
export function isUpdateChannel(value: unknown): value is UpdateChannel {
return typeof value === 'string' && (UPDATE_CHANNELS as readonly string[]).includes(value)
}