1311 lines
34 KiB
JavaScript
1311 lines
34 KiB
JavaScript
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()));
|
|
}
|
|
});
|
|
};
|