d3ro-voice/docs/deployment/release-guide.md
Yun Chan 0411f389d9
Some checks failed
deploy-site / deploy (push) Failing after 3m26s
feat(release): make the signing-free install work with nothing but Windows
The manual install path still needed 7-Zip, which the target machine does not
have, so "installable without a certificate" was not yet true.

The channel now also publishes the app as byte-split zip parts, and the install
script joins them and extracts with the built-in Windows Expand-Archive after
verifying every part and the joined archive. Version 1.3.1 republishes the
channel from a single build, because a version's artifacts can only match one
build and published volumes are never overwritten.
2026-09-18 12:03:18 +09:00

19 KiB

D3RO Voice 릴리스 가이드

기준일: 2026-09-18. 이 문서는 desktop GitLab 패키지·자동 업데이트와 mobile store release의 경계를 분리한다. 태그 생성이나 HTTP 200 하나만으로 배포 완료를 선언하지 않는다.

현재 release identity

항목 정본 현재 판정
제품 버전 release/product-version.json: 1.3.0 source SSOT 확정
Android versionCode 1030001 production AAB 미생성
iOS build 1030001 production archive 미검증
Android upload key alias d3ro-upload-20260821, cert SHA-256 4F:AC:69:24:...:15:2B:54 external PKCS12·user-only ACL·Credential Manager·private-key readback GREEN; CI secret·복구 백업·AAB signer 대조 대기
release evidence Ed25519 public release/mobile-release-evidence-public.pem, keyId 2797d3e6...4a890b7f external private key ACL·roundtrip GREEN; CI private-key secret·복구 백업 대기
desktop offline license Ed25519 public apps/desktop/resources/license/production-public.pem, keyId 5c52b765...81a887f 새 전용 keypair·external private ACL·roundtrip·desktop production build GREEN; admin ADMIN_LICENSE_PRIVATE_KEY secret 주입 대기
Windows Authenticode external public-trust code-signing certificate production PFX 없음. Forgejo 저장소 시크릿 4종(WIN_CSC_LINK/WIN_CSC_KEY_PASSWORD/WIN_CSC_EXPECTED_SIGNER_SUBJECT/FORGEJO_TOKEN)이 0건이라 릴리스 파이프라인이 fail-closed. scripts/ci/set-forgejo-secrets.mjs --check로 확인한다. GREEN 전까지 게시 금지
Firebase Console u/0, u/1 모두 D3RO project 없음 사용자 승인 후 project·Android app 생성 필요
AdMob app ca-app-pub-1039714767792854~6427959892; banner /9840591290; rewarded /2255790918 SSOT 확정. 검토 필요·광고 게재 제한·store 미연결·결제 프로필 미완료
updater feed (canonical) https://git.chanpaca.net/api/packages/yunchan/generic/d3ro-voice/latest Forgejo Generic Registry. GitLab project 1172은 legacy mirror
release notes CHANGELOG.md ## [1.3.0] + Play changelog 1030001.txt (ko/en) 태그 전 확정·검증 필수
직전 게시본 Forgejo Release v1.1.0 (2026-09-15 게시, unsigned installer) v1.2.0·v1.3.0 파이프라인은 Forgejo 저장소 시크릿이 없어 실패 = 게시본 없음. 설치본이 있는 마지막 버전은 1.1.0

live canonical feed(git.chanpaca.net/.../d3ro-voice/latest)의 latest.yml은 현재 1.1.0을 보고한다. 시크릿을 채우고 v1.3.0 파이프라인이 GREEN이 되면 그 값이 1.3.0으로 올라가고, 그때부터 기존 설치본이 자동 업데이트를 받는다.

desktop 릴리스 파이프라인

