feat(release): ship an install path that needs no code-signing certificate
Some checks failed
deploy-site / deploy (push) Failing after 4m9s

Installers could not be published at all: the signing certificate does not
exist yet, and the release pipelines stop at their signing guard. Users had no
way to install a fixed build, so the product was effectively stuck behind a
certificate that takes weeks to obtain.

There is also a second, independent blocker: the download feed sits behind
Cloudflare, which rejects any upload body over about 100 MiB, and the app with
its speech engine exceeds that even when signed.

A portable channel now publishes what can actually be delivered today: the app
compressed into 95 MiB 7z volumes (162 MiB total instead of 243 MiB), a Scoop
bucket for a normal install and uninstall experience, and a verifiable manual
installer script. It is deliberately separate from the auto-update feed, needs
no certificate, and refuses to overwrite an already published version.
This commit is contained in:
Yun Chan 2026-09-18 11:03:34 +09:00
parent a85ab799a3
commit c35c6f3e95
13 changed files with 805 additions and 1 deletions

View file

@ -139,6 +139,7 @@ See [`03-shared-packages.md`](./03-shared-packages.md). Summary:
Stages `validate → test → build → e2e → package → publish → deploy`. Primary pipeline for desktop Windows/macOS releases (Forgejo Generic Registry is the canonical updater feed; GitLab project 1172 is a legacy mirror) and production mobile releases (`mobile-production-release`, manual/protected). Admin NAS deploy job is intentionally **disabled**. `package-windows`/`package-macos` build the faster-whisper sidecar (`sidecar:setup``sidecar:build`) and run `scripts/ci/verify-sidecar-bundle.mjs` before electron-builder, so a release can never ship without the local STT engine.
### Forgejo Actions (`.forgejo/workflows/`)
`portable.yml` — 태그/수동 실행으로 **서명 없이** portable 채널(95MiB 7z 분할 볼륨 + Scoop 매니페스트 + 설치 스크립트)을 게시한다. `WIN_CSC_*` 불필요, updater feed는 건드리지 않는다.
`deploy-site.yml` / `deploy-site-windows.yml` — build `site`, write release identity, deploy to Cloudflare Pages `d3ro` (`d3ro.chanpaca.net`), verify live commit/version, app-links, legal URLs.
`release.yml` — tag-triggered Windows build (signed) + `publish-forgejo-release.mjs` to the canonical Forgejo feed/release hub.

View file

@ -193,6 +193,7 @@ Status quick-reference: `[x]` done+verified · `[~]` partial/unverified · `[ ]`
| INFRA-13 | Site deploy (Cloudflare Pages + GitHub Pages) | [x] | `.forgejo/workflows/deploy-site.yml`, `.github/workflows/deploy-site.yml` |
| INFRA-15 | Update & release system | [x] | Canonical Forgejo feed + channels/policy (`release/update-policy.json`, `src/main/update-policy.ts`), canonical publisher `scripts/ci/publish-forgejo-release.mjs`, legacy GitLab mirror; `npm run release:metadata:test`. v1.1.0 was published to Forgejo on 2026-09-15; product version moved to `1.2.0` as a forward-fix with CI-only publication, a same-version re-release guard, and download centers that link the feed instead of repository paths. `1.3.0` (2026-09-18) carries the local-STT fixes; Windows publication still needs the CI signing secrets (`11` GAP-REL-02). |
| INFRA-16 | Desktop STT engine packaging | [x] | `apps/desktop/scripts/setup-sidecar.mjs` + `build-sidecar.mjs`, `electron-builder.yml` `extraResources` (`sidecar-dist/sidecar``resources/sidecar`, `resources/ffmpeg`), and `scripts/ci/verify-sidecar-bundle.mjs` run in `package-windows`/`package-macos` before electron-builder. Verified on the real bundle: `sidecar.exe` + `_internal` including `faster_whisper/assets/silero_vad_v6.onnx`, plus a packaged-engine transcription round-trip on GPU. |
| INFRA-17 | 서명 없는 배포 채널 (portable + Scoop) | [x] | `scripts/ci/build-portable.mjs` (95MiB 7z 분할 볼륨 + Scoop 매니페스트), `scripts/ci/publish-portable-release.mjs`, `scripts/local/install-d3ro-voice.ps1`, `bucket/` 버킷, `.forgejo/workflows/portable.yml`; updater feed와 분리. 2026-09-18 게시 + 실제 설치 검증. |
---

View file

