import crypto from 'node:crypto' import fs from 'node:fs' import path from 'node:path' // ── Windows VC++ runtime for the managed Python ───────────────────────────── // // Voice features load native wheels (onnxruntime for the wake word, // ctranslate2 for local speech-to-text) that link against msvcp140.dll and // msvcp140_1.dll. The managed CPython only ships vcruntime140*.dll, so on a // Windows install without the system-wide VC++ Redistributable those imports // fail with "DLL load failed / The specified module could not be found". // // The desktop package carries the Microsoft-signed VC143 CRT set and copies it // next to the managed python.exe before every local backend start. That // directory is the process image directory, which Windows searches first for // extension-module dependencies, so a stale msvcp140.dll elsewhere on PATH can // never win. Existing installs are healed on their next launch. export interface VcRuntimeFile { name: string size: number sha256: string // Files the managed CPython already ships. Never overwrite an existing copy: // the interpreter owns it and replacing a loaded DLL is not possible anyway. ownedByPython?: boolean } export interface VcRuntimeManifest { schemaVersion: 1 version: string architecture: string files: VcRuntimeFile[] } export type VcRuntimeResult = | { status: 'skipped'; reason: string } | { status: 'ok' | 'repaired'; pythonHome: string; copied: string[] } | { status: 'failed'; pythonHome: string | null; copied: string[]; error: string } const FILE_NAME_RE = /^[a-z0-9_]+\.dll$/i const SHA256_RE = /^[0-9a-f]{64}$/ export function readPythonHome(venvRoot: string): string | null { let text: string try { text = fs.readFileSync(path.join(venvRoot, 'pyvenv.cfg'), 'utf8') } catch { return null } for (const line of text.split(/\r?\n/)) { const match = /^\s*home\s*=\s*(.+?)\s*$/i.exec(line) if (match) { return match[1] } } return null } export function loadVcRuntimeManifest(sourceDir: string): VcRuntimeManifest { const manifest = JSON.parse(fs.readFileSync(path.join(sourceDir, 'manifest.json'), 'utf8')) if (manifest?.schemaVersion !== 1 || !Array.isArray(manifest.files) || manifest.files.length === 0) { throw new Error('invalid VC++ runtime manifest') } for (const file of manifest.files) { if (!FILE_NAME_RE.test(file?.name) || !SHA256_RE.test(file?.sha256) || !Number.isInteger(file?.size)) { throw new Error(`invalid VC++ runtime manifest entry: ${JSON.stringify(file?.name)}`) } } return manifest } function sha256File(filePath: string): string { return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex') } function matches(filePath: string, file: VcRuntimeFile): boolean { try { return fs.statSync(filePath).size === file.size && sha256File(filePath) === file.sha256 } catch { return false } } function installFile(sourceDir: string, pythonHome: string, file: VcRuntimeFile) { const source = path.join(sourceDir, file.name) if (!matches(source, file)) { throw new Error(`bundled ${file.name} does not match its manifest digest`) } const target = path.join(pythonHome, file.name) const temp = `${target}.aiturk-${process.pid}.tmp` try { fs.copyFileSync(source, temp) if (!matches(temp, file)) { throw new Error(`copied ${file.name} failed digest verification`) } fs.renameSync(temp, target) } finally { fs.rmSync(temp, { force: true }) } } export function ensureWindowsVcRuntime({ platform, venvRoot, sourceDir }: { platform: NodeJS.Platform venvRoot: string sourceDir: string }): VcRuntimeResult { if (platform !== 'win32') { return { status: 'skipped', reason: 'not windows' } } const pythonHome = readPythonHome(venvRoot) if (!pythonHome || !fs.existsSync(path.join(pythonHome, 'python.exe'))) { return { status: 'failed', pythonHome, copied: [], error: 'managed python home not found' } } const copied: string[] = [] try { const manifest = loadVcRuntimeManifest(sourceDir) for (const file of manifest.files) { const target = path.join(pythonHome, file.name) if (file.ownedByPython ? fs.existsSync(target) : matches(target, file)) { continue } installFile(sourceDir, pythonHome, file) copied.push(file.name) } } catch (error) { return { status: 'failed', pythonHome, copied, error: error instanceof Error ? error.message : String(error) } } return { status: copied.length ? 'repaired' : 'ok', pythonHome, copied } } export function describeVcRuntimeResult(result: VcRuntimeResult): string { if (result.status === 'skipped') { return `skipped (${result.reason})` } if (result.status === 'failed') { return `failed for ${result.pythonHome ?? 'unknown python home'}: ${result.error}` + (result.copied.length ? ` (copied before failure: ${result.copied.join(', ')})` : '') } return result.status === 'repaired' ? `installed ${result.copied.join(', ')} into ${result.pythonHome}` : `verified in ${result.pythonHome}` }