authoritative release commit
  → version/check/test/build GREEN
  → annotated tag v1.3.0
  → package-windows  (build-win-x64)
  → package-macos    (build-mac-arm64)
  → publish-release  (build-linux-x64)
       ├─ publish-forgejo-release.mjs  ← canonical
       │    ├─ Forgejo Generic Registry /d3ro-voice/1.3.0/  (버전별 보존)
       │    ├─ Forgejo Generic Registry /d3ro-voice/latest/ (updater feed + update-policy.json)
       │    └─ Forgejo Release + CHANGELOG notes + 자산 첨부
       └─ publish-gitlab-release.mjs   ← legacy mirror (pre-Forgejo 설치본)
            ├─ GitLab Generic Registry /d3ro-voice/1.3.0/
            ├─ GitLab Generic Registry /d3ro-voice/latest/
            └─ GitLab Release
  • Forgejo·GitHub Actions도 동일한 publish-forgejo-release.mjs로 수렴한다. 어느 빌더가 성공해도 canonical feed는 하나다.

  • publish-forgejo-release.mjs는 설치 자산을 먼저, latest.yml을 마지막에 게시하고, 공개 URL에서 재검증한 뒤 Release 자산을 첨부한다.

  • 설치 바이너리는 저장소에 커밋하지 않는다. 배포 정본은 Forgejo feed이며, site/public/releases/*/, apps/web/public/releases/*/, apps/api-server/wwwroot/releases/*/는 build graph 밖이라 배포 산출물에 포함되지 않는다. 사이트·웹 다운로드 센터는 로컬 경로가 아니라 feed URL을 링크한다. (역사적 1.0.0 자산만 추적 상태로 남아 있다.)

  • scripts/ci/sync-version.mjs --check --tag v1.3.0는 태그, release/product-version.json, package/lockfile, Android/iOS 버전 면의 일치를 fail-closed로 검증한다.

  • scripts/ci/verify-release-metadata.mjs는 배포 메타데이터와 CI/publisher 계약을 검증한다.

  • 같은 gate는 desktop license public key가 Ed25519이고 release/product-version.jsondesktopLicensePublicKeyId와 일치하는지 검증한다. electron.vite.config.ts는 이 파일을 직접 읽으므로 누락·손상된 키로는 build가 시작되지 않는다.

  • scripts/ci/publish-forgejo-release.mjs는 canonical이다. 버전별 패키지를 먼저 올리고, latest에서 설치 자산 참조를 검증한 뒤 latest.ymlupdate-policy.json을 마지막에 게시하고 공개 URL에서 재검증한다. scripts/ci/publish-gitlab-release.mjs는 legacy mirror로 동일 자산을 GitLab에도 올린다.

  • Windows installer와 latest.yml은 필수다. macOS 산출물이 없는 Windows-only release를 의도했다면 그 판단을 release record에 남긴다.

  • package-windows는 external public-trust code-signing PFX를 protected file variable WIN_CSC_PFX_FILE로, 암호와 exact certificate subject를 protected WIN_CSC_KEY_PASSWORD, WIN_CSC_EXPECTED_SIGNER_SUBJECT로 받아야 한다. GitHub Release도 WIN_CSC_LINK, WIN_CSC_KEY_PASSWORD, WIN_CSC_EXPECTED_SIGNER_SUBJECT가 모두 없으면 실패한다.

  • scripts/ci/verify-windows-release-artifact.ps1는 installer와 unpacked app의 Authenticode Valid, exact signer subject, non-self-signed code-signing EKU, PE version, latest.yml path/size/SHA-512를 검증한다. 로컬 self-signed Everything2EverythingDev는 production 신뢰 인증서가 아니며 gate에서 명시적으로 거부한다.

  • 현재 GitLab Linux runner는 Shell executor이므로 job의 image:를 툴체인 근거로 간주하지 않는다. .gitlab-ci.ymlscripts/ci/bootstrap-linux-toolchain.sh를 source해 .NET 10.0.302, Deno 2.8.1, Microsoft OpenJDK 17.0.20.1, Android command-line tools 15859902를 고정 해시로 검증·캐시한 뒤 실행한다.

자동 업데이트 계약

apps/desktop/src/main/update-feed.tsapps/desktop/electron-builder.yml은 버전 없는 같은 canonical Forgejo URL을 가리켜야 한다.

https://git.chanpaca.net/api/packages/yunchan/generic/d3ro-voice/latest

legacy mirror(제거 예정, 런타임 참조 금지):

https://gitlab.twentyoz.kr:8443/api/v4/projects/1172/packages/generic/d3ro-voice/latest

아래 면이 하나라도 깨지면 release를 중단한다.

  1. Forgejo Generic Registry의 무인증 public pull이 허용됐다 (FORGEJO_TOKEN으로 게시, 익명으로 읽기).
  2. latest.ymlversion이 태그와 일치한다.
  3. latest.yml URL/path가 같은 latest 경로의 실제 installer를 참조한다.
  4. installer 파일명에 공백이 없다: D3RO-Voice-Setup-<version>-x64.exe.
  5. metadata SHA-512와 다운로드한 installer가 일치한다.
  6. installer와 win-unpacked/D3RO Voice.exe 모두 external public-trust certificate의 Authenticode Valid이고, signer subject가 protected CI identity와 정확히 일치한다.
  7. 이전 실제 설치본이 feed를 탐지하고, 다운로드·재시작·버전 상승을 끝까지 완료한다.

2026-09-16 live canonical latest.yml1.1.0을 보고한다(2026-09-15 hand-publish). 그 installer는 Authenticode 서명이 없으므로 정책상 정상 게시본이 아니다. 1.2.0은 서명 gate를 통과한 CI build로 이 값을 대체하는 forward-fix다. legacy GitLab feed는 여전히 0.2.1-alpha다.

업데이트 채널과 메이저/증분 정책

정책 정본은 release/update-policy.json이고, 런타임 로직은 apps/desktop/src/main/update-policy.ts다. publisher가 이 파일을 feed 루트에 게시하면 실행 중 앱이 내려받아 적용한다.

필드 의미
defaultChannel / channels latest(stable) · beta · alpha. allowPrerelease가 false면 stable 고객은 prerelease를 받지 않는다
minimumSupportedVersion 이 미만 설치본은 업데이트가 필수(연기·건너뛰기 불가)
forceInstallBelow 이 미만은 다이얼로그 없이 즉시 설치. null이면 비활성
fullInstallOnMajorChange major 승격 시 blockmap 차분 대신 전체 설치자
fullInstallVersionGap 같은 major에서 minor 갭이 이 값 이상이면 전체 설치자
stagingPercentage stable 업데이트를 노출할 사용자 비율(0~100). 강제 업데이트는 무시
killSwitch true면 업데이트 확인 자체를 중단
  • 증분(delta): 기본. .blockmap으로 변경 블록만 받는다. 첫 업데이트는 항상 full이다.
  • 전체(full): 현재 버전을 신뢰할 수 없거나, major 승격이거나, minor 갭이 임계 이상일 때 disableDifferentialDownload를 켠다.
  • 회수(rollback): stagingPercentage를 낮추거나 killSwitch를 켠다. 이미 배포된 버전은 되돌리지 않고 더 높은 patch로 forward-fix한다.

1.3.0 릴리스 절차 (canonical = Forgejo Actions)

선행 조건 — Forgejo 저장소 시크릿. .forgejo/workflows/release.yml은 아래 4개가 없으면 fail-closed로 중단한다. 실측(2026-09-18): 저장소 시크릿이 0건이라 v1.2.0v1.3.0 태그 파이프라인이 서명 가드에서 실패했고, 그래서 설치본·업데이트가 게시되지 않았다. 현재 상태 점검과 등록은 다음 한 줄로 한다.

node scripts/ci/set-forgejo-secrets.mjs --check
node --env-file-if-exists=.env scripts/ci/set-forgejo-secrets.mjs --write
시크릿 의미
WIN_CSC_LINK public-trust Authenticode PFX (base64). 개발용 Everything2EverythingDev는 거부된다
WIN_CSC_KEY_PASSWORD PFX password
WIN_CSC_EXPECTED_SIGNER_SUBJECT 인증서의 정확한 subject. verify-windows-release-artifact.ps1이 이 identity를 요구한다
FORGEJO_TOKEN write:package + write:repository (릴리스 게시 단계)
  1. release/product-version.json의 version/build 값과 모든 버전 면을 npm run version:check로 대조한다.
  2. CHANGELOG.md ## [1.3.0] - 2026-09-18 섹션을 사용자 변경점 중심으로 확정한다. publisher는 이 섹션과 그에 대응하는 Play changelog(apps/mobile-rn/metadata/android/*/changelogs/<versionCode>.txt)가 없으면 실패한다.
  3. dirty/untracked 작업을 임의로 reset·clean하지 말고, release 범위만 검토 가능한 authoritative commit으로 보존한다.
  4. 같은 commit에서 lint, typecheck, test, build, release metadata·security·artifact gate를 전부 GREEN으로 만든다.
  5. 로컬 전사 엔진 검증: 파이프라인은 sidecar:setupsidecar:buildverify-sidecar-bundle.mjs를 패키징 전에 실행한다. 이 게이트가 없으면 설치본에 엔진이 빠진 채 게시된다(과거 실제 사고). 로컬에서 electron-builder --dir만 볼 때는 서명 실패로 extraResources 복사가 중간에 끊기므로 -c.win.forceCodeSigning=false로 확인한다.
  6. desktop offline license를 제공한다면 external private key를 admin의 ADMIN_LICENSE_PRIVATE_KEY secret로 주입하고, 저장소 public key와 sign/verify roundtrip 및 발급 감사 로그를 확인한다.
  7. 이미 게시된 버전보다 높은 annotated 태그 v1.3.0을 생성해 push한다. npm run release:tag -- --dry-run으로 검증한 뒤 npm run release:tag(GPG 사용 시 -- --sign)와 git push chanpaca v1.3.0를 실행한다. 태그는 불변이며 게이트를 시작하는 후속 단계지 검증을 대체하지 않는다. 이미 게시된 버전을 재게시하지 않는다: canonical publisher는 버전별 자산이 다른 바이트를 가지면 fail-closed로 중단한다.
  8. 시크릿을 나중에 채웠다면 태그를 새로 만들 필요가 없다 — release.ymlworkflow_dispatch를 지원하므로 Forgejo UI에서 해당 태그 ref로 수동 실행한다.
  9. Forgejo Actions run(/actions/tasks API 또는 UI)에서 단계별 결과를 확인한다. pending/stuck/skipped를 GREEN으로 기록하지 않는다.
  10. Forgejo Release note/asset, latest.yml, update-policy.json, installer hash를 외부 public URL에서 다시 검증한다.
  11. 이전 설치본에서 자동 업데이트 E2E를 실행하고 실행 중 버전·프로세스·사용자 데이터 보존, 그리고 업데이트 후 로컬 받아쓰기 1회 성공을 확인한다.

