UNPKG

mct1-server

Version:

A Minecraft for Type 1 Diabetes Server

332 lines (301 loc) 11 kB
/** * This file contains code shamelessly, and grateful, cribbed from Medium Phantomjs installer. It is licensed under Apache 2.0. */ const path = require('path') const request = require('request') const os = require('os') const url = require('url') const requestProgress = require('request-progress') const progress = require('progress') const extractZip = require('extract-zip') const rmrf = require('rimraf') const util = require('util') const fs = require('fs-extra') const cmp = require('semver-compare') module.exports = { getWorlds, getLocalWorldsMetadata, mct1WorldsExistLocally, worldUpdateAvailable, } const { worldSpec, mct1WorldDir: targetPath } = require('./util') const mdFile = `${targetPath}/metadata.json` function getWorlds() { return Promise.resolve() .then(downloadMCT1Worlds) .then(extractDownload) .then(copyIntoPlace) } function mct1WorldsExistLocally() { return fs.existsSync(targetPath) } function getLocalWorldsMetadata() { return Promise.resolve().then(() => { if (fs.existsSync(mdFile)) { return fs.readJson(mdFile) } else { return { version: '0.0.0' } } }) } function worldUpdateAvailable() { return Promise.resolve() .then(getLocalWorldsMetadata) .then(({ version }) => cmp(worldSpec.version, version) === 1 ? worldSpec.version : undefined ) } function downloadMCT1Worlds() { return new Promise(resolve => { const tmpPath = findSuitableTempDirectory() const fileName = worldSpec.downloadUrl.split('/').pop() const downloadedFile = path.join(tmpPath, fileName) worldUpdateAvailable().then(updateAvailable => { if (fs.existsSync(downloadedFile)) { if (!updateAvailable) { console.log('Worlds already downloaded as', downloadedFile) return resolve(downloadedFile) } } // Start the install. console.log('Downloading MCT1 worlds') console.log('Downloading', worldSpec.downloadUrl) console.log('Saving to', downloadedFile) resolve(requestWorldZip(getRequestOptions(), downloadedFile)) }) }) } function getRequestOptions() { const strictSSL = !!process.env.npm_config_strict_ssl if (process.version == 'v0.10.34') { console.log( 'Node v0.10.34 detected, turning off strict ssl due to https://github.com/joyent/node/issues/8894' ) strictSSL = false } const options = { uri: worldSpec.downloadUrl, encoding: null, // Get response as a buffer followRedirect: true, // The default download path redirects to a CDN URL. headers: {}, strictSSL: strictSSL, } const proxyUrl = process.env.npm_config_https_proxy || process.env.npm_config_http_proxy || process.env.npm_config_proxy if (proxyUrl) { // Print using proxy var proxy = url.parse(proxyUrl) if (proxy.auth) { // Mask password proxy.auth = proxy.auth.replace(/:.*$/, ':******') } console.log('Using proxy ' + url.format(proxy)) // Enable proxy options.proxy = proxyUrl } // Use the user-agent string from the npm config options.headers['User-Agent'] = process.env.npm_config_user_agent // Use certificate authority settings from npm var ca = process.env.npm_config_ca if (!ca && process.env.npm_config_cafile) { try { ca = fs .readFileSync(process.env.npm_config_cafile, { encoding: 'utf8', }) .split(/\n(?=-----BEGIN CERTIFICATE-----)/g) // Comments at the beginning of the file result in the first // item not containing a certificate - in this case the // download will fail if (ca.length > 0 && !/-----BEGIN CERTIFICATE-----/.test(ca[0])) { ca.shift() } } catch (e) { console.error( 'Could not read cafile', process.env.npm_config_cafile, e ) } } if (ca) { console.log('Using npmconf ca') options.agentOptions = { ca: ca, } options.ca = ca } return options } function extractDownload(filePath) { return new Promise((resolve, reject) => { // extract to a unique directory in case multiple processes are // installing and extracting at once const extractedPath = filePath + '-extract-' + Date.now() var options = { cwd: extractedPath } fs.mkdirsSync(extractedPath, '0777') // Make double sure we have 0777 permissions; some operating systems // default umask does not allow write by default. fs.chmodSync(extractedPath, '0777') if (filePath.substr(-4) === '.zip') { console.log('Extracting zip contents') extractZip(path.resolve(filePath), { dir: extractedPath }, function( err ) { if (err) { console.error('Error extracting zip') reject(err) } else { resolve(extractedPath) } }) } }) } function copyIntoPlace(extractedPath) { const rm = util.promisify(rmrf) console.log('Removing', targetPath) return rm(targetPath) .then(() => { const move = util.promisify(fs.move) // Look for the extracted directory, so we can rename it. return move(extractedPath, targetPath, { overwrite: true, }) .catch(error => { console.log( 'Error copying ' + extractedPath + ' to ' + targetPath ) console.log(error) exit(1) }) .then(() => fs.writeJson(`${targetPath}/metadata.json`, { version: worldSpec.version, }) ) }) .then(() => { console.log('Copied MCT1 worlds to ' + targetPath) }) // console.log('Could not find extracted file', files) // throw new Error('Could not find extracted file') } function requestWorldZip(requestOptions, filePath) { return new Promise(resolve => { const writePath = filePath + '-download-' + Date.now() console.log('Receiving...') var bar = null requestProgress( request(requestOptions, function(error, response, body) { console.log('') if (!error && response.statusCode === 200) { fs.writeFileSync(writePath, body) console.log( 'Received ' + Math.floor(body.length / 1024) + 'K total.' ) fs.renameSync(writePath, filePath) resolve(filePath) } else if (response) { console.error( 'Error requesting archive.\n' + 'Status: ' + response.statusCode + '\n' + 'Request options: ' + JSON.stringify(requestOptions, null, 2) + '\n' + 'Response headers: ' + JSON.stringify(response.headers, null, 2) + '\n' + 'Make sure your network and proxy settings are correct.\n\n' + 'If you continue to have issues, please report this full log at ' + 'https://github.com/Magikcraft/mct1-server/issues' ) exit(1) } else { handleRequestError(error) } }) ) .on('progress', function(state) { try { if (!bar) { bar = new progress(' [:bar] :percent', { total: state.size.total, width: 40, }) } bar.curr = state.size.transferred bar.tick() } catch (e) { // It doesn't really matter if the progress bar doesn't update. } }) .on('error', handleRequestError) }) } function handleRequestError(error) { if ( error && error.stack && error.stack.indexOf('SELF_SIGNED_CERT_IN_CHAIN') != -1 ) { console.error( 'Error making request, SELF_SIGNED_CERT_IN_CHAIN. ' + 'Please read https://github.com/Medium/phantomjs#i-am-behind-a-corporate-proxy-that-uses-self-signed-ssl-certificates-to-intercept-encrypted-traffic' ) exit(1) } else if (error) { console.error( 'Error making request.\n' + error.stack + '\n\n' + 'Please report this full log at https://github.com/Magikcraft/mct1-server/issues' ) exit(1) } else { console.error( 'Something unexpected happened, please report this full ' + 'log at https://github.com/Magikcraft/mct1-server/issues' ) exit(1) } } function findSuitableTempDirectory() { var now = Date.now() var candidateTmpDirs = [ process.env.npm_config_tmp, os.tmpdir(), path.join(process.cwd(), 'tmp'), ] for (var i = 0; i < candidateTmpDirs.length; i++) { var candidatePath = candidateTmpDirs[i] if (!candidatePath) continue try { candidatePath = path.join(path.resolve(candidatePath), 'mct1') fs.mkdirsSync(candidatePath, '0777') // Make double sure we have 0777 permissions; some operating systems // default umask does not allow write by default. fs.chmodSync(candidatePath, '0777') var testFile = path.join(candidatePath, now + '.tmp') fs.writeFileSync(testFile, 'test') fs.unlinkSync(testFile) return candidatePath } catch (e) { console.log(candidatePath, 'is not writable:', e.message) } } console.error( 'Can not find a writable tmp directory, please report issue ' + 'on https://github.com/Magikcraft/mct1-server/issues with as much ' + 'information as possible.' ) exit(1) }