@ -22,7 +22,7 @@ Legend: `[ ]` open · `[~]` in progress · `[!]` blocked externally · `[x]` res
|---|---|---|---|---|
| GAP-QA-01 | Quality | Extreme Red Team: headful end-to-end bug hunting across real desktop Electron, Web Next.js, and CI pipelines. | `red_team_log.md`, `tests/e2e/red_team_cycle*.spec.ts`, `apps/web/e2e/red_team_cycle4_web.spec.ts` | `[x]` 2026-09-15: 18 scenarios executed, 14 defects caught and 100% resolved (infinite chunking loop DEF-008, IPC signature mismatch DEF-004, markdown editor typing rollback DEF-006, Web RSC Link serialization DEF-012, secret scanner lookahead DEF-013, etc.). All 18 scenarios GREEN with zero regressions. |
| GAP-REL-01 | Release | Official release publication to Forgejo and active public download center deployment. | `scripts/ci/publish-forgejo-release.mjs`, `apps/web/src/app/download/page.tsx`, `site/src/sections/Download.tsx`, `apps/web/e2e/red_team_cycle4_web.spec.ts` | `[~]` 2026-09-15: v1.1.0 release assets (`D3RO-Voice-Setup-1.1.0-x64.exe`, `.blockmap`, `latest.yml`, `update-policy.json`) published to canonical Forgejo registry and release hub. 2026-09-16: the published 1.1.0 installer carries no Authenticode signature, so it does not satisfy the release policy; product version moved to `1.2.0` and publication must come from CI with the signing gate GREEN. Download centers in `apps/web` (`/download`) and `site` (`#download`) link the canonical Forgejo feed. |
| GAP-REL-02 | Release | Windows stable publication needs an external public-trust Authenticode PFX, its password, the exact signer subject, and a Forgejo token, none of which live in the repository. | `.forgejo/workflows/release.yml`, `.gitlab-ci.yml`, `scripts/ci/set-forgejo-secrets.mjs`, `scripts/ci/verify-windows-release-artifact.ps1` | `[!]` 2026-09-18 measured: the Forgejo repo has **zero** Actions secrets (`GET /repos/yunchan/d3ro-voice/actions/secrets``[]`), so `v1.2.0` (run 49) and `v1.3.0` (run 51) both failed at the signing guard and **no installer or update has been published since `1.1.0`**. Inject the four secrets (`WIN_CSC_LINK`, `WIN_CSC_KEY_PASSWORD`, `WIN_CSC_EXPECTED_SIGNER_SUBJECT`, `FORGEJO_TOKEN`) with `npm run release:secrets` (check: `npm run release:secrets:check`), then re-run `release.yml` for the `v1.3.0` tag via `workflow_dispatch` (tags are immutable). |
| GAP-REL-02 | Release | Windows stable publication needs an external public-trust Authenticode PFX, its password, the exact signer subject, and a Forgejo token, none of which live in the repository. | `.forgejo/workflows/release.yml`, `.gitlab-ci.yml`, `scripts/ci/set-forgejo-secrets.mjs`, `scripts/ci/verify-windows-release-artifact.ps1` | `[!]` 2026-09-18 measured: the Forgejo repo had **zero** Actions secrets; `FORGEJO_TOKEN` is registered now (2026-09-18) but `WIN_CSC_*` still have no values, so `v1.2.0` (run 49) and `v1.3.0` (run 51) both failed at the signing guard and **no updater-feed release has been published since `1.1.0`**. Inject the four secrets (`WIN_CSC_LINK`, `WIN_CSC_KEY_PASSWORD`, `WIN_CSC_EXPECTED_SIGNER_SUBJECT`, `FORGEJO_TOKEN`) with `npm run release:secrets` (check: `npm run release:secrets:check`), then re-run `release.yml` for the `v1.3.0` tag via `workflow_dispatch` (tags are immutable). |
| GAP-ADS-01 | Ads | 9 of 10 desktop ad adapters still extend `UnavailableAdAdapter` (`provider_not_integrated`). | `apps/desktop/src/main/services/ads/*` | `[~]` 2026-09-13: `DirectHouseSponsorAdapter` is now a real configurable REST adapter (bid/impression/click/reward via `endpointUrl`; fail-closed when unconfigured; 22 unit tests GREEN). Remaining 9 need official SDKs/authenticated endpoints. |
| GAP-ADS-02 | Ads | Desktop mediation reward accounting is not wired to license quota (`claimReward` still returns no tokens). | `AdMediationEngine.ts`, `AppLayout.tsx` | Wire verified `reportRewardCompletion` to `LicenseService` quota after the direct sponsor endpoint exists. |
| GAP-ID-01 | Identity | Supabase, .NET JWT/SQLite, and the desktop offline license each had their own tier/role shape. | `@d3ro/core/entitlement`, `LicenseService`, `entitlement-context` | `[~]` 2026-09-13: canonical `EntitlementSnapshot` + `resolveEntitlement` added with tests; desktop tier normalization + `isPro` fixed. Full adoption tracked as GAP-ID-02. |
@ -47,6 +47,9 @@ Legend: `[ ]` open · `[~]` in progress · `[!]` blocked externally · `[x]` res
| GAP-STT-03 | Local engines | On hosts where `localhost` resolves only to IPv6, every local engine call (STT sidecar and Ollama) was refused. Audio capture and local LLM appeared dead. | `apps/desktop/src/main/utils/loopback.ts`, `LocalSTTService`, `LocalLLMService`, `RAGService`, `OnlineLLMService`, `STTManager` | `[x]` 2026-09-18: loopback normalization to `127.0.0.1` for all local engine URLs; defaults updated; 9 unit tests. Verified against the live sidecar and Ollama on a host with an IPv6-only `localhost`. |
| GAP-STT-04 | Local STT | Live partial transcript (`CAP-03`, `voice:partialTranscript`) was marked done but had **no producer**: the channel, popup UI, and preload existed, nothing ever emitted. | `apps/desktop/src/main/services/VoiceModeService.ts`, `LocalSTTService.transcribePartial`, `STTManager.transcribePartial` | `[x]` 2026-09-18: 1.5 s cadence over a 7.5 s trailing window, greedy decode, drained before the final transcription; never inserted. |
| GAP-STT-05 | Local STT | The bundled sidecar lacked faster-whisper's Silero VAD data, so `vad_filter=true` transcription would have failed at runtime even with the engine bundled. | `apps/desktop/scripts/build-sidecar.mjs`, `scripts/ci/verify-sidecar-bundle.mjs` | `[x]` 2026-09-18: `--collect-all faster_whisper` plus a packaging-time presence check for `assets/silero_vad_v6.onnx`. |
| GAP-REL-03 | Release | 서명이 없어 설치할 수 있는 경로가 없다(인증서 발급 전 공백). | `.forgejo/workflows/portable.yml`, `scripts/ci/build-portable.mjs`, `scripts/local/install-d3ro-voice.ps1`, `bucket/d3ro-voice.json` | `[x]` 2026-09-18: 서명 없는 portable 채널 구현 — 95MiB 7z 분할 볼륨(688MB → 162MiB) + Scoop 버킷 + 수동 설치 스크립트를 Forgejo에 게시. 실제 설치 스크립트 end-to-end 검증(볼륨 다운로드 → SHA-256 → 결합 → 해제 → 엔진 포함 확인). updater feed는 건드리지 않음. |
| GAP-REL-04 | Release | canonical feed는 Cloudflare 뒤에 있어 업로드 본문이 **100MiB**를 넘으면 HTTP 413으로 거부한다. 사이드카를 포함한 NSIS 설치본은 189MB라 **인증서가 있어도 게시할 수 없다**. | `scripts/ci/publish-portable-release.mjs`, `.forgejo/workflows/release.yml`, `docs/deployment/unsigned-distribution.md` | `[ ]` 2026-09-18 실측(60MiB 201 · 110MiB 413). 해결: 앱 번들을 100MiB 이하로 만들고 엔진은 분할 다운로드로 받거나(GAP-STT-07), 게시 경로를 바꾼다. |
| GAP-STT-07 | Local STT | 엔진(사이드카)를 앱 번들에 넣으면 설치본이 100MiB를 넘고 매 업데이트마다 162MiB를 다시 받는다. | `apps/desktop/electron-builder.yml`, `LocalSTTService` | `[ ]` 앱은 엔진을 첫 실행 시 분할 다운로드 + SHA-256 검증으로 받도록 분리한다. 설치 크기/업데이트 크기/패키징 실패 지점이 모두 줄어든다. |
| GAP-STT-06 | Local STT | Decode settings were untuned: previous-text conditioning let repeated hallucinations compound, and no VAD parameters meant slow, uneven segments. | `apps/desktop/sidecar/main.py` | `[x]` 2026-09-18: `condition_on_previous_text=false`, bounded low-temperature fallback, `no_speech`/`compression_ratio`/`log_prob` thresholds, 300 ms silence trimming. Same transcript, ~5x faster on the reference machine (7.7 s audio: 1609 ms → 303 ms). |
---