'use strict'; exports = module.exports = { getDisks, checkDiskSpace, getMemory, getMemoryAllocation }; const apps = require('./apps.js'), assert = require('assert'), async = require('async'), BoxError = require('./boxerror.js'), debug = require('debug')('box:disks'), df = require('@sindresorhus/df'), docker = require('./docker.js'), notifications = require('./notifications.js'), os = require('os'), paths = require('./paths.js'), safe = require('safetydance'), settings = require('./settings.js'), volumes = require('./volumes.js'); const dfAsync = async.asyncify(df), dfFileAsync = async.asyncify(df.file); async function getVolumeDisks(appsDataDisk) { assert.strictEqual(typeof appsDataDisk, 'string'); let volumeDisks = {}; const allVolumes = await volumes.list(); for (const volume of allVolumes) { const [error, result] = await safe(df(volume.hostPath)); volumeDisks[volume.id] = error ? appsDataDisk : result.filesystem; // ignore any errors } return volumeDisks; } async function getAppDisks(appsDataDisk) { assert.strictEqual(typeof appsDataDisk, 'string'); let appDisks = {}; return new Promise((resolve, reject) => { apps.getAll(async function (error, allApps) { if (error) return reject(error); for (const app of allApps) { if (!app.dataDir) { appDisks[app.id] = appsDataDisk; } else { const [error, result] = await safe(df.file(app.dataDir)); appDisks[app.id] = error ? appsDataDisk : result.filesystem; // ignore any errors } } resolve(appDisks); }); }); } function getBackupDisk(callback) { assert.strictEqual(typeof callback, 'function'); settings.getBackupConfig(function (error, backupConfig) { if (error) return callback(error); if (backupConfig.provider !== 'filesystem') return callback(null, null); dfFileAsync(backupConfig.backupFolder, function (error, result) { if (error) return callback(error); callback(null, result.filesystem); }); }); } function getDisks(callback) { assert.strictEqual(typeof callback, 'function'); docker.info(function (error, info) { if (error) return callback(error); async.series([ dfAsync, dfFileAsync.bind(null, paths.BOX_DATA_DIR), dfFileAsync.bind(null, paths.PLATFORM_DATA_DIR), dfFileAsync.bind(null, paths.APPS_DATA_DIR), dfFileAsync.bind(null, info.DockerRootDir), getBackupDisk, ], async function (error, values) { if (error) return callback(new BoxError(BoxError.FS_ERROR, error)); // filter by ext4 and then sort to make sure root disk is first const ext4Disks = values[0].filter((r) => r.type === 'ext4').sort((a, b) => a.mountpoint.localeCompare(b.mountpoint)); const disks = { disks: ext4Disks, // root disk is first. { filesystem, type, size, used, avialable, capacity, mountpoint } boxDataDisk: values[1].filesystem, mailDataDisk: values[1].filesystem, platformDataDisk: values[2].filesystem, appsDataDisk: values[3].filesystem, dockerDataDisk: values[4].filesystem, backupsDisk: values[5], apps: {}, // filled below volumes: {} // filled below }; [error, disks.apps] = await safe(getAppDisks(disks.appsDataDisk)); if (error) return callback(error); [error, disks.volumes] = await safe(getVolumeDisks(disks.appsDataDisk)); if (error) return callback(error); callback(null, disks); }); }); } function checkDiskSpace(callback) { assert.strictEqual(typeof callback, 'function'); debug('checkDiskSpace: checking disk space'); getDisks(async function (error, disks) { if (error) { debug('checkDiskSpace: error getting disks %s', error.message); return callback(); } let markdownMessage = ''; disks.disks.forEach(function (entry) { // ignore other filesystems but where box, app and platform data is if (entry.filesystem !== disks.boxDataDisk && entry.filesystem !== disks.platformDataDisk && entry.filesystem !== disks.appsDataDisk && entry.filesystem !== disks.backupsDisk && entry.filesystem !== disks.dockerDataDisk) return false; if (entry.available <= (1.25 * 1024 * 1024 * 1024)) { // 1.5G markdownMessage += `* ${entry.filesystem} is at ${entry.capacity*100}% capacity.\n`; } }); debug(`checkDiskSpace: disk space checked. out of space: ${markdownMessage || 'no'}`); if (markdownMessage) markdownMessage = `One or more file systems are running out of space. Please increase the disk size at the earliest.\n\n${markdownMessage}`; await notifications.alert(notifications.ALERT_DISK_SPACE, 'Server is running out of disk space', markdownMessage); callback(); }); } function getSwapSize() { const stdout = safe.child_process.execSync('swapon --noheadings --raw --bytes --show=SIZE', { encoding: 'utf8' }); const swap = !stdout ? 0 : stdout.trim().split('\n').map(x => parseInt(x, 10) || 0).reduce((acc, cur) => acc + cur); return swap; } function getMemory(callback) { assert.strictEqual(typeof callback, 'function'); callback(null, { memory: os.totalmem(), swap: getSwapSize() }); } function getMemoryAllocation(limit) { let ratio = parseFloat(safe.fs.readFileSync(paths.SWAP_RATIO_FILE, 'utf8'), 10); if (!ratio) { const pc = os.totalmem() / (os.totalmem() + getSwapSize()); ratio = Math.round(pc * 10) / 10; // a simple ratio } return Math.round(Math.round(limit * ratio) / 1048576) * 1048576; // nearest MB }