[eric] merge #117: VMP-sign packaged builds so Widevine playback works

This commit is contained in:
ciregenz
2026-07-05 16:14:57 -07:00
7 changed files with 99 additions and 2 deletions
+54 -1
View File
@@ -11,8 +11,54 @@
// this rescue.
const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');
exports.default = async function afterPack(context) {
// Widevine VMP signing of the PACKAGED app. Has to happen here in afterPack, not
// at npm-install time on node_modules: the OS code-sign electron-builder runs
// right after this seals the VMP signature into the bundle, so signing the source
// electron earlier gets stripped/relocated and Spotify's license server then 500s.
// Lenient by default (a dev `npm run dist` without an EVS account still produces an
// app, just with limited DRM); VMP_REQUIRE_SIGN=1 (set by the signed release paths)
// turns a missing/failed signature into a hard build failure so prod never ships
// an unsigned-for-DRM client silently.
function signVmp(context) {
const { appOutDir, electronPlatformName, packager } = context;
const required = process.env.VMP_REQUIRE_SIGN === '1';
const acct = process.env.EVS_ACCOUNT_NAME;
const pass = process.env.EVS_PASSWD;
if (!acct || !pass) {
if (required) {
throw new Error('[afterPack] VMP_REQUIRE_SIGN=1 but EVS_ACCOUNT_NAME/EVS_PASSWD are absent — refusing to ship a release whose Widevine DRM (Spotify/Netflix) would be dead');
}
console.warn('[afterPack] EVS creds absent — skipping VMP signing; DRM playback will be limited (dev build)');
return;
}
// mac: sign the .app bundle; win: sign the unpacked dir holding the exe + framework.
const target = electronPlatformName === 'darwin'
? path.join(appOutDir, `${packager.appInfo.productFilename}.app`)
: appOutDir;
const py = process.platform === 'win32' ? 'python' : 'python3';
try {
console.log(`[afterPack] VMP-signing ${target}`);
// Creds go via the environment (EVS reads EVS_ACCOUNT_NAME/EVS_PASSWD), never on
// the argv — a password in a command line is readable by any `ps` on the host.
execFileSync(py, ['-m', 'castlabs_evs.vmp', 'sign-pkg', target, '--no-ask'], {
stdio: 'inherit',
env: { ...process.env, EVS_ACCOUNT_NAME: acct, EVS_PASSWD: pass },
});
console.log('[afterPack] VMP signing successful — full DRM playback enabled');
} catch (err) {
if (required) {
throw new Error(`[afterPack] VMP signing failed (release would have broken DRM): ${err && err.message}`);
}
console.warn(`[afterPack] VMP signing failed (non-fatal in dev): ${err && err.message}`);
}
}
function stageRouterNodeModules(context) {
const { appOutDir, electronPlatformName, packager } = context;
const src = path.join(__dirname, '..', 'build-staging', 'router', 'node_modules');
if (!fs.existsSync(src)) return; // dev/no-router build; nothing to do
@@ -34,4 +80,11 @@ exports.default = async function afterPack(context) {
throw new Error(`afterPack: 9Router node_modules/next missing in ${routerDir} after copy`);
}
console.log(`[afterPack] staged 9Router node_modules into ${routerDir}`);
}
exports.default = async function afterPack(context) {
stageRouterNodeModules(context);
// VMP signing runs last and unconditionally, after every file is staged, so the
// OS code-sign that electron-builder runs next seals the VMP signature too.
signVmp(context);
};
+9 -1
View File
@@ -42,6 +42,14 @@ if ! python3 -c "import castlabs_evs" 2>/dev/null; then
exit 0
fi
# When creds are in the env (EVS reads EVS_ACCOUNT_NAME/EVS_PASSWD itself), go
# non-interactive so CI / non-TTY runs don't hang on a prompt. Creds stay in the
# environment, never on the argv where any `ps` on the host could read them.
EVS_AUTH=()
if [ -n "${EVS_ACCOUNT_NAME:-}" ] && [ -n "${EVS_PASSWD:-}" ]; then
EVS_AUTH=(--no-ask)
fi
VERIFY_OUTPUT=$(python3 -m castlabs_evs.vmp verify-pkg "$ELECTRON_DIR" 2>&1)
if echo "$VERIFY_OUTPUT" | grep -q "Signature is valid" && ! echo "$VERIFY_OUTPUT" | grep -q "development only"; then
echo "[vmp] Electron already has a valid production VMP signature"
@@ -49,7 +57,7 @@ if echo "$VERIFY_OUTPUT" | grep -q "Signature is valid" && ! echo "$VERIFY_OUTPU
fi
echo "[vmp] Signing Electron with production VMP certificate..."
if python3 -m castlabs_evs.vmp sign-pkg "$ELECTRON_DIR" 2>&1; then
if python3 -m castlabs_evs.vmp sign-pkg "$ELECTRON_DIR" "${EVS_AUTH[@]}" 2>&1; then
echo "[vmp] VMP signing successful — full DRM playback enabled"
# Re-fix symlinks in case signing modified the bundle
fix_framework_symlinks