서명 없이 내놓기 (portable 채널)

인증서가 없어도 사용자가 설치할 수 있어야 할 때 사용한다. 자세한 조사·비교·제약은 unsigned-distribution.md에 있다.

npm run release:portable:build    # 7z 분할 볼륨 + Scoop 매니페스트 생성
npm run release:portable:check    # 게시 예정 목록 확인(실제 업로드 없음)
npm run release:portable          # Forgejo portable 채널 게시
  • 자동 업데이트 피드(latest.yml)는 건드리지 않는다 — 서명된 릴리스 전용이다.
  • 볼륨은 불변이다: 같은 버전 경로에 다른 바이트가 있으면 게시가 중단된다.
  • 태그/수동 실행 워크플로: .forgejo/workflows/portable.yml (필요 시크릿: FORGEJO_TOKEN).
  • 산출물은 7z 분할 볼륨(Scoop용, 162MiB)과 zip 분할 부품(수동 설치용, 243MiB) 두 가지다. 수동 설치 스크립트는 Windows 내장 Expand-Archive만 쓰므로 7-Zip이 필요 없다.
  • 사용자 설치: Scoop 버킷(bucket/) 또는 install-d3ro-voice.ps1.
  • 한 버전의 산출물은 한 번의 빌드에서만 나온다(볼륨은 불변). CI 태그 파이프라인이 최초 게시자가 되게 하고, 이미 게시된 버전을 같은 번호로 다시 게시하지 않는다 — 필요하면 버전을 올린다.
  • 로컬에서 게시할 때는 npm run release:portable:build가 out/와 sidecar-dist/를 먼저 요구한다 (npm run build --workspace=@d3ro/desktop, npm run sidecar:build --workspace=@d3ro/desktop).

