mirror of
https://github.com/webinstall/webi-installers.git
synced 2026-02-14 17:49:53 +00:00
305 lines
7.6 KiB
JavaScript
305 lines
7.6 KiB
JavaScript
'use strict';
|
|
|
|
var InstallerServer = module.exports;
|
|
|
|
let Fs = require('fs/promises');
|
|
let Path = require('path');
|
|
|
|
let HostTargets = require('./build-classifier/host-targets.js');
|
|
let Builds = require('./builds.js');
|
|
let Installers = require('./installers.js');
|
|
|
|
InstallerServer.INSTALLERS_DIR = Path.join(__dirname, '..');
|
|
InstallerServer.serveInstaller = async function (
|
|
baseurl,
|
|
ua,
|
|
pkg,
|
|
tag,
|
|
ext,
|
|
formats,
|
|
libc,
|
|
) {
|
|
let unameAgent = ua;
|
|
let projectName = pkg;
|
|
let [rel, tmplParams] = await InstallerServer.helper({
|
|
unameAgent,
|
|
projectName,
|
|
tag,
|
|
formats,
|
|
libc,
|
|
});
|
|
Object.assign(tmplParams, {
|
|
baseurl,
|
|
});
|
|
|
|
var pkgdir = Path.join(InstallerServer.INSTALLERS_DIR, projectName);
|
|
if ('ps1' === ext) {
|
|
return Installers.renderPowerShell(pkgdir, rel, tmplParams);
|
|
}
|
|
return Installers.renderBash(pkgdir, rel, tmplParams);
|
|
};
|
|
|
|
// TODO put some of this in a middleware? or common function?
|
|
// TODO maybe move package/version/lts/channel detection into getReleases
|
|
InstallerServer.helper = async function ({
|
|
unameAgent,
|
|
projectName,
|
|
tag,
|
|
formats,
|
|
libc,
|
|
}) {
|
|
console.log(`dbg: Installer User-Agent: ${unameAgent}`);
|
|
|
|
let releaseTarget = toReleaseTarget(tag);
|
|
let hostFormats = formats;
|
|
let terms = unameAgent.split(/[\s\/]+/g);
|
|
let hostTarget = {};
|
|
try {
|
|
void HostTargets.termsToTarget(hostTarget, terms);
|
|
} catch (e) {
|
|
// if we can't guarantee the results...
|
|
// "in the face of ambiguity, refuse the temptation to guess"
|
|
throw e;
|
|
}
|
|
console.log(`dbg: Installer Host Target:`);
|
|
console.log(hostTarget);
|
|
|
|
if (!hostTarget.os) {
|
|
throw new Error(`OS could not be identified by User-Agent '${unameAgent}'`);
|
|
}
|
|
|
|
console.log(`dbg: Get Project Installer Type for '${projectName}':`);
|
|
let proj = await Builds.getProjectType(projectName);
|
|
if (proj.type === 'alias') {
|
|
console.log(`dbg: alias`, proj);
|
|
projectName = proj.detail;
|
|
proj = await Builds.getProjectType(projectName); // an alias should never resolve to an alias
|
|
}
|
|
console.log(`dbg: proj`, proj);
|
|
|
|
let validTypes = ['selfhosted', 'valid'];
|
|
if (!validTypes.includes(proj.type)) {
|
|
let msg = `'${projectName}' doesn't have an installer: '${proj.type}': '${proj.detail}'`;
|
|
let err = new Error(msg);
|
|
err.code = 'ENOENT';
|
|
throw err;
|
|
}
|
|
|
|
let tmplParams = {
|
|
pkg: projectName,
|
|
tag: tag,
|
|
os: hostTarget.os,
|
|
arch: hostTarget.arch,
|
|
libc: hostTarget.libc,
|
|
formats: hostFormats,
|
|
limit: 1,
|
|
};
|
|
Object.assign(tmplParams, releaseTarget);
|
|
console.log('tmplParams', tmplParams);
|
|
|
|
let errPackage = {
|
|
name: 'doesntexist.ext',
|
|
version: '0.0.0',
|
|
lts: '-',
|
|
channel: 'error',
|
|
date: '1970-01-01',
|
|
os: hostTarget.os || '-',
|
|
arch: hostTarget.arch || '-',
|
|
libc: hostTarget.libc || '-',
|
|
ext: 'err',
|
|
download: 'https://example.com/doesntexist.ext',
|
|
comment:
|
|
'No matches found. Could be bad or missing version info' +
|
|
',' +
|
|
"Check query parameters. Should be something like '/api/releases/{package}@{version}.tab?os={macos|linux|windows|-}&arch={amd64|x86|aarch64|arm64|armv7l|-}&libc={musl|gnu|msvc|libc|static}&limit=10'",
|
|
};
|
|
|
|
if (proj.type === 'selfhosted') {
|
|
return [errPackage, tmplParams];
|
|
}
|
|
|
|
let projInfo = await Builds.getPackage({
|
|
name: projectName,
|
|
date: new Date(),
|
|
});
|
|
let latestVersions = Builds.enumerateLatestVersions(projInfo);
|
|
//console.log('projInfo', projInfo);
|
|
|
|
let buildTargetInfo = {
|
|
triplets: projInfo.triplets,
|
|
oses: projInfo.oses,
|
|
arches: projInfo.arches,
|
|
libcs: projInfo.libcs,
|
|
formats: projInfo.formats,
|
|
latest: latestVersions.latest,
|
|
stable: latestVersions.stable,
|
|
};
|
|
|
|
// TODO .findMatchingPackages() should probably account for this
|
|
let hasOs = projInfo.oses.includes(hostTarget.os);
|
|
let maybePosix = !hasOs && hostTarget.os !== 'windows';
|
|
if (maybePosix) {
|
|
let posixes = ['posix_2017', 'posix_2024'];
|
|
for (let posixYear of posixes) {
|
|
let hasPosix = projInfo.oses.includes(posixYear);
|
|
if (hasPosix) {
|
|
hasOs = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!hasOs) {
|
|
hasOs = projInfo.oses.includes('ANYOS');
|
|
}
|
|
|
|
if (!hasOs) {
|
|
let pkg1 = Object.assign(buildTargetInfo, errPackage);
|
|
return [pkg1, tmplParams];
|
|
}
|
|
|
|
let targetRelease = Builds.findMatchingPackages(
|
|
projInfo,
|
|
hostTarget,
|
|
releaseTarget,
|
|
);
|
|
// { triplet: `${os}-${arch}-${libc}`, packages: targetPackages
|
|
// , latest: projInfo.versions[0], versions: matchInfo
|
|
// }
|
|
|
|
if (!targetRelease?.packages) {
|
|
let pkg1 = Object.assign(buildTargetInfo, errPackage);
|
|
return [pkg1, tmplParams];
|
|
}
|
|
|
|
let buildPkg = Builds.selectPackage(targetRelease.packages, hostFormats);
|
|
let ext = buildPkg.ext || '.exe';
|
|
if (ext.startsWith('.')) {
|
|
ext = ext.slice(1);
|
|
}
|
|
|
|
let version = targetRelease.version;
|
|
if (version.startsWith('v')) {
|
|
version = version.slice(1);
|
|
}
|
|
|
|
buildPkg = Object.assign(buildTargetInfo, buildPkg, { ext, version });
|
|
console.log('dbg: buildPkg', buildPkg);
|
|
console.log('dbg: tmplParams', tmplParams);
|
|
return [buildPkg, tmplParams];
|
|
};
|
|
|
|
let channelNames = [
|
|
'stable',
|
|
// 'hotfix',
|
|
'latest',
|
|
'rc',
|
|
'preview',
|
|
'pre',
|
|
'dev',
|
|
'beta',
|
|
'alpha',
|
|
];
|
|
|
|
function toReleaseTarget(tag) {
|
|
tag = tag.replace(/^v/, '');
|
|
|
|
let releaseTarget = {
|
|
channel: '',
|
|
lts: false,
|
|
version: '',
|
|
};
|
|
|
|
if (tag === 'lts') {
|
|
releaseTarget.lts = true;
|
|
releaseTarget.channel = 'stable';
|
|
} else if (channelNames.includes(tag)) {
|
|
releaseTarget.channel = tag;
|
|
} else {
|
|
releaseTarget.version = tag;
|
|
}
|
|
|
|
return releaseTarget;
|
|
}
|
|
|
|
var CURL_PIPE_PS1_BOOT = Path.join(__dirname, 'curl-pipe-bootstrap.tpl.ps1');
|
|
var CURL_PIPE_SH_BOOT = Path.join(__dirname, 'curl-pipe-bootstrap.tpl.sh');
|
|
var BAD_SH_RE = /[<>'"`$\\]/;
|
|
|
|
InstallerServer.getPosixCurlPipeBootstrap = async function ({
|
|
baseurl,
|
|
pkg,
|
|
ver,
|
|
}) {
|
|
let bootTxt = await Fs.readFile(CURL_PIPE_SH_BOOT, 'utf8');
|
|
|
|
var webiPkg = [pkg, ver].filter(Boolean).join('@');
|
|
var webiChecksum = await Installers.getWebiShChecksum();
|
|
var envReplacements = [
|
|
['WEBI_PKG', webiPkg],
|
|
['WEBI_HOST', baseurl],
|
|
['WEBI_CHECKSUM', webiChecksum],
|
|
];
|
|
|
|
for (let env of envReplacements) {
|
|
let name = env[0];
|
|
let value = env[1];
|
|
|
|
let envRe = new RegExp(
|
|
`^[ \\t]*#?[ \\t]*(export[ \\t])?[ \\t]*(${name})=.*`,
|
|
'm',
|
|
);
|
|
|
|
if (BAD_SH_RE.test(value)) {
|
|
throw new Error(`key '${name}' has invalid value '${value}'`);
|
|
}
|
|
|
|
bootTxt = bootTxt.replace(envRe, `$1$2='${value}'`);
|
|
}
|
|
// TODO init config here
|
|
//bootTxt.replace(/CHEATSHEET_URL/g, `${Config.cheatUrl}/${pkg}`);
|
|
|
|
return bootTxt;
|
|
};
|
|
|
|
InstallerServer.getPwshCurlPipeBootstrap = async function ({
|
|
baseurl,
|
|
pkg,
|
|
ver,
|
|
exename,
|
|
}) {
|
|
let bootTxt = await Fs.readFile(CURL_PIPE_PS1_BOOT, 'utf8');
|
|
|
|
var webiPkg = [pkg, ver].filter(Boolean).join('@');
|
|
//var webiChecksum = await InstallerServer.getWebiPs1Checksum();
|
|
var envReplacements = [
|
|
['Env:WEBI_PKG', webiPkg],
|
|
['Env:WEBI_HOST', baseurl],
|
|
//['Env:WEBI_CHECKSUM', webiChecksum],
|
|
['baseurl', baseurl],
|
|
['exename', exename],
|
|
['version', ver],
|
|
];
|
|
|
|
for (let env of envReplacements) {
|
|
let name = env[0];
|
|
let value = env[1];
|
|
|
|
if (BAD_SH_RE.test(value)) {
|
|
throw new Error(`key '${name}' has invalid value '${value}'`);
|
|
}
|
|
|
|
let tplRe = new RegExp(`{{ (${name}) }}`, 'g');
|
|
bootTxt = bootTxt.replace(tplRe, `${value}`);
|
|
|
|
let setRe = new RegExp(
|
|
`(#[ \\t]*)?(\\$${name})[ \\t]*=[ \\t]['"].*['"][ \\t]`,
|
|
'im',
|
|
);
|
|
bootTxt = bootTxt.replace(setRe, `$$${name} = '${value}'`);
|
|
}
|
|
// TODO init config here
|
|
//bootTxt.replace(/CHEATSHEET_URL/g, `${Config.cheatUrl}/${pkg}`);
|
|
|
|
return bootTxt;
|
|
};
|