server-youtube: add POSIX zipapp fallback when native bundled binary won't run
If the native yt-dlp_linux/yt-dlp_macos binary fails to execute (glibc
mismatch, musl libc, wrong arch) AND no system yt-dlp is on PATH, fall
back to downloading the universal Python zipapp ('yt-dlp', ~3MB) and
running it via shebang. Requires python3 on PATH, which is standard on
modern Linux servers. Also: rewrite stale Windows-flavored install-path
comments to reflect actual cross-platform behavior.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -32,21 +32,35 @@ function getYtDlpAssetName(): string {
|
||||
return 'yt-dlp' // 그 외 OS: 순수 파이썬 zipapp. python3 가 PATH 에 있어야 동작
|
||||
}
|
||||
|
||||
/** 로컬 설치 경로: %appdata%/.mc_custom/<asset> */
|
||||
/**
|
||||
* 로컬 설치 경로: OS별 사용자 데이터 디렉터리 안의 .mc_custom/<asset>.
|
||||
* - Windows: %APPDATA%/.mc_custom/yt-dlp.exe
|
||||
* - macOS : ~/Library/Application Support/.mc_custom/yt-dlp_macos
|
||||
* - Linux 등: $XDG_CONFIG_HOME 또는 ~/.config/.mc_custom/yt-dlp_linux (arch 따라 다름)
|
||||
*/
|
||||
export function getYtDlpInstallPath(): string {
|
||||
return path.join(getMcCustomDir(), getYtDlpAssetName())
|
||||
}
|
||||
|
||||
/** 순수 파이썬 zipapp(`yt-dlp`) 의 로컬 설치 경로. python3 가 PATH 에 있어야 동작. */
|
||||
function getYtDlpZipappPath(): string {
|
||||
return path.join(getMcCustomDir(), 'yt-dlp_zipapp')
|
||||
}
|
||||
|
||||
/** 한 번에 한 다운로드만 진행하도록 락 (서버 동시 요청 보호). */
|
||||
let installPromise: Promise<string> | null = null
|
||||
|
||||
type ProbeResult = { ok: true } | { ok: false; detail: string }
|
||||
|
||||
/**
|
||||
* %appdata%/.mc_custom/ 에 yt-dlp 가 준비됐는지 확인하고, 없으면 GitHub Releases 에서
|
||||
* 현재 OS/아키텍처용 바이너리를 자동으로 받아 설치한다. 성공 시 실행 경로 반환.
|
||||
* 다운로드된 바이너리가 실행되지 않는 환경(antivirus, 권한 문제 등)이라면
|
||||
* PATH 의 `yt-dlp(.exe)` 로 폴백하고, 그것도 실패하면 진단정보가 포함된 에러를 던진다.
|
||||
* .mc_custom/ 디렉터리에 yt-dlp 가 준비됐는지 확인하고, 없으면 GitHub Releases 에서
|
||||
* 현재 OS/아키텍처용 네이티브 바이너리를 자동으로 받아 설치한다. 성공 시 실행 경로 반환.
|
||||
*
|
||||
* 네이티브 바이너리가 실행되지 않는 환경(glibc 미스매치, musl libc, antivirus 차단 등)
|
||||
* 이면 다음 순서로 폴백한다:
|
||||
* 1) PATH 의 `yt-dlp(.exe)` (시스템에 따로 깐 거)
|
||||
* 2) (POSIX 한정) 범용 파이썬 zipapp `yt-dlp` 를 다운로드 후 shebang 실행 — python3 필요
|
||||
* 전부 실패하면 각 시도의 진단정보가 포함된 에러를 던진다.
|
||||
*/
|
||||
export async function ensureYtDlp(): Promise<string> {
|
||||
const target = getYtDlpInstallPath()
|
||||
@@ -105,6 +119,25 @@ async function prepareYtDlp(target: string): Promise<string> {
|
||||
try { await fs.unlink(target) } catch { /* noop */ }
|
||||
}
|
||||
|
||||
// 4. POSIX 한정 최후 폴백: 범용 파이썬 zipapp `yt-dlp` 다운로드 후 shebang 실행.
|
||||
// 네이티브 바이너리가 glibc/musl/arch 문제로 못 도는 리눅스 환경이라도
|
||||
// python3 가 PATH 에 있으면 동작한다. ~ 3MB 짜리 스크립트.
|
||||
if (process.platform !== 'win32') {
|
||||
const zipappPath = getYtDlpZipappPath()
|
||||
try {
|
||||
try { await fs.unlink(zipappPath) } catch { /* noop */ }
|
||||
await downloadToFile('https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp', zipappPath)
|
||||
await fs.chmod(zipappPath, 0o755)
|
||||
const probe = await probeVersion(zipappPath)
|
||||
if (probe.ok) return zipappPath
|
||||
diagnostics.push(`zipapp yt-dlp 검증 실패: ${probe.detail} (python3 누락이거나 PATH 에 없음)`)
|
||||
try { await fs.unlink(zipappPath) } catch { /* noop */ }
|
||||
} catch (err) {
|
||||
diagnostics.push(`zipapp 다운로드 실패: ${err instanceof Error ? err.message : String(err)}`)
|
||||
try { await fs.unlink(zipappPath) } catch { /* noop */ }
|
||||
}
|
||||
}
|
||||
|
||||
throw new YtDlpUnavailableError(
|
||||
t('youtube.ytdlpVerifyFailedDetail', { detail: diagnostics.join(' | ') })
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user