1.2.0 릴리스 절차 (기록)

  1. release/product-version.json의 version/build 값과 모든 버전 면을 npm run version:check로 대조한다.
  2. CHANGELOG.md ## [1.2.0] - 2026-09-16 섹션을 사용자 변경점 중심으로 확정한다. publisher는 이 섹션이 없으면 실패해야 한다.
  3. dirty/untracked 작업을 임의로 reset·clean하지 말고, release 범위만 검토 가능한 authoritative commit으로 보존한다.
  4. 같은 commit에서 lint, typecheck, test, build, release metadata·security·artifact gate를 전부 GREEN으로 만든다. Windows는 production Authenticode PFX를 주입한 CI build와 verify-windows-release-artifact.ps1 GREEN이 필수다.
  5. desktop offline license를 제공한다면 external private key를 admin의 ADMIN_LICENSE_PRIVATE_KEY secret로 주입하고, 저장소 public key와 sign/verify roundtrip 및 발급 감사 로그를 확인한다.
  6. 이미 게시된 버전보다 높은 annotated 태그 v1.2.0을 생성해 push한다. npm run release:tag -- --dry-run으로 검증한 뒤 npm run release:tag(GPG 사용 시 -- --sign)와 git push origin v1.2.0를 실행한다. 태그는 불변이며 게이트를 시작하는 후속 단계지 검증을 대체하지 않는다. 이미 게시된 버전을 재게시하지 않는다: canonical publisher는 버전별 자산이 다른 바이트를 가지면 fail-closed로 중단한다.
  7. GitLab에서 package-windows, package-macos, publish-release와 의도한 mobile job 상태를 모두 확인한다. publish-release가 Forgejo와 GitLab 양쪽에 게시했는지 로그로 확인한다. pending/stuck/skipped를 GREEN으로 기록하지 않는다.
  8. Forgejo Release note/asset, latest.yml, update-policy.json, installer hash를 외부 public URL에서 다시 검증한다.
  9. 이전 설치본에서 자동 업데이트 E2E를 실행하고 실행 중 버전·프로세스·사용자 데이터 보존을 확인한다.

