9a82fede7a
• checkYtdlp() — version + GitHub-latest check (24h-cached) • autoUpdateYtdlp(dataDir) — multi-strategy update (-U, pip, brew, binary) Module-private memoization (ytdlpVersion, ytdlpLastCheck) now stays inside ytdlp.js where it belongs. autoUpdateYtdlp gained an explicit dataDir parameter — strategy 4 (StartOS binary download to /data/bin/) needs it; passing it in keeps the module pure of caller-side state. server/index.js: 2694 → 2614 lines. Smoke tested: server boots; /api/license-status, /api/health respond. No behavior change.
105 lines
4.2 KiB
JavaScript
105 lines
4.2 KiB
JavaScript
// yt-dlp lifecycle helpers: version check, GitHub-latest comparison, and
|
|
// multi-strategy auto-update. Self-contained — module-private memoization
|
|
// for the GitHub check (capped to once per 24h to avoid hammering the
|
|
// API).
|
|
|
|
import { execFile } from "child_process";
|
|
import { promisify } from "util";
|
|
import path from "path";
|
|
import fs from "fs/promises";
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
|
|
let ytdlpVersion = null;
|
|
let ytdlpLastCheck = 0;
|
|
const UPDATE_CHECK_INTERVAL = 24 * 60 * 60 * 1000; // 24 hours
|
|
|
|
// ── Version + update-availability check ─────────────────────────────────────
|
|
// Returns `{ installed, version, updateAvailable, latestVersion }`.
|
|
// Suppresses errors — a missing yt-dlp returns `installed: false`, a
|
|
// failed GitHub call just leaves `latestVersion` as null. The GitHub
|
|
// fetch is rate-limited to once per 24h.
|
|
export async function checkYtdlp() {
|
|
const info = { installed: false, version: null, updateAvailable: false, latestVersion: null };
|
|
try {
|
|
const { stdout } = await execFileAsync("yt-dlp", ["--version"]);
|
|
info.installed = true;
|
|
info.version = stdout.trim();
|
|
ytdlpVersion = info.version;
|
|
} catch {
|
|
return info;
|
|
}
|
|
|
|
const now = Date.now();
|
|
if (now - ytdlpLastCheck > UPDATE_CHECK_INTERVAL) {
|
|
ytdlpLastCheck = now;
|
|
try {
|
|
const resp = await fetch("https://api.github.com/repos/yt-dlp/yt-dlp/releases/latest", {
|
|
headers: { "Accept": "application/vnd.github.v3+json" },
|
|
signal: AbortSignal.timeout(5000),
|
|
});
|
|
if (resp.ok) {
|
|
const data = await resp.json();
|
|
info.latestVersion = data.tag_name?.replace(/^v/, "") || null;
|
|
if (info.latestVersion && info.version !== info.latestVersion) {
|
|
info.updateAvailable = true;
|
|
}
|
|
}
|
|
} catch {} // Network errors are fine, just skip the check
|
|
}
|
|
return info;
|
|
}
|
|
|
|
// ── Multi-strategy auto-update ──────────────────────────────────────────────
|
|
// Tries strategies in order until one works:
|
|
// 1. yt-dlp -U (built-in self-update, when permitted)
|
|
// 2. pip3/pip install -U (containers, macOS)
|
|
// 3. brew upgrade yt-dlp (macOS local dev)
|
|
// 4. Direct binary download (StartOS fallback to /data/bin/)
|
|
//
|
|
// `dataDir` is needed only for strategy 4. Returns `{ success, message }`.
|
|
export async function autoUpdateYtdlp(dataDir) {
|
|
console.log(" ↻ Updating yt-dlp...");
|
|
|
|
// Strategy 1: yt-dlp's built-in self-update
|
|
try {
|
|
const { stdout } = await execFileAsync("yt-dlp", ["-U"], { timeout: 60000 });
|
|
console.log(` ${stdout.trim()}`);
|
|
return { success: true, message: stdout.trim() };
|
|
} catch {
|
|
console.log(" … Self-update failed, trying pip...");
|
|
}
|
|
|
|
// Strategy 2: pip3 / pip
|
|
for (const pip of ["pip3", "pip"]) {
|
|
try {
|
|
await execFileAsync(pip, ["install", "-U", "yt-dlp"], { timeout: 120000 });
|
|
console.log(` ✓ Updated via ${pip}`);
|
|
return { success: true, message: `Updated via ${pip}` };
|
|
} catch {}
|
|
}
|
|
|
|
// Strategy 3: Homebrew (macOS local dev only)
|
|
try {
|
|
await execFileAsync("brew", ["upgrade", "yt-dlp"], { timeout: 120000 });
|
|
console.log(" ✓ Updated via Homebrew");
|
|
return { success: true, message: "Updated via Homebrew" };
|
|
} catch {}
|
|
|
|
// Strategy 4: Direct binary download to persistent storage (StartOS fallback)
|
|
try {
|
|
const binDir = path.join(dataDir, "bin");
|
|
await fs.mkdir(binDir, { recursive: true });
|
|
const binPath = path.join(binDir, "yt-dlp");
|
|
console.log(" … Trying direct binary download...");
|
|
const { stdout } = await execFileAsync("sh", ["-c",
|
|
`curl -L -o "${binPath}" "https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp" && chmod +x "${binPath}" && "${binPath}" --version`
|
|
], { timeout: 120000 });
|
|
console.log(` ✓ Downloaded yt-dlp binary: ${stdout.trim()}`);
|
|
return { success: true, message: `Downloaded binary: ${stdout.trim()}` };
|
|
} catch {
|
|
console.log(" ✗ All update strategies failed");
|
|
return { success: false, message: "Auto-update failed. Try updating manually from the StartOS Actions menu." };
|
|
}
|
|
}
|