feat(release): ship an install path that needs no code-signing certificate
Some checks failed
deploy-site / deploy (push) Failing after 4m9s
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:
parent
a85ab799a3
commit
c35c6f3e95
13 changed files with 805 additions and 1 deletions
146
scripts/install/install-d3ro-voice.ps1
Normal file
146
scripts/install/install-d3ro-voice.ps1
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
# scripts/local/install-d3ro-voice.ps1
|
||||
# 서명 없이 D3RO Voice를 설치하는 수동 설치 스크립트.
|
||||
#
|
||||
# 왜 스크립트인가: canonical feed는 Cloudflare 뒤에 있어 업로드 본문이 100MiB를 넘으면
|
||||
# 거부된다. 사이드카(faster-whisper)를 포함한 앱은 95MiB 단위 7z 볼으로 나뉘어 있고,
|
||||
# 이 스크립트가 볼륨을 이어 붙여 해제한다. Scoop을 쓰면 Scoop이 같은 일을 자동으로 한다.
|
||||
#
|
||||
# 사용:
|
||||
# irm https://git.chanpaca.net/api/packages/yunchan/generic/d3ro-voice/portable-latest/install-d3ro-voice.ps1 | iex
|
||||
# 또는 저장 후:
|
||||
# powershell -ExecutionPolicy Bypass -File install-d3ro-voice.ps1
|
||||
#
|
||||
# 요구 사항: Windows 10/11 x64, 7-Zip(없으면 Scoop 사용을 권장).
|
||||
# 관리자 권한 불필요 — %LOCALAPPDATA%\Programs 아래에 설치한다.
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$FeedBase = 'https://git.chanpaca.net/api/packages/yunchan/generic/d3ro-voice/portable-latest',
|
||||
[string]$InstallDir = (Join-Path $env:LOCALAPPDATA 'Programs\D3RO Voice'),
|
||||
[string]$SevenZipPath = '',
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
function Write-Step($message) { Write-Host "[d3ro] $message" -ForegroundColor Cyan }
|
||||
|
||||
function Get-Sha256($path) {
|
||||
$sha = [System.Security.Cryptography.SHA256]::Create()
|
||||
try {
|
||||
$stream = [System.IO.File]::OpenRead($path)
|
||||
try {
|
||||
$bytes = $sha.ComputeHash($stream)
|
||||
} finally { $stream.Dispose() }
|
||||
} finally { $sha.Dispose() }
|
||||
return ($bytes | ForEach-Object { $_.ToString("x2") }) -join ''
|
||||
}
|
||||
|
||||
Write-Step 'D3RO Voice 휴대용 배포본 설치를 시작합니다 (서명되지 않은 빌드).'
|
||||
|
||||
# 1. 인덱스 내려받기
|
||||
$indexUrl = "$FeedBase/portable.json"
|
||||
Write-Step "인덱스: $indexUrl"
|
||||
$index = Invoke-RestMethod -Uri $indexUrl -UseBasicParsing
|
||||
$version = $index.version
|
||||
Write-Step "버전 $version, 볼륨 $($index.volumeCount)개 (합계 $([math]::Round($index.totalSize / 1MB, 1)) MB)"
|
||||
|
||||
# 2. 임시 디렉터리에 볼 내려받기 + 해시 검증
|
||||
$tempRoot = [System.IO.Path]::GetTempPath()
|
||||
if ($env:TEMP) { $tempRoot = $env:TEMP }
|
||||
elseif ($env:TMP) { $tempRoot = $env:TMP }
|
||||
$workDir = Join-Path $tempRoot "d3ro-voice-$version-portable"
|
||||
if (Test-Path $workDir) { Remove-Item -Recurse -Force $workDir }
|
||||
New-Item -ItemType Directory -Path $workDir | Out-Null
|
||||
|
||||
foreach ($volume in $index.volumes) {
|
||||
$dest = Join-Path $workDir $volume.name
|
||||
$url = "$FeedBase/$($volume.name)"
|
||||
Write-Step "내려받기: $($volume.name) ($([math]::Round($volume.size / 1MB, 1)) MB)"
|
||||
Invoke-WebRequest -Uri $url -OutFile $dest -UseBasicParsing
|
||||
|
||||
$hash = Get-Sha256 $dest
|
||||
if ($hash -ne $volume.sha256) {
|
||||
throw "해시가 일치하지 않습니다: $($volume.name)`n 기대: $($volume.sha256)`n 실제: $hash"
|
||||
}
|
||||
}
|
||||
Write-Step '모든 볼륨의 SHA-256 검증 완료'
|
||||
|
||||
# 3. 볼륨 이어 붙이기
|
||||
$archive = Join-Path $workDir "$($index.archive)"
|
||||
$stream = [System.IO.File]::Create($archive)
|
||||
try {
|
||||
foreach ($volume in $index.volumes) {
|
||||
$part = [System.IO.File]::OpenRead((Join-Path $workDir $volume.name))
|
||||
try { $part.CopyTo($stream) } finally { $part.Dispose() }
|
||||
}
|
||||
} finally {
|
||||
$stream.Dispose()
|
||||
}
|
||||
Write-Step "아카이브 결합 완료: $([math]::Round((Get-Item $archive).Length / 1MB, 1)) MB"
|
||||
|
||||
# 4. 해제 (7-Zip 필요; 없으면 안내)
|
||||
$sevenZipCandidates = @()
|
||||
foreach ($base in @($env:ProgramFiles, ${env:ProgramFiles(x86)})) {
|
||||
if ($base) { $sevenZipCandidates += (Join-Path $base '7-Zip\7z.exe') }
|
||||
}
|
||||
$sevenZip = $sevenZipCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1
|
||||
|
||||
if ($SevenZipPath) { $sevenZip = $SevenZipPath }
|
||||
|
||||
if (-not $sevenZip) {
|
||||
# Windows PowerShell 5.1 호환 (?. 연산자는 PowerShell 7 전용)
|
||||
$sevenZipCommand = Get-Command 7z -ErrorAction SilentlyContinue
|
||||
if ($sevenZipCommand) { $sevenZip = $sevenZipCommand.Source }
|
||||
}
|
||||
|
||||
if (-not $sevenZip) {
|
||||
throw @'
|
||||
7-Zip을 찾을 수 없습니다. 두 가지 방법이 있습니다.
|
||||
1) Scoop 사용(권장, 7-Zip 자동 준비):
|
||||
scoop bucket add d3ro https://git.chanpaca.net/yunchan/d3ro-voice.git
|
||||
scoop install d3ro/d3ro-voice
|
||||
2) 7-Zip 설치 후 이 스크립트를 다시 실행: https://www.7-zip.org/
|
||||
'@
|
||||
}
|
||||
|
||||
$extractDir = Join-Path $workDir 'extract'
|
||||
if (Test-Path $InstallDir) {
|
||||
if (-not $Force) {
|
||||
throw "설치 경로가 이미 있습니다: $InstallDir`n 다시 설치하려면 -Force 붙이세요."
|
||||
}
|
||||
Write-Step "기존 설치를 교체합니다: $InstallDir"
|
||||
Remove-Item -Recurse -Force $InstallDir
|
||||
}
|
||||
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
|
||||
|
||||
Write-Step '압축 해제 중 (수백 MB, 시간이 걸릴 수 있습니다)'
|
||||
& $sevenZip x $archive "-o$extractDir" -y | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) { throw "압축 해제 실패 (7-Zip exit $LASTEXITCODE)" }
|
||||
|
||||
Copy-Item -Path (Join-Path $extractDir '*') -Destination $InstallDir -Recurse -Force
|
||||
|
||||
# 5. 시작 메뉴 바로가기
|
||||
$exe = Join-Path $InstallDir 'D3RO Voice.exe'
|
||||
if (-not (Test-Path $exe)) { throw "실행 파일을 찾을 수 없습니다: $exe" }
|
||||
|
||||
$startMenu = Join-Path $env:APPDATA 'Microsoft\Windows\Start Menu\Programs'
|
||||
$shortcutPath = Join-Path $startMenu 'D3RO Voice.lnk'
|
||||
$shell = New-Object -ComObject WScript.Shell
|
||||
$shortcut = $shell.CreateShortcut($shortcutPath)
|
||||
$shortcut.TargetPath = $exe
|
||||
$shortcut.WorkingDirectory = $InstallDir
|
||||
$shortcut.Save()
|
||||
|
||||
Remove-Item -Recurse -Force $workDir -ErrorAction SilentlyContinue
|
||||
|
||||
Write-Step "설치 완료: $InstallDir"
|
||||
Write-Step "시작 메뉴 바로가기: $shortcutPath"
|
||||
Write-Host ''
|
||||
Write-Host '참고:' -ForegroundColor Yellow
|
||||
Write-Host ' - 이 빌드는 Authenticode 서명이 없어 첫 실행 시 SmartScreen 경고가 뜰 수 있습니다.'
|
||||
Write-Host ' - 자동 업데이트는 서명된 릴리스가 게시된 뒤부터 동작합니다(현재 설치본은 그 피드를 봅니다).'
|
||||
Write-Host ' - 설정/모델/기록은 %APPDATA%\d3ro-voice 를 공유하므로 기존 설치와 동일하게 유지됩니다.'
|
||||
Write-Host ''
|
||||
Write-Host "실행: `"$exe`"" -ForegroundColor Green
|
||||
Loading…
Add table
Add a link
Reference in a new issue