diff --git a/migrations/20191029223928-settings-default-sysinfo.js b/migrations/20191029223928-settings-default-sysinfo.js new file mode 100644 index 000000000..c55edd128 --- /dev/null +++ b/migrations/20191029223928-settings-default-sysinfo.js @@ -0,0 +1,25 @@ +'use strict'; + +var fs = require('fs'); + +exports.up = function(db, callback) { + if (!fs.existsSync('/etc/cloudron/PROVIDER')) { + console.log('Unable to locate PROVIDER'); + return callback(); + } + + const provider = fs.readFileSync('/etc/cloudron/PROVIDER', 'utf8'); + if (provider === 'ec2' || provider === 'lightsail' || provider === 'ami') { + sysinfoConfig = { provider: 'ec2' }; + } else if (provider === 'scaleway') { + sysinfoConfig = { provider: 'scaleway' }; + } else { + sysinfoConfig = { provider: 'generic' }; + } + + db.runSql('REPLACE INTO settings (name, value) VALUES(?, ?)', [ 'sysinfo_config', JSON.stringify(sysinfoConfig) ], callback); +}; + +exports.down = function(db, callback) { + callback(); +}; diff --git a/src/appstore.js b/src/appstore.js index 30096eacc..e674d7558 100644 --- a/src/appstore.js +++ b/src/appstore.js @@ -38,7 +38,6 @@ var apps = require('./apps.js'), semver = require('semver'), settings = require('./settings.js'), superagent = require('superagent'), - sysinfo = require('./sysinfo.js'), users = require('./users.js'), util = require('util'); @@ -246,12 +245,13 @@ function sendAliveStatus(callback) { appAutoupdatePattern: allSettings[settings.APP_AUTOUPDATE_PATTERN_KEY], boxAutoupdatePattern: allSettings[settings.BOX_AUTOUPDATE_PATTERN_KEY], timeZone: allSettings[settings.TIME_ZONE_KEY], + sysinfoProvider: allSettings[settings.SYSINFO_CONFIG_KEY].provider }; var data = { version: constants.VERSION, adminFqdn: settings.adminFqdn(), - provider: sysinfo.provider(), + provider: settings.provider(), backendSettings: backendSettings, machine: { cpus: os.cpus(), diff --git a/src/apptask.js b/src/apptask.js index c74bdfda9..6339a1975 100644 --- a/src/apptask.js +++ b/src/apptask.js @@ -350,7 +350,7 @@ function registerSubdomains(app, overwrite, callback) { assert.strictEqual(typeof overwrite, 'boolean'); assert.strictEqual(typeof callback, 'function'); - sysinfo.getPublicIp(function (error, ip) { + sysinfo.getServerIp(function (error, ip) { if (error) return callback(error); const allDomains = [ { subdomain: app.location, domain: app.domain }].concat(app.alternateDomains); @@ -396,7 +396,7 @@ function unregisterSubdomains(app, allDomains, callback) { assert(Array.isArray(allDomains)); assert.strictEqual(typeof callback, 'function'); - sysinfo.getPublicIp(function (error, ip) { + sysinfo.getServerIp(function (error, ip) { if (error) return callback(error); async.eachSeries(allDomains, function (domain, iteratorDone) { @@ -430,7 +430,7 @@ function waitForDnsPropagation(app, callback) { return callback(null); } - sysinfo.getPublicIp(function (error, ip) { + sysinfo.getServerIp(function (error, ip) { if (error) return callback(new BoxError(BoxError.NETWORK_ERROR, `Error getting public IP: ${error.message}`)); domains.waitForDnsRecord(app.location, app.domain, 'A', ip, { interval: 5000, times: 240 }, function (error) { diff --git a/src/cloudron.js b/src/cloudron.js index 77f69759a..c11cb52fa 100644 --- a/src/cloudron.js +++ b/src/cloudron.js @@ -47,7 +47,6 @@ var apps = require('./apps.js'), shell = require('./shell.js'), spawn = require('child_process').spawn, split = require('split'), - sysinfo = require('./sysinfo.js'), tasks = require('./tasks.js'), users = require('./users.js'); @@ -136,7 +135,7 @@ function getConfig(callback) { version: constants.VERSION, isDemo: settings.isDemo(), memory: os.totalmem(), - provider: sysinfo.provider(), + provider: settings.provider(), cloudronName: allSettings[settings.CLOUDRON_NAME_KEY], uiSpec: custom.uiSpec() }); diff --git a/src/dns/namecheap.js b/src/dns/namecheap.js index 595aea08b..ce6cbb6d6 100644 --- a/src/dns/namecheap.js +++ b/src/dns/namecheap.js @@ -37,7 +37,7 @@ function getQuery(dnsConfig, callback) { assert.strictEqual(typeof dnsConfig, 'object'); assert.strictEqual(typeof callback, 'function'); - sysinfo.getPublicIp(function (error, ip) { + sysinfo.getServerIp(function (error, ip) { if (error) return callback(error); callback(null, { diff --git a/src/dns/wildcard.js b/src/dns/wildcard.js index 44c95c3c9..fc1e5058c 100644 --- a/src/dns/wildcard.js +++ b/src/dns/wildcard.js @@ -89,7 +89,7 @@ function verifyDnsConfig(domainObject, callback) { if (error && error.code === 'ENOTFOUND') return callback(new BoxError(BoxError.BAD_FIELD, `Unable to resolve ${fqdn}`, { field: 'nameservers' })); if (error || !result) return callback(new BoxError(BoxError.BAD_FIELD, error ? error.message : `Unable to resolve ${fqdn}`, { field: 'nameservers' })); - sysinfo.getPublicIp(function (error, ip) { + sysinfo.getServerIp(function (error, ip) { if (error) return callback(new BoxError(BoxError.EXTERNAL_ERROR, `Failed to detect IP of this server: ${error.message}`)); if (result.length !== 1 || ip !== result[0]) return callback(new BoxError(BoxError.EXTERNAL_ERROR, `Domain resolves to ${JSON.stringify(result)} instead of ${ip}`)); diff --git a/src/domains.js b/src/domains.js index a0dbdc47d..fe743e614 100644 --- a/src/domains.js +++ b/src/domains.js @@ -371,7 +371,7 @@ function checkDnsRecords(location, domain, callback) { getDnsRecords(location, domain, 'A', function (error, values) { if (error) return callback(error); - sysinfo.getPublicIp(function (error, ip) { + sysinfo.getServerIp(function (error, ip) { if (error) return callback(error); if (values.length === 0) return callback(null, { needsOverwrite: false }); // does not exist @@ -473,7 +473,7 @@ function prepareDashboardDomain(domain, auditSource, progressCallback, callback) const adminFqdn = fqdn(constants.ADMIN_LOCATION, domainObject); - sysinfo.getPublicIp(function (error, ip) { + sysinfo.getServerIp(function (error, ip) { if (error) return callback(error); async.series([ diff --git a/src/dyndns.js b/src/dyndns.js index c3c8a5e8c..c4cc73f89 100644 --- a/src/dyndns.js +++ b/src/dyndns.js @@ -21,7 +21,7 @@ function sync(auditSource, callback) { assert.strictEqual(typeof auditSource, 'object'); assert.strictEqual(typeof callback, 'function'); - sysinfo.getPublicIp(function (error, ip) { + sysinfo.getServerIp(function (error, ip) { if (error) return callback(error); let info = safe.JSON.parse(safe.fs.readFileSync(paths.DYNDNS_INFO_FILE, 'utf8')) || { ip: null }; diff --git a/src/mail.js b/src/mail.js index 1e16fbef9..c35772e56 100644 --- a/src/mail.js +++ b/src/mail.js @@ -275,7 +275,7 @@ function checkMx(domain, mailFqdn, callback) { dns.resolve(mxRecords[0].exchange, 'A', DNS_OPTIONS, function (error, mxIps) { if (error || mxIps.length !== 1) return callback(null, mx); - sysinfo.getPublicIp(function (error, ip) { + sysinfo.getServerIp(function (error, ip) { if (error) return callback(null, mx); mx.status = mxIps[0] === ip; @@ -332,7 +332,7 @@ function checkPtr(mailFqdn, callback) { status: false }; - sysinfo.getPublicIp(function (error, ip) { + sysinfo.getServerIp(function (error, ip) { if (error) return callback(error, ptr); ptr.domain = ip.split('.').reverse().join('.') + '.in-addr.arpa'; @@ -415,7 +415,7 @@ const RBL_LIST = [ function checkRblStatus(domain, callback) { assert.strictEqual(typeof callback, 'function'); - sysinfo.getPublicIp(function (error, ip) { + sysinfo.getServerIp(function (error, ip) { if (error) return callback(error, ip); var flippedIp = ip.split('.').reverse().join('.'); diff --git a/src/provision.js b/src/provision.js index e755c7c47..9e5884d50 100644 --- a/src/provision.js +++ b/src/provision.js @@ -27,7 +27,6 @@ var appstore = require('./appstore.js'), semver = require('semver'), settings = require('./settings.js'), superagent = require('superagent'), - sysinfo = require('./sysinfo.js'), users = require('./users.js'), tld = require('tldjs'), _ = require('underscore'); @@ -264,7 +263,7 @@ function getStatus(callback) { callback(null, _.extend({ version: constants.VERSION, apiServerOrigin: settings.apiServerOrigin(), // used by CaaS tool - provider: sysinfo.provider(), + provider: settings.provider(), cloudronName: cloudronName, adminFqdn: settings.adminDomain() ? settings.adminFqdn() : null, activated: activated, diff --git a/src/routes/provision.js b/src/routes/provision.js index 36d09fc9b..3609b4f3d 100644 --- a/src/routes/provision.js +++ b/src/routes/provision.js @@ -15,13 +15,13 @@ var assert = require('assert'), HttpError = require('connect-lastmile').HttpError, HttpSuccess = require('connect-lastmile').HttpSuccess, provision = require('../provision.js'), - sysinfo = require('../sysinfo.js'), + settings = require('../settings.js'), superagent = require('superagent'); function providerTokenAuth(req, res, next) { assert.strictEqual(typeof req.body, 'object'); - if (sysinfo.provider() === 'ami') { + if (settings.provider() === 'ami') { if (typeof req.body.providerToken !== 'string' || !req.body.providerToken) return next(new HttpError(400, 'providerToken must be a non empty string')); superagent.get('http://169.254.169.254/latest/meta-data/instance-id').timeout(30 * 1000).end(function (error, result) { diff --git a/src/settings.js b/src/settings.js index 9d2b34585..dd67e0d04 100644 --- a/src/settings.js +++ b/src/settings.js @@ -43,6 +43,11 @@ exports = module.exports = { getCloudronToken: getCloudronToken, setCloudronToken: setCloudronToken, + getSysinfoConfig: getSysinfoConfig, + setSysinfoConfig: setSysinfoConfig, + + provider: provider, + getAll: getAll, initCache: initCache, @@ -69,6 +74,7 @@ exports = module.exports = { PLATFORM_CONFIG_KEY: 'platform_config', EXTERNAL_LDAP_KEY: 'external_ldap_config', REGISTRY_CONFIG_KEY: 'registry_config', + SYSINFO_CONFIG_KEY: 'sysinfo_config', // strings APP_AUTOUPDATE_PATTERN_KEY: 'app_autoupdate_pattern', @@ -133,6 +139,9 @@ let gDefaults = (function () { provider: 'noop' }; result[exports.REGISTRY_CONFIG_KEY] = {}; + result[exports.SYSINFO_CONFIG_KEY] = { + provider: 'generic' + }; result[exports.ADMIN_DOMAIN_KEY] = ''; result[exports.ADMIN_FQDN_KEY] = ''; result[exports.API_SERVER_ORIGIN_KEY] = 'https://api.cloudron.io'; @@ -467,6 +476,30 @@ function setRegistryConfig(registryConfig, callback) { }); } +function getSysinfoConfig(callback) { + assert.strictEqual(typeof callback, 'function'); + + settingsdb.get(exports.SYSINFO_CONFIG_KEY, function (error, value) { + if (error && error.reason === BoxError.NOT_FOUND) return callback(null, gDefaults[exports.SYSINFO_CONFIG_KEY]); + if (error) return callback(error); + + callback(null, JSON.parse(value)); + }); +} + +function setSysinfoConfig(sysinfoConfig, callback) { + assert.strictEqual(typeof sysinfoConfig, 'object'); + assert.strictEqual(typeof callback, 'function'); + + settingsdb.set(exports.SYSINFO_CONFIG_KEY, JSON.stringify(sysinfoConfig), function (error) { + if (error) return callback(error); + + notifyChange(exports.REGISTRY_CONFIG_KEY, sysinfoConfig); + + callback(null); + }); +} + function getLicenseKey(callback) { assert.strictEqual(typeof callback, 'function'); @@ -554,7 +587,7 @@ function getAll(callback) { result[exports.DEMO_KEY] = !!result[exports.DEMO_KEY]; // convert JSON objects - [exports.BACKUP_CONFIG_KEY, exports.PLATFORM_CONFIG_KEY, exports.EXTERNAL_LDAP_KEY, exports.REGISTRY_CONFIG_KEY ].forEach(function (key) { + [exports.BACKUP_CONFIG_KEY, exports.PLATFORM_CONFIG_KEY, exports.EXTERNAL_LDAP_KEY, exports.REGISTRY_CONFIG_KEY, exports.SYSINFO_CONFIG_KEY ].forEach(function (key) { result[key] = typeof result[key] === 'object' ? result[key] : safe.JSON.parse(result[key]); }); @@ -568,12 +601,15 @@ function initCache(callback) { getAll(function (error, allSettings) { if (error) return callback(error); + const provider = safe.fs.readFileSync(paths.PROVIDER_FILE, 'utf8'); + gCache = { apiServerOrigin: allSettings[exports.API_SERVER_ORIGIN_KEY], webServerOrigin: allSettings[exports.WEB_SERVER_ORIGIN_KEY], adminDomain: allSettings[exports.ADMIN_DOMAIN_KEY], adminFqdn: allSettings[exports.ADMIN_FQDN_KEY], - isDemo: allSettings[exports.DEMO_KEY] + isDemo: allSettings[exports.DEMO_KEY], + provider: provider ? provider.trim() : 'generic' }; callback(); @@ -614,6 +650,7 @@ function setApiServerOrigin(origin, callback) { }); } +function provider() { return gCache.provider; } function apiServerOrigin() { return gCache.apiServerOrigin; } function webServerOrigin() { return gCache.webServerOrigin; } function adminDomain() { return gCache.adminDomain; } diff --git a/src/support.js b/src/support.js index 4cc1d2a4e..4d40a455f 100644 --- a/src/support.js +++ b/src/support.js @@ -12,12 +12,27 @@ let assert = require('assert'), once = require('once'), path = require('path'), paths = require('./paths.js'), - sysinfo = require('./sysinfo.js'); + settings = require('./settings.js'); // the logic here is also used in the cloudron-support tool -const AUTHORIZED_KEYS_FILEPATH = constants.TEST ? path.join(paths.baseDir(), 'authorized_keys') : ((sysinfo.provider() === 'ec2' || sysinfo.provider() === 'lightsail' || sysinfo.provider() === 'ami') ? '/home/ubuntu/.ssh/authorized_keys' : '/root/.ssh/authorized_keys'), - AUTHORIZED_KEYS_USER = constants.TEST ? process.getuid() : ((sysinfo.provider() === 'ec2' || sysinfo.provider() === 'lightsail' || sysinfo.provider() === 'ami') ? 'ubuntu' : 'root'), - AUTHORIZED_KEYS_CMD = path.join(__dirname, 'scripts/remotesupport.sh'); +const AUTHORIZED_KEYS_CMD = path.join(__dirname, 'scripts/remotesupport.sh'); + +function sshInfo() { + let filePath, user; + + if (constants.TEST) { + filePath = path.join(paths.baseDir(), 'authorized_keys'); + user = process.getuid(); + } else if (settings.provider() === 'ec2' || settings.provider() === 'lightsail' || settings.provider() === 'ami') { + filePath = '/home/ubuntu/.ssh/authorized_keys'; + user = 'ubuntu'; + } else { + filePath = '/root/.ssh/authorized_keys'; + user = 'root'; + } + + return { filePath, user }; +} function getRemoteSupport(callback) { assert.strictEqual(typeof callback, 'function'); @@ -25,7 +40,7 @@ function getRemoteSupport(callback) { callback = once(callback); // exit may or may not be called after an 'error' let result = ''; - let cp = shell.sudo('support', [ AUTHORIZED_KEYS_CMD, 'is-enabled', AUTHORIZED_KEYS_FILEPATH ], {}, function (error) { + let cp = shell.sudo('support', [ AUTHORIZED_KEYS_CMD, 'is-enabled', sshInfo().filePath ], {}, function (error) { if (error) callback(new BoxError(BoxError.FS_ERROR, error)); callback(null, { enabled: result.trim() === 'true' }); @@ -36,7 +51,8 @@ function getRemoteSupport(callback) { function enableRemoteSupport(enable, callback) { assert.strictEqual(typeof callback, 'function'); - shell.sudo('support', [ AUTHORIZED_KEYS_CMD, enable ? 'enable' : 'disable', AUTHORIZED_KEYS_FILEPATH, AUTHORIZED_KEYS_USER ], {}, function (error) { + let si = sshInfo(); + shell.sudo('support', [ AUTHORIZED_KEYS_CMD, enable ? 'enable' : 'disable', si.filePath, si.user ], {}, function (error) { if (error) callback(new BoxError(BoxError.FS_ERROR, error)); callback(); diff --git a/src/sysinfo.js b/src/sysinfo.js index 6f35d71af..fc5858d12 100644 --- a/src/sysinfo.js +++ b/src/sysinfo.js @@ -1,35 +1,25 @@ 'use strict'; exports = module.exports = { - getPublicIp: getPublicIp, + getServerIp: getServerIp, - hasIPv6: hasIPv6, - provider: provider + hasIPv6: hasIPv6 }; var assert = require('assert'), ec2 = require('./sysinfo/ec2.js'), fs = require('fs'), generic = require('./sysinfo/generic.js'), - paths = require('./paths.js'), scaleway = require('./sysinfo/scaleway.js'), - safe = require('safetydance'); + settings = require('./settings.js'); -let gProvider = null; - -function provider() { - if (gProvider) return gProvider; - - gProvider = safe.fs.readFileSync(paths.PROVIDER_FILE, 'utf8'); - if (!gProvider) return gProvider = 'generic'; - - return gProvider.trim(); -} - -function getApi(callback) { +function getApi(config, callback) { + assert.strictEqual(typeof config, 'object'); assert.strictEqual(typeof callback, 'function'); - switch (provider()) { + const provider = config.provider || 'generic'; + + switch (provider) { case 'ec2': return callback(null, ec2); case 'lightsail': return callback(null, ec2); case 'ami': return callback(null, ec2); @@ -38,13 +28,17 @@ function getApi(callback) { } } -function getPublicIp(callback) { +function getServerIp(callback) { assert.strictEqual(typeof callback, 'function'); - getApi(function (error, api) { + settings.getSysinfoConfig(function (error, config) { if (error) return callback(error); - api.getPublicIp(callback); + getApi(config, function (error, api) { + if (error) return callback(error); + + api.getServerIp(config, callback); + }); }); } diff --git a/src/sysinfo/ec2.js b/src/sysinfo/ec2.js index f6116a893..01756d075 100644 --- a/src/sysinfo/ec2.js +++ b/src/sysinfo/ec2.js @@ -1,7 +1,7 @@ 'use strict'; exports = module.exports = { - getPublicIp: getPublicIp + getServerIp: getServerIp }; var assert = require('assert'), @@ -9,7 +9,8 @@ var assert = require('assert'), superagent = require('superagent'), util = require('util'); -function getPublicIp(callback) { +function getServerIp(config, callback) { + assert.strictEqual(typeof config, 'object'); assert.strictEqual(typeof callback, 'function'); superagent.get('http://169.254.169.254/latest/meta-data/public-ipv4').timeout(30 * 1000).end(function (error, result) { diff --git a/src/sysinfo/generic.js b/src/sysinfo/generic.js index 034a72a1b..b37e19443 100644 --- a/src/sysinfo/generic.js +++ b/src/sysinfo/generic.js @@ -1,7 +1,7 @@ 'use strict'; exports = module.exports = { - getPublicIp: getPublicIp + getServerIp: getServerIp }; var assert = require('assert'), @@ -9,7 +9,8 @@ var assert = require('assert'), BoxError = require('../boxerror.js'), superagent = require('superagent'); -function getPublicIp(callback) { +function getServerIp(config, callback) { + assert.strictEqual(typeof config, 'object'); assert.strictEqual(typeof callback, 'function'); if (process.env.BOX_ENV === 'test') return callback(null, '127.0.0.1'); diff --git a/src/sysinfo/interface.js b/src/sysinfo/interface.js index 8cc7905de..5b51e81ea 100644 --- a/src/sysinfo/interface.js +++ b/src/sysinfo/interface.js @@ -7,12 +7,13 @@ // ------------------------------------------- exports = module.exports = { - getPublicIp: getPublicIp + getServerIp: getServerIp }; var assert = require('assert'); -function getPublicIp(callback) { +function getServerIp(config, callback) { + assert.strictEqual(typeo config, 'object'); assert.strictEqual(typeof callback, 'function'); callback(new Error('not implemented')); diff --git a/src/sysinfo/scaleway.js b/src/sysinfo/scaleway.js index 6de2c065c..085c3028b 100644 --- a/src/sysinfo/scaleway.js +++ b/src/sysinfo/scaleway.js @@ -1,7 +1,7 @@ 'use strict'; exports = module.exports = { - getPublicIp: getPublicIp + getServerIp: getServerIp }; var assert = require('assert'), @@ -9,7 +9,8 @@ var assert = require('assert'), superagent = require('superagent'), util = require('util'); -function getPublicIp(callback) { +function getServerIp(config, callback) { + assert.strictEqual(typeof config, 'object'); assert.strictEqual(typeof callback, 'function'); superagent.get('http://169.254.42.42/conf').timeout(30 * 1000).end(function (error, result) { diff --git a/src/test/appstore-test.js b/src/test/appstore-test.js index b48f553ab..95fd02949 100644 --- a/src/test/appstore-test.js +++ b/src/test/appstore-test.js @@ -31,6 +31,7 @@ function setup(done) { database._clear, settings._setApiServerOrigin.bind(null, MOCK_API_SERVER_ORIGIN), settings.setAdmin.bind(null, ADMIN_DOMAIN, 'my.' + ADMIN_DOMAIN), + settings.initCache ], done); } @@ -81,6 +82,7 @@ describe('Appstore', function () { expect(body.backendSettings.mailConfig.relayProviders).to.be.an('array'); expect(body.backendSettings.appAutoupdatePattern).to.be.a('string'); expect(body.backendSettings.boxAutoupdatePattern).to.be.a('string'); + expect(body.backendSettings.sysinfoProvider).to.be.a('string'); expect(body.backendSettings.timeZone).to.be.a('string'); expect(body.machine).to.be.an('object'); expect(body.machine.cpus).to.be.an('array');