Auto-install yt-dlp into %appdata%/.mc_custom on first use

The previous flow required the operator to manually install yt-dlp on the
server. Now the server downloads the correct binary for the current OS/arch
from GitHub Releases (latest) the first time a playlist fetch is requested,
caches it under %appdata%/.mc_custom (resolved per-OS), chmod +x's it on
POSIX, and skips the download on every subsequent call.

- shared/paths.ts: add getAppDataDir() + getMcCustomDir() that map %appdata%
  to ~/.config (Linux), ~/Library/Application Support (macOS), or
  process.env.APPDATA (Windows) so the path is OS-agnostic.
- server/youtube.ts: replace the old "probe PATH and bail" probeYtDlp with
  ensureYtDlp(): picks the right GitHub asset name per platform.arch
  (yt-dlp.exe / yt-dlp_macos / yt-dlp_linux / yt-dlp_linux_aarch64 /
  yt-dlp_linux_armv7l), downloads it with redirect-following https.get to
  the install path, chmods +x, then verifies with `--version` before
  reporting success. Uses an installPromise singleton so concurrent
  requests don't race the download. On any failure it cleans up the
  partial file and throws YtDlpUnavailableError with a real reason.
- docs/yt-dlp-setup.md: note that auto-install is now the default; manual
  install is only for environments where the auto-download fails.

Smoke-tested on this Linux x64 box: first call downloads to
~/.config/.mc_custom/yt-dlp_linux and reports `2026.03.17`; second call
takes ~700ms (just the version probe) and reuses the cached file.
This commit is contained in:
2026-05-12 13:15:25 +09:00
parent a532ce5507
commit 7349d4e71e
3 changed files with 151 additions and 16 deletions

View File

@@ -1,4 +1,5 @@
import path from 'node:path'
import os from 'node:os'
// 컴파일 후 dist/shared/paths.js → 2단계 상위가 프로젝트 루트.
export const projectRoot = path.resolve(__dirname, '..', '..')
@@ -9,3 +10,24 @@ export const fileDirPath = path.join(projectRoot, 'file')
export const fileListDirPath = path.join(fileDirPath, 'list')
export const viewsDirPath = path.join(projectRoot, 'views')
export const publicDirPath = path.join(projectRoot, 'public')
/**
* 사용자 환경의 "%appdata%" 디렉터리(OS별 표준 사용자 데이터 경로)를 반환.
* - Windows : %APPDATA% (보통 C:\Users\<user>\AppData\Roaming)
* - macOS : ~/Library/Application Support
* - Linux 등 : $XDG_CONFIG_HOME 또는 ~/.config
*/
export function getAppDataDir(): string {
if (process.platform === 'win32') {
return process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming')
}
if (process.platform === 'darwin') {
return path.join(os.homedir(), 'Library', 'Application Support')
}
return process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config')
}
/** %appdata%/.mc_custom — 음악퀴즈 관련 외부 도구/캐시 보관 디렉터리. */
export function getMcCustomDir(): string {
return path.join(getAppDataDir(), '.mc_custom')
}