mobile release와의 경계

Desktop release를 게시해도 Android production 출시가 자동으로 완료되지 않는다. Android는 다음을 별도로 증명한다.

  • 준비된 local upload/evidence key와 AdMob identity를 protected CI secret에 주입하고, 사용자 승인 후 생성한 production Firebase config와 함께 version 1.2.0, versionCode 1020001 AAB 생성
  • package/config/upload signer/ABI/16 KB page size/signed provenance GREEN
  • public APK 게시 없이 Play internal track에 제한 업로드
  • Play-signed 실기기 E2E, 12명·연속 14일 closed test, production access 승인
  • Data safety/App content/production 선언은 사용자의 action-time 검토·승인 후에만 제출

현재 local upload/evidence key는 존재하지만 CI secret 주입·복구 백업·production Firebase·AAB가 없고 Play production access도 disabled이므로 Android production 출시는 RED다.

rollback

  • Desktop: 게시 전이면 latest metadata를 바꾸지 않는다. 이미 업데이트된 클라이언트는 downgrade하지 말고 더 높은 patch 버전으로 forward-fix한다.
  • Play: staged rollout을 중단하고 더 높은 versionCode의 수정 AAB를 새로 검증·배포한다.
  • 복구 작업도 release record에 artifact hash, 시각, 판단자, 영향 범위를 남긴다.

참고 파일

  • release/product-version.json — 제품 version/build SSOT
  • release/android-release-identity.json — Play app/package/certificate identity
  • release/mobile-release-evidence-public.pem — release evidence public key
  • apps/desktop/resources/license/production-public.pem — desktop offline license public key SSOT
  • scripts/ci/sync-version.mjs — 버전 면 동기화·검증
  • scripts/ci/build-portable.mjs — 서명 없는 portable 산출물(7z 분할 볼륨 + Scoop 매니페스트)
  • scripts/ci/publish-portable-release.mjs — portable 채널 게시(불변 가드)
  • scripts/install/install-d3ro-voice.ps1 — 수동 설치 스크립트(해시 검증 + 결합 + 해제)
  • scripts/ci/create-release-tag.mjs — 릴리스 태그 게이트 (annotated/서명, 불변)
  • scripts/ci/verify-release-metadata.mjs — release metadata 자가 검증
  • scripts/ci/verify-windows-release-artifact.ps1 — Windows version·updater metadata·Authenticode gate
  • scripts/ci/publish-gitlab-release.mjs — legacy GitLab registry·Release mirror publisher
  • apps/desktop/src/main/update-feed.ts — runtime updater URL SSOT (canonical Forgejo + legacy mirror 상수)
  • release/update-policy.json — 채널·최소 지원 버전·강제 업데이트·full/delta·staged rollout·킬 스위치 SSOT
  • apps/desktop/src/main/update-policy.ts — 정책 파싱·결정 순수 로직
  • scripts/ci/publish-forgejo-release.mjs — canonical Forgejo registry·Release·feed publisher
  • apps/desktop/electron-builder.yml — builder publish URL·artifact contract
  • docs/deployment/update-system-assessment.md — 평가·2026 방법론·ADR
  • docs/v3/play/04-release-checklist.md — Play Console·AAB·closed test·production gate