const fs = require('fs'); const path = require('path'); const os = require('os'); const axios = require('axios/index'); const exec = require('child_process').exec; const execFile = require('child_process').execFile; const spawn = require('child_process').spawn; const Constants = require('./constants'); const RandomString = require('randomstring'); const IS_64BIT = process.arch.indexOf('64') >= 0; let vcRuntimeExists; const _vcRuntimeExists = () => { return new Promise((resolve, reject) => { if (os.platform() !== 'win32') { reject('Windows OS is not being used'); } else { if (vcRuntimeExists) { resolve(true); } else { const cmd = path.join(process.env.windir, 'system32', 'reg.exe'); const args = [ 'QUERY', IS_64BIT ? 'HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall' : 'HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall', ]; _execProcessGetOutput(cmd, null, args) .then((lines) => { const parseLine = (index) => { if (index < lines.length) { const line = lines[index]; if (line.startsWith('HKEY_LOCAL_MACHINE\\')) { let args2 = JSON.parse(JSON.stringify(args)); args2[1] = 'HKLM\\' + line.substr(19); args2.push('/v'); args2.push('DisplayName'); args2.push('/t'); args2.push('REG_SZ'); _execProcessGetOutput(cmd, null, args2) .then((lines) => { const value = lines[2].trim().substr(args2[3].length).trim().substr(6).trim(); if ( value.includes( IS_64BIT ? 'Microsoft Visual C++ 2015-2019 Redistributable (x64)' : 'Microsoft Visual C++ 2015-2019 Redistributable (x32)' ) ) { vcRuntimeExists = true; resolve(true); } else { parseLine(++index); } }) .catch(() => { parseLine(++index); }); } else { parseLine(++index); } } else { resolve(false); } }; parseLine(0); }) .catch((err) => { reject(err); }); } } }); }; // https://stackoverflow.com/questions/19531453/transform-file-directory-structure-into-tree-in-javascript const _createTreeNodes = (fileList) => { let tree = {}; const directorySort = (a, b) => { return !!a.directory === !!b.directory ? a.name.localeCompare(b.name) : a.directory ? -1 : 1; }; const addNode = (obj) => { let fullPath; const idx = obj.skylink.indexOf('/'); if (idx > -1) { fullPath = path.join(obj.directory, obj.skylink.substr(idx + 1)).replace(/\\/g, '/'); } else { fullPath = path.join(obj.directory, obj.filename).replace(/\\/g, '/'); } const pathParts = fullPath.replace(/^\/|\/$/g, '').split('/'); let ptr = tree; for (let i = 0; i < pathParts.length; i++) { const node = { directory: true, name: pathParts[i], }; if (i === pathParts.length - 1) { node.directory = false; node.path = fullPath; } ptr[pathParts[i]] = ptr[pathParts[i]] || node; ptr[pathParts[i]].children = ptr[pathParts[i]].children || {}; ptr = ptr[pathParts[i]].children; } }; const objectToArray = (node) => { Object.keys(node || {}).forEach((k) => { if (node[k].children) { objectToArray(node[k]); } }); if (node.children) { node.children = Object.values(node.children); node.children.forEach(objectToArray); node.children = node.children.sort(directorySort); } }; fileList.map(addNode); objectToArray(tree); return Object.values(tree).sort(directorySort); }; const _exportAllSkylinks = (version) => { return new Promise((resolve, reject) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd: repertoryExec.working, detached: true, shell: false, windowsHide: true, }; const args = _getDefaultRepertoryArgs('Skynet'); args.push('-ea'); let result = ''; const process = new spawn(repertoryExec.cmd, args, processOptions); process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d) => { result += d; }); process.stderr.on('data', (d) => { result += d; }); process.on('exit', (code) => { if (code === 0) { result = result.substr(result.indexOf('{')); resolve(JSON.parse(result)); } else { reject(new Error('Failed to import: ' + code + ':' + result)); } }); process.unref(); }); }; const _executeProcess = (command, working, args = []) => { return new Promise((resolve, reject) => { let processOptions = { detached: true, shell: false, }; if (working) { processOptions.cwd = working; } const process = new spawn(command, args, processOptions); const pid = process.pid; process.on('error', (err) => { reject(err, pid); }); process.on('exit', (code) => { resolve(code); }); process.unref(); }); }; const _execProcessGetOutput = (cmd, working, args) => { return new Promise((resolve, reject) => { let processOptions = { env: process.env, stdio: ['ignore', 'pipe', 'pipe'], }; if (working) { processOptions.cwd = working; } const proc = spawn(cmd, args, processOptions); let output = ''; proc.stdout.on('data', (data) => { output += data.toString(); }); proc.on('error', (err) => { reject(err); }); proc.on('exit', () => { const lines = output.replace(/\r\n/g, '\n').split('\n'); resolve(lines); }); proc.unref(); }); }; const _getDataDirectory = () => { return _resolvePath(Constants.DATA_LOCATIONS[os.platform()]); }; const _getRepertoryDirectory = () => { return _resolvePath(Constants.REPERTORY_LOCATIONS[os.platform()]); }; const _getDefaultRepertoryArgs = (provider, remote, s3) => { const providerLower = provider.toLowerCase(); const args = []; if (s3) { args.push('-s3'); args.push('-na'); args.push(provider.substr(2)); } else if (remote) { args.push('-rm'); args.push(provider.substr(6)); } else if ( Constants.PROVIDER_ARG[providerLower] && Constants.PROVIDER_ARG[providerLower].length > 0 ) { args.push(Constants.PROVIDER_ARG[providerLower]); } return args; }; const _getRepertoryExec = (version) => { return { cmd: os.platform() === 'win32' ? 'repertory.exe' : './repertory', working: path.join(_getDataDirectory(), version), }; }; const _removeDirectoryRecursively = (dir) => { if (fs.existsSync(dir)) { fs.readdirSync(dir).forEach((file) => { const curPath = path.join(dir, file); if (fs.lstatSync(curPath).isDirectory()) { module.exports.removeDirectoryRecursively(curPath); } else { fs.unlinkSync(curPath); } }); fs.rmdirSync(dir); } }; const _resolvePath = (str) => { if (os.platform() === 'win32') { return str.replace(/%([^%]+)%/g, (_, n) => { return process.env[n]; }); } else { return str.replace('~', os.homedir()); } }; const _tryParse = (j, def) => { try { return JSON.parse(j); } catch (e) { return def; } }; module.exports.checkDaemonVersion = (version, provider) => { return new Promise((resolve, reject) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd: repertoryExec.working, detached: true, shell: false, windowsHide: true, }; const args = _getDefaultRepertoryArgs(provider); args.push('-cv'); const process = new spawn(repertoryExec.cmd, args, processOptions); process.on('error', (err) => { reject(err); }); process.on('exit', (code) => { resolve(code); }); process.unref(); }); }; module.exports.cleanupOldReleases = (versionList) => { return new Promise((resolve, reject) => { try { if (versionList && versionList.length > 0) { const dataDir = _getDataDirectory(); const directoryList = fs .readdirSync(dataDir, { withFileTypes: true }) .filter((dirent) => dirent.isDirectory()) .map((dirent) => dirent); const removeList = directoryList .filter((dirent) => !versionList.includes(dirent.name)) .map((dirent) => dirent.name); for (const dir of removeList) { try { _removeDirectoryRecursively(path.join(dataDir, dir)); } catch (e) { console.log(e); } } resolve(); } } catch (e) { reject(e); console.log(e); } }); }; module.exports.createSignatureFiles = (signature, publicKey) => { const fileName1 = RandomString.generate({ length: 12, charset: 'alphabetic', }); const fileName2 = RandomString.generate({ length: 12, charset: 'alphabetic', }); const signatureFile = path.join(os.tmpdir(), fileName1 + '.sig'); const publicKeyFile = path.join(os.tmpdir(), fileName2 + '.pub'); const buffer = Buffer.from(signature, 'base64'); fs.writeFileSync(signatureFile, buffer); fs.writeFileSync(publicKeyFile, publicKey); return { PublicKeyFile: publicKeyFile, SignatureFile: signatureFile, }; }; module.exports.detectRepertoryMounts = (version, providerList) => { return new Promise((resolve, reject) => { let mountState = {}; const defaultData = {}; for (const provider of providerList) { defaultData[provider] = { Active: false, Location: '', PID: -1, }; } const grabStatus = (index) => { if (index >= providerList.length) { resolve(mountState); } else { const provider = providerList[index]; const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd: repertoryExec.working, detached: true, shell: false, windowsHide: true, }; const args = _getDefaultRepertoryArgs( provider, !Constants.PROVIDER_LIST.includes(provider) && provider.toLowerCase().startsWith('remote'), !Constants.PROVIDER_LIST.includes(provider) && provider.toLowerCase().startsWith('s3') ); args.push('-status'); const process = new spawn(repertoryExec.cmd, args, processOptions); let result = ''; process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d) => { result += d; }); process.on('exit', () => { mountState[provider] = _tryParse(result, defaultData)[provider] || defaultData; if ( mountState[provider].Active && (mountState[provider].Location === 'elevating' || mountState[provider].Location === '') ) { setTimeout(() => { grabStatus(index); }, 2000); } else { grabStatus(++index); } }); process.unref(); } }; grabStatus(0); }); }; module.exports.downloadFile = (url, destination, progressCallback, completeCallback) => { try { if (fs.existsSync(destination)) { fs.unlinkSync(destination); } } catch (e) { completeCallback(e); return; } axios .get(url, { responseType: 'stream', }) .then((response) => { try { const total = parseInt(response.headers['content-length'], 10); if (total === 0) { completeCallback(new Error('No data available for download')); } else { const stream = fs.createWriteStream(destination); let downloaded = 0; response.data.on('data', (chunk) => { stream.write(Buffer.from(chunk)); downloaded += chunk.length; if (progressCallback) { progressCallback(((downloaded / total) * 100.0).toFixed(2)); } }); response.data.on('end', () => { stream.end(() => { if (downloaded === 0) { completeCallback(new Error('Received 0 bytes')); } else if (downloaded !== total) { completeCallback(new Error('Received incorrect number of bytes')); } else { completeCallback(); } }); }); response.data.on('error', (error) => { stream.end(() => { completeCallback(error); }); }); } } catch (error) { completeCallback(error); } }) .catch((error) => { completeCallback(error); }); }; module.exports.executeAndWait = (command, ignoreResult) => { return new Promise((resolve, reject) => { const retryExecute = (count, lastError) => { if (++count <= 5) { exec(command, (error) => { if (error) { if (!ignoreResult && error.code === 1) { setTimeout(() => { retryExecute(count, error); }, 1000); } else { reject(error); } } else { resolve(); } }); } else { reject(lastError); } }; retryExecute(0); }); }; module.exports.executeAsync = (command, args = []) => { return new Promise((resolve, reject) => { const launchProcess = (count, timeout) => { let cmd = path.basename(command); const working = cmd.length === command.length ? null : command.substr(0, command.length - cmd.length); let processOptions = { detached: true, shell: false, }; if (working) { processOptions.cwd = working; if (os.platform() !== 'win32') { cmd = './' + cmd; } } const process = new spawn(cmd, args, processOptions); const pid = process.pid; process.on('error', (err) => { if (++count === 5) { reject(err, pid); } else { clearTimeout(timeout); setTimeout( () => launchProcess( count, setTimeout(() => resolve(), 3000) ), 1000 ); } }); process.on('exit', (code) => { if (code !== 0) { if (++count === 5) { reject(code, pid); } else { clearTimeout(timeout); setTimeout( () => launchProcess( count, setTimeout(() => resolve(), 3000) ), 1000 ); } } }); process.unref(); }; launchProcess( 0, setTimeout(() => resolve(), 3000) ); }); }; module.exports.executeScript = (script) => { return new Promise((resolve, reject) => { const processOptions = { detached: false, shell: true, windowsHide: true, }; const command = '/bin/sh'; const args = [script]; const process = new spawn(command, args, processOptions); let result = ''; process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d) => { result += d; }); process.on('exit', () => { resolve(result); }); process.unref(); }); }; module.exports.executeMount = (version, provider, remote, s3, location, exitCallback) => { return new Promise((resolve) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd: repertoryExec.working, detached: false, shell: os.platform() !== 'darwin', stdio: 'ignore', }; const args = _getDefaultRepertoryArgs(provider, remote, s3); if (os.platform() === 'linux' || os.platform() === 'darwin') { args.push('-o'); args.push('big_writes'); args.push('-f'); args.push('-nc'); } else if (os.platform() === 'win32') { args.push('-hidden'); } args.push(location); let process = new spawn(repertoryExec.cmd, args, processOptions); const pid = process.pid; const timeout = setTimeout(() => { resolve(pid); }, 3000); process.on('error', (err) => { clearTimeout(timeout); exitCallback(err, pid); }); process.on('exit', (code) => { clearTimeout(timeout); exitCallback(code, pid); }); }); }; module.exports.exportAllSkylinks = _exportAllSkylinks; module.exports.exportSkylinks = (version, paths) => { return new Promise((resolve, reject) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd: repertoryExec.working, detached: true, shell: false, windowsHide: true, }; const args = _getDefaultRepertoryArgs('Skynet'); args.push('-ex'); args.push(paths.join(',')); let result = ''; const process = new spawn(repertoryExec.cmd, args, processOptions); process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d) => { result += d; }); process.stderr.on('data', (d) => { result += d; }); process.on('exit', (code) => { if (code === 0) { result = result.substr(result.indexOf('{')); resolve(JSON.parse(result)); } else { reject(new Error('Failed to import: ' + code + ':' + result)); } }); process.unref(); }); }; module.exports.getConfig = (version, provider, remote, s3) => { return new Promise((resolve, reject) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd: repertoryExec.working, detached: true, shell: false, windowsHide: true, }; const args = _getDefaultRepertoryArgs(provider, remote, s3); args.push('-dc'); const process = new spawn(repertoryExec.cmd, args, processOptions); let result = ''; process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d) => { result += d; }); process.on('exit', () => { const lines = result.replace(/\r\n/g, '\n').split('\n'); const code = parseInt(lines[0], 10); if (code === 0) { lines.shift(); resolve({ Code: code, Data: JSON.parse(lines.join('\n')), }); } else { resolve(code); } }); process.unref(); }); }; module.exports.getConfigTemplate = (version, provider, remote, s3) => { return new Promise((resolve, reject) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd: repertoryExec.working, detached: true, shell: false, windowsHide: true, }; const args = _getDefaultRepertoryArgs(provider, remote, s3); args.push('-gt'); const process = new spawn(repertoryExec.cmd, args, processOptions); let result = ''; process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d) => { result += d; }); process.on('exit', () => { resolve(JSON.parse(result)); }); process.unref(); }); }; module.exports.getDataDirectory = _getDataDirectory; module.exports.getRepertoryDirectory = _getRepertoryDirectory; module.exports.getMissingDependencies = (dependencies) => { return new Promise((resolve, reject) => { if (!dependencies) { reject(new Error('Dependency list is invalid')); } let missing = []; if (os.platform() === 'win32') { let count = 0; const resolveIfComplete = () => { if (++count === dependencies.length) { resolve(missing); } }; const Registry = require('winreg'); const checkRegistry = (dep, index) => { if (index >= dep.registry.length) { if (dep.display === 'VC Runtime 2015-2019') { _vcRuntimeExists() .then((exists) => { if (!exists) { missing.push(dep); } resolveIfComplete(); }) .catch(() => { missing.push(dep); resolveIfComplete(); }); } else { missing.push(dep); resolveIfComplete(); } } else { let hive = null; const hiveName = dep.registry[index].split('\\')[0]; switch (hiveName) { case 'HKEY_CLASSES_ROOT': hive = Registry.HKCR; break; case 'HKEY_CURRENT_CONFIG': hive = Registry.HKCC; break; case 'HKEY_CURRENT_USER': hive = Registry.HKCU; break; case 'HKEY_LOCAL_MACHINE': hive = Registry.HKLM; break; case 'HKEY_USERS': hive = Registry.HKU; break; default: throw new Error('Invalid registry hive: ' + hiveName); } const key = dep.registry[index].substr(hiveName.length); const regKey = new Registry({ hive: hive, key: key }); regKey.valueExists('DisplayName', (err, exists) => { if (err || !exists) { regKey.valueExists('ProductName', (err, exists) => { if (err || !exists) { checkRegistry(dep, ++index); } else { resolveIfComplete(); } }); } else { resolveIfComplete(); } }); } }; for (const dependency of dependencies) { checkRegistry(dependency, 0); } } else { for (const dep of dependencies) { try { if (!(fs.lstatSync(dep.file).isFile() || fs.lstatSync(dep.file).isSymbolicLink())) { missing.push(dep); } } catch (e) { missing.push(dep); } } resolve(missing); } }); }; module.exports.grabSkynetFileTree = (version) => { return new Promise((resolve, reject) => { _exportAllSkylinks(version) .then((results) => { resolve([ { name: '/', directory: true, children: _createTreeNodes(results.success), }, ]); }) .catch((e) => { reject(e); }); }); }; module.exports.grabDirectoryItems = (path, version, provider, remote, s3) => { return new Promise((resolve, reject) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd: repertoryExec.working, detached: true, shell: false, windowsHide: true, }; const args = _getDefaultRepertoryArgs(provider, remote, s3); args.push('-gdi'); args.push(path); let result = ''; const process = new spawn(repertoryExec.cmd, args, processOptions); process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d) => { result += d; }); process.stderr.on('data', (d) => { result += d; }); process.on('exit', (code) => { if (code === 0) { result = result.substr(result.indexOf('{')); resolve(JSON.parse(result)); } else { reject(new Error('Failed to import: ' + code + ':' + result)); } }); process.unref(); }); }; module.exports.setPinned = (path, pinned, version, provider, remote, s3) => { return new Promise((resolve, reject) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd: repertoryExec.working, detached: true, shell: false, windowsHide: true, }; const args = _getDefaultRepertoryArgs(provider, remote, s3); args.push(pinned ? '-pf' : '-uf'); args.push(path); let result = ''; const process = new spawn(repertoryExec.cmd, args, processOptions); process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d) => { result += d; }); process.stderr.on('data', (d) => { result += d; }); process.on('exit', (code) => { if (code === 0) { resolve(JSON.parse(result).success); } else { reject(new Error('Failed to import: ' + code + ':' + result)); } }); process.unref(); }); }; module.exports.importSkylinks = (version, jsonArray) => { return new Promise((resolve, reject) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd: repertoryExec.working, detached: true, shell: false, windowsHide: true, }; const args = _getDefaultRepertoryArgs('Skynet'); args.push('-ij'); args.push(JSON.stringify(jsonArray)); let result = ''; const process = new spawn(repertoryExec.cmd, args, processOptions); process.on('error', (err) => { reject(err); }); process.stdout.on('data', (d) => { result += d; }); process.stderr.on('data', (d) => { result += d; }); process.on('exit', (code) => { if (code === 0) { result = result.substr(result.indexOf('{')); resolve(JSON.parse(result)); } else { reject(new Error('Failed to import: ' + code + ':' + result)); } }); process.unref(); }); }; // https://stackoverflow.com/questions/31645738/how-to-create-full-path-with-nodes-fs-mkdirsync module.exports.mkDirByPathSync = (targetDir, { isRelativeToScript = false } = {}) => { const sep = path.sep; const initDir = path.isAbsolute(targetDir) ? sep : ''; const baseDir = isRelativeToScript ? __dirname : '.'; return targetDir.split(sep).reduce((parentDir, childDir) => { const curDir = path.resolve(baseDir, parentDir, childDir); try { fs.mkdirSync(curDir); } catch (err) { if (err.code === 'EEXIST') { // curDir already exists! return curDir; } // To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on // Windows. if (err.code === 'ENOENT') { // Throw the original parentDir error on // curDir `ENOENT` failure. throw new Error(`EACCES: permission denied, mkdir '${parentDir}'`); } const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1; if (!caughtErr || (caughtErr && targetDir === curDir)) { throw err; // Throw if it's just the last created dir. } } return curDir; }, initDir); }; module.exports.performWindowsUninstall = (names) => { return new Promise((resolve, reject) => { if (os.platform() !== 'win32') { reject('Windows OS is not being used'); } else { const cmd = path.join(process.env.windir, 'system32', 'reg.exe'); const args = [ 'QUERY', IS_64BIT ? 'HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall' : 'HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall', ]; _execProcessGetOutput(cmd, null, args) .then((lines) => { const parseLine = (index) => { if (index < lines.length) { const line = lines[index]; if (line.startsWith('HKEY_LOCAL_MACHINE\\')) { let args2 = JSON.parse(JSON.stringify(args)); args2[1] = 'HKLM\\' + line.substr(19); args2.push('/v'); args2.push('DisplayName'); args2.push('/t'); args2.push('REG_SZ'); _execProcessGetOutput(cmd, null, args2) .then((lines) => { const value = lines[2].trim().substr(args2[3].length).trim().substr(6).trim(); if (names.includes(value)) { const items = line.split('\\'); const productCode = items[items.length - 1]; _executeProcess('msiexec.exe', null, ['/x', productCode, '/norestart']) .then((code) => { if (code === 0 || code === 3010 || code === 1641) { resolve(true); } else { reject('[' + value + '] uninstall failed: ' + code); } }) .catch((err) => { reject(err); }); } else { parseLine(++index); } }) .catch(() => { parseLine(++index); }); } else { parseLine(++index); } } else { resolve(false); } }; parseLine(0); }) .catch((err) => { reject(err); }); } }); }; module.exports.removeDirectoryRecursively = _removeDirectoryRecursively; module.exports.resolvePath = _resolvePath; module.exports.setConfigValue = (name, value, provider, remote, s3, version) => { return new Promise((resolve, reject) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd: repertoryExec.working, detached: true, shell: false, windowsHide: true, }; const args = _getDefaultRepertoryArgs(provider, remote, s3); args.push('-set'); args.push(name); args.push(value); const process = new spawn(repertoryExec.cmd, args, processOptions); process.on('error', (err) => { reject(err); }); process.on('exit', (code) => { if (code !== 0) { reject(new Error('Failed to set configuration value: ' + code)); } else { resolve(); } }); process.unref(); }); }; module.exports.testSkynetLogon = ( version, authURL, authUser, authPassword, agentString, apiKey ) => { return new Promise((resolve, reject) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd: repertoryExec.working, detached: true, shell: false, windowsHide: true, }; const args = _getDefaultRepertoryArgs('Skynet'); args.push('-tsa'); args.push(authURL); args.push(authUser); args.push(authPassword); args.push(agentString || ''); args.push(apiKey || ''); const process = new spawn(repertoryExec.cmd, args, processOptions); process.on('error', (err) => { reject(err); }); process.on('exit', (code) => { resolve(code === 0); }); process.unref(); }); }; module.exports.stopMountProcess = (version, provider, remote, s3) => { return new Promise((resolve, reject) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd: repertoryExec.working, detached: os.platform() === 'darwin', shell: os.platform() !== 'darwin', windowsHide: true, }; const args = _getDefaultRepertoryArgs(provider, remote, s3); args.push('-unmount'); const process = new spawn(repertoryExec.cmd, args, processOptions); const pid = process.pid; process.on('error', (err) => { reject(err); }); process.on('exit', (code) => { resolve({ PID: pid, Code: code, }); }); if (os.platform() === 'darwin') { process.unref(); } }); }; module.exports.stopMountProcessSync = (version, provider, remote, s3) => { const repertoryExec = _getRepertoryExec(version); const processOptions = { cwd: repertoryExec.working, detached: true, shell: os.platform() !== 'darwin', windowsHide: true, }; const args = _getDefaultRepertoryArgs(provider, remote, s3); args.push('-unmount'); const process = new spawn(repertoryExec.cmd, args, processOptions); process.unref(); }; module.exports.testRepertoryBinary = (version) => { return new Promise((resolve, reject) => { const repertoryExec = _getRepertoryExec(version); _executeProcess(repertoryExec.cmd, repertoryExec.working, ['-dc']) .then((code) => { if (code === 0) { resolve(); } else { reject(new Error('Invalid exit code: ' + code)); } }) .catch((error) => { reject(error); }); }); }; module.exports.verifyHash = (file, hash) => { return new Promise((resolve, reject) => { const platform = os.platform(); let command; let args; if (platform === 'darwin') { command = 'shasum'; args = ['-b', '-a', '256', file]; } else if (platform === 'linux') { command = 'sha256sum'; args = ['-b', file, '-z']; } else { reject(new Error('Platform not supported: ' + os.platform())); } if (command) { execFile(command, args, (err, stdout) => { if (err) { reject(err); } else { const hash2 = stdout.split(' ')[0].trim().toLowerCase(); if (hash2 === hash.toLowerCase()) { resolve(hash2); } else { reject(new Error('Checksum failed for file')); } } }); } }); }; module.exports.verifySignature = (file, signatureFile, publicKeyFile) => { return new Promise((resolve, reject) => { const executeVerify = (openssl) => { execFile( openssl, ['dgst', '-sha256', '-verify', publicKeyFile, '-signature', signatureFile, file], (err, stdout) => { if (err) { reject(err); } else { resolve(stdout); } } ); }; if (os.platform() === 'win32') { const Registry = require('winreg'); const regKey = new Registry({ hive: Registry.HKLM, key: IS_64BIT ? 'SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1' : 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (32-bit)_is1', }); regKey.valueExists('InstallLocation', (err, exists) => { if (err) { reject(err); } else if (exists) { regKey.get('InstallLocation', (err, item) => { if (err) { reject(err); } else { executeVerify(path.join(item.value(), 'bin', 'openssl.exe')); } }); } else { reject(new Error("Failed to locate 'openssl.exe'")); } }); } else if (os.platform() === 'linux') { executeVerify('openssl'); } else { reject(new Error('Platform not supported: ' + os.platform())); } }); };