diff --git a/_webi/bootstrap.sh b/_webi/bootstrap.sh index cc7fe06..aac8b9f 100644 --- a/_webi/bootstrap.sh +++ b/_webi/bootstrap.sh @@ -92,8 +92,13 @@ __webi_main() { export WEBI_WGET="\$(command -v wget)" set -e + my_libc='' + if ldd /bin/ls 2> /dev/null | grep -q 'musl' 2> /dev/null; then + my_libc=' musl-native' + fi + export WEBI_HOST="\${WEBI_HOST:-https://webinstall.dev}" - export WEBI_UA="\$(uname -s)/\$(uname -r) \$(uname -m)/unknown" + export WEBI_UA="\$(uname -s)/\$(uname -r) \$(uname -m)/unknown\${my_libc}" webinstall() { diff --git a/_webi/normalize.js b/_webi/normalize.js index 81945d6..1a75421 100644 --- a/_webi/normalize.js +++ b/_webi/normalize.js @@ -85,9 +85,11 @@ function normalize(all) { } // Hacky-doo for musl // TODO some sort of glibc vs musl tag? - if (!rel._musl) { - if (/(\b|\.|_|-)(musl)(\b|\.|_|-)/.test(rel.download)) { - rel._musl = true; + if (!rel._musl_native) { + if (!rel._musl) { + if (/(\b|\.|_|-)(musl)(\b|\.|_|-)/.test(rel.download)) { + rel._musl = true; + } } } supported.oses[rel.os] = true; diff --git a/_webi/serve-installer.js b/_webi/serve-installer.js index 2d914f1..6aa3bb8 100644 --- a/_webi/serve-installer.js +++ b/_webi/serve-installer.js @@ -47,12 +47,14 @@ async function serveInstaller(baseurl, ua, pkg, tag, ext, formats) { // TODO maybe move package/version/lts/channel detection into getReleases var myOs = uaDetect.os(ua); var myArch = uaDetect.arch(ua); + var myLibc = uaDetect.libc(ua); let cfg = await packages.get(pkg); let rels = await getReleases({ pkg: cfg.alias || pkg, ver, os: myOs, arch: myArch, + libc: myLibc, lts, channel, formats, diff --git a/_webi/template.sh b/_webi/template.sh index cfbbafa..614f2c1 100644 --- a/_webi/template.sh +++ b/_webi/template.sh @@ -6,6 +6,11 @@ __bootstrap_webi() { set -u #set -x + my_libc='' + if ldd /bin/ls 2> /dev/null | grep -q 'musl' 2> /dev/null; then + my_libc=' musl-native' + fi + #WEBI_PKG= #PKG_NAME= # TODO should this be BASEURL instead? @@ -30,7 +35,7 @@ __bootstrap_webi() { #PKG_OSES= #PKG_ARCHES= #PKG_FORMATS= - WEBI_UA="$(uname -s)/$(uname -r) $(uname -m)/unknown" + WEBI_UA="$(uname -s)/$(uname -r) $(uname -m)/unknown${my_libc}" WEBI_PKG_DOWNLOAD="" WEBI_DOWNLOAD_DIR="${HOME}/Downloads" if command -v xdg-user-dir > /dev/null; then @@ -168,7 +173,11 @@ __bootstrap_webi() { echo >&2 " '$PKG_NAME' is available for '$PKG_OSES' on '$PKG_ARCHES' as one of '$PKG_FORMATS'" echo >&2 " (check that the package name and version are correct)" echo >&2 "" - echo >&2 " Double check at $(echo "$WEBI_RELEASES" | sed 's:\?.*::')" + my_release_url="$( + echo "$WEBI_RELEASES" | + sed 's:\?.*::' + )" + echo >&2 " Double check at ${my_release_url}" echo >&2 "" exit 1 fi diff --git a/_webi/transform-releases.js b/_webi/transform-releases.js index c7f075f..590ef16 100644 --- a/_webi/transform-releases.js +++ b/_webi/transform-releases.js @@ -2,6 +2,8 @@ var path = require('path'); var Releases = require('./releases.js'); +var uaDetect = require('./ua-detect.js'); + var cache = {}; //var staleAge = 5 * 1000; //var expiredAge = 15 * 1000; @@ -52,6 +54,14 @@ function createFormatsSorter(formats) { return 1; } + // Hacky-doo for musl-native: prefer non-musl + if (a._musl_native && !b._musl_native) { + return 1; + } + if (!a._musl_native && b._musl_native) { + return -1; + } + // Hacky-doo for linux: prefer musl if (a._musl && !b._musl) { return -1; @@ -161,7 +171,7 @@ async function getCachedReleases(pkg) { async function filterReleases( all, - { ver, os, arch, lts, channel, formats, limit }, + { ver, os, arch, libc, lts, channel, formats, limit }, ) { // When multiple formats are downloadable (i.e. .zip and .pkg) // sort the most compatible format first @@ -170,27 +180,62 @@ async function filterReleases( var sortByVerExt = createFormatsSorter(rformats); var reVer = new RegExp('^' + ver + '\\b'); - var sortedRels = all.releases - .filter(function (rel) { - if ( - (os && rel.os !== os) || - // Hacky-doo for linux musl - (arch && rel.arch !== arch) || - (lts && !rel.lts) || - (channel && rel.channel !== channel) || - // to match 'tar.gz' and 'tar.xz' with just 'tar' - (formats.length && - !formats.some(function (ext) { - return rel.ext.match(ext); - })) || - (ver && !rel.version.match(reVer)) - ) { + function selectMatches(rel) { + if (os) { + if (rel.os !== os) { return false; } - return true; - }) - .sort(sortByVerExt); + } + + if (arch) { + if (rel.arch !== arch) { + return false; + } + } + + // Hacky-doo for linux musl + if (libc === uaDetect.MUSL_NATIVE) { + if (!rel._musl && !rel._musl_native) { + return false; + } + } else if (rel._musl_native) { + return false; + } + + if (lts) { + if (!rel.lts) { + return false; + } + } + + if (channel) { + if (rel.channel !== channel) { + return false; + } + } + + // to match 'tar.gz' and 'tar.xz' with just 'tar' + function hasExt(ext) { + return rel.ext.match(ext); + } + if (formats.length) { + if (!formats.some(hasExt)) { + return false; + } + } + + if (ver) { + if (!rel.version.match(reVer)) { + return false; + } + } + + return true; + } + + var sortedRels = all.releases.filter(selectMatches).sort(sortByVerExt); //console.log(sortedRels.slice(0, 4)); + return sortedRels.slice(0, limit || 1000); } @@ -200,6 +245,7 @@ module.exports = function getReleases({ ver, os, arch, + libc, lts, channel, formats, @@ -213,6 +259,7 @@ module.exports = function getReleases({ ver, os, arch, + libc, lts, channel, formats, @@ -236,6 +283,7 @@ module.exports = function getReleases({ ver, os, arch: 'amd64', + libc, lts, channel, formats, @@ -249,6 +297,7 @@ module.exports = function getReleases({ ver, os, arch: 'amd64', + libc, lts, channel, formats, @@ -263,6 +312,7 @@ module.exports = function getReleases({ ver, os, arch: 'arm64', + libc, lts, channel, formats, @@ -278,6 +328,7 @@ module.exports = function getReleases({ ver, os, arch: 'armv7l', + libc, lts, channel, formats, @@ -292,6 +343,7 @@ module.exports = function getReleases({ ver, os, arch: 'armv6l', + libc, lts, channel, formats, @@ -308,6 +360,7 @@ module.exports = function getReleases({ os: os || '-', arch: arch || '-', _musl: undefined, + _musl_native: undefined, ext: 'err', download: 'https://example.com/doesntexist.ext', comment: @@ -335,6 +388,7 @@ if (require.main === module) { os: 'macos', arch: 'amd64', lts: true, + libc: '', channel: 'stable', formats: ['tar', 'exe', 'zip', 'xz', 'dmg', 'pkg'], limit: 10, diff --git a/_webi/ua-detect.js b/_webi/ua-detect.js index ba9b8a1..7a3930d 100644 --- a/_webi/ua-detect.js +++ b/_webi/ua-detect.js @@ -2,6 +2,10 @@ var uaDetect = module.exports; +const MUSL_NATIVE = 'musl-native'; + +uaDetect.MUSL_NATIVE = MUSL_NATIVE; + function getRequest(req) { var ua = req.headers['user-agent'] || ''; var os = req.query.os; @@ -56,10 +60,15 @@ function getArch(ua) { return '-'; } - // quick hack for Apple Silicon M1 + // Quick hack for Apple Silicon M1 + // + // Note: we now use `uname -srm` which does not have the native arch + // info included with `uname -v` and `uname -a`. + // // Native: Darwin boomer.local 20.2.0 Darwin Kernel Version 20.2.0: Wed Dec 2 20:40:21 PST 2020; root:xnu-7195.60.75~1/RELEASE_ARM64_T8101 arm64 // Resetta: Darwin boomer.local 20.2.0 Darwin Kernel Version 20.2.0: Wed Dec 2 20:40:21 PST 2020; root:xnu-7195.60.75~1/RELEASE_ARM64_T8101 x86_64 ua = ua.replace(/xnu-.*RELEASE_[^\s]*/, ''); + if (/aarch64|arm64|arm8|armv8/i.test(ua)) { return 'arm64'; } else if (/aarch|arm7|armv7/i.test(ua)) { @@ -84,6 +93,27 @@ function getArch(ua) { } } +function getLibc(ua) { + if ('-' === ua) { + return '-'; + } + + // Use native 'libc' information, if provided + // + // Generally, we prefer 'musl' builds because they DO work on glibc systems (Ubuntu), + // but 'glibc' builds will NOT work on musl systems (Alpine / Docker). + // + // However, there are a few instances (ex: Node.js), where the 'musl' builds + // DO NOT work on glibc systems. + if (ua.match(MUSL_NATIVE)) { + return MUSL_NATIVE; + } + + // TODO handle explicit invalid different + return ''; +} + uaDetect.os = getOs; uaDetect.arch = getArch; +uaDetect.libc = getLibc; uaDetect.request = getRequest;