Files
Keysat 9a82fede7a Module split: extract yt-dlp lifecycle helpers to server/ytdlp.js
• 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.
2026-05-08 16:54:51 -05:00

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." };
}
}