2019-08-19 13:50:44 -07:00
'use strict' ;
exports = module . exports = {
2021-01-04 11:05:42 -08:00
getDisks ,
2022-11-04 15:09:37 +01:00
getSwaps ,
2021-01-04 11:05:42 -08:00
checkDiskSpace ,
2021-01-20 11:45:04 -08:00
getMemory ,
2022-10-11 22:58:12 +02:00
getMemoryAllocation ,
2022-10-12 10:26:21 +02:00
getDiskUsage ,
updateDiskUsage
2019-08-19 13:50:44 -07:00
} ;
const apps = require ( './apps.js' ) ,
assert = require ( 'assert' ) ,
2019-10-22 11:11:41 -07:00
BoxError = require ( './boxerror.js' ) ,
2019-08-19 13:50:44 -07:00
debug = require ( 'debug' ) ( 'box:disks' ) ,
2022-10-18 19:32:07 +02:00
df = require ( './df.js' ) ,
2019-08-19 13:50:44 -07:00
docker = require ( './docker.js' ) ,
notifications = require ( './notifications.js' ) ,
2019-11-21 12:55:17 -08:00
os = require ( 'os' ) ,
2022-10-11 22:58:12 +02:00
path = require ( 'path' ) ,
2019-11-21 12:55:17 -08:00
paths = require ( './paths.js' ) ,
2020-01-31 13:37:07 -08:00
safe = require ( 'safetydance' ) ,
2021-01-04 11:05:42 -08:00
settings = require ( './settings.js' ) ,
2022-10-11 22:58:12 +02:00
shell = require ( './shell.js' ) ,
2021-01-04 11:05:42 -08:00
volumes = require ( './volumes.js' ) ;
2019-08-19 13:50:44 -07:00
2022-10-11 22:58:12 +02:00
const DU _CMD = path . join ( _ _dirname , 'scripts/du.sh' ) ;
2023-01-27 21:05:25 +01:00
const HDPARM _CMD = path . join ( _ _dirname , 'scripts/hdparm.sh' ) ;
2022-10-11 22:58:12 +02:00
2022-10-12 11:59:28 +02:00
async function du ( file ) {
2022-10-11 23:14:50 +02:00
assert . strictEqual ( typeof file , 'string' ) ;
2022-10-12 11:59:28 +02:00
const [ error , stdoutResult ] = await safe ( shell . promises . sudo ( 'system' , [ DU _CMD , file ] , { } ) ) ;
2022-10-11 22:58:12 +02:00
if ( error ) throw new BoxError ( BoxError . FS _ERROR , error ) ;
return parseInt ( stdoutResult . trim ( ) , 10 ) ;
}
2023-01-27 21:05:25 +01:00
async function hdparm ( file ) {
assert . strictEqual ( typeof file , 'string' ) ;
const [ error , stdoutResult ] = await safe ( shell . promises . sudo ( 'system' , [ HDPARM _CMD , file ] , { } ) ) ;
if ( error ) throw new BoxError ( BoxError . FS _ERROR , error ) ;
const lines = stdoutResult . split ( '\n' ) ;
if ( lines . length != 4 ) return - 1 ;
if ( lines [ 2 ] . split ( '=' ) . length !== 2 ) return - 1 ;
const speed = lines [ 2 ] . split ( '=' ) [ 1 ] . slice ( 0 , 'MB/sec' . length ) . trim ( ) ;
return Number ( speed ) ;
}
2022-11-04 15:09:37 +01:00
async function getSwaps ( ) {
const stdout = safe . child _process . execSync ( 'swapon --noheadings --raw --bytes --show=type,size,used,name' , { encoding : 'utf8' } ) ;
if ( ! stdout ) return { } ;
const swaps = { } ;
for ( const line of stdout . trim ( ) . split ( '\n' ) ) {
const parts = line . split ( ' ' , 4 ) ;
const name = parts [ 3 ] ;
swaps [ name ] = {
name : parts [ 3 ] ,
type : parts [ 0 ] , // partition or file
size : parseInt ( parts [ 1 ] ) ,
used : parseInt ( parts [ 2 ] ) ,
} ;
}
return swaps ;
}
2022-10-12 09:42:14 +02:00
async function getDisks ( ) {
2022-10-18 19:32:07 +02:00
let [ dfError , dfEntries ] = await safe ( df . disks ( ) ) ;
2022-10-12 09:42:14 +02:00
if ( dfError ) throw new BoxError ( BoxError . FS _ERROR , ` Error running df: ${ dfError . message } ` ) ;
const disks = { } ; // by file system
let rootDisk ;
2022-12-08 08:52:50 +01:00
const DISK _TYPES = [ 'ext4' , 'xfs' , 'cifs' , 'nfs' , 'fuse.sshfs' ] ; // we don't show size of contents in untracked disk types
2022-10-12 09:42:14 +02:00
for ( const disk of dfEntries ) {
2022-12-08 08:52:50 +01:00
if ( ! DISK _TYPES . includes ( disk . type ) ) continue ;
2022-10-12 09:42:14 +02:00
if ( disk . mountpoint === '/' ) rootDisk = disk ;
disks [ disk . filesystem ] = {
filesystem : disk . filesystem ,
type : disk . type ,
size : disk . size ,
used : disk . used ,
available : disk . available ,
capacity : disk . capacity ,
mountpoint : disk . mountpoint ,
contents : [ ] // filled below
} ;
2021-05-11 17:50:48 -07:00
}
2021-01-04 11:05:42 -08:00
2022-10-12 09:42:14 +02:00
const standardPaths = [
{ type : 'standard' , id : 'platformdata' , path : paths . PLATFORM _DATA _DIR } ,
2022-10-12 11:59:28 +02:00
{ type : 'standard' , id : 'boxdata' , path : paths . BOX _DATA _DIR } ,
2022-10-12 09:42:14 +02:00
{ type : 'standard' , id : 'maildata' , path : paths . MAIL _DATA _DIR } ,
] ;
2021-01-04 11:05:42 -08:00
2022-10-12 09:42:14 +02:00
for ( const stdPath of standardPaths ) {
const [ dfError , diskInfo ] = await safe ( df . file ( stdPath . path ) ) ;
if ( dfError ) throw new BoxError ( BoxError . FS _ERROR , ` Error getting std path: ${ dfError . message } ` ) ;
disks [ diskInfo . filesystem ] . contents . push ( stdPath ) ;
2021-08-20 09:19:44 -07:00
}
2021-01-04 11:05:42 -08:00
2021-08-19 13:24:38 -07:00
const backupConfig = await settings . getBackupConfig ( ) ;
2022-10-12 09:42:14 +02:00
if ( backupConfig . provider === 'filesystem' ) {
const [ , dfResult ] = await safe ( df . file ( backupConfig . backupFolder ) ) ;
2022-12-08 08:52:50 +01:00
const filesystem = dfResult ? . filesystem || rootDisk . filesystem ;
if ( disks [ filesystem ] ) disks [ filesystem ] . contents . push ( { type : 'standard' , id : 'cloudron-backup' , path : backupConfig . backupFolder } ) ;
2022-10-12 09:42:14 +02:00
}
2021-01-04 11:05:42 -08:00
2022-10-12 09:42:14 +02:00
const [ dockerError , dockerInfo ] = await safe ( docker . info ( ) ) ;
if ( ! dockerError ) {
const [ , dfResult ] = await safe ( df . file ( dockerInfo . DockerRootDir ) ) ;
2022-12-08 08:52:50 +01:00
const filesystem = dfResult ? . filesystem || rootDisk . filesystem ;
if ( disks [ filesystem ] ) disks [ filesystem ] . contents . push ( { type : 'standard' , id : 'docker' , path : dockerInfo . DockerRootDir } ) ;
2021-08-25 19:41:46 -07:00
}
2022-10-12 09:42:14 +02:00
for ( const volume of await volumes . list ( ) ) {
2022-10-18 19:32:07 +02:00
const [ , dfResult ] = await safe ( df . file ( volume . hostPath ) ) ;
2022-12-08 08:52:50 +01:00
const filesystem = dfResult ? . filesystem || rootDisk . filesystem ;
if ( disks [ filesystem ] ) disks [ filesystem ] . contents . push ( { type : 'volume' , id : volume . id , path : volume . hostPath } ) ;
2022-10-12 09:42:14 +02:00
}
2021-08-25 19:41:46 -07:00
2022-10-12 09:42:14 +02:00
for ( const app of await apps . list ( ) ) {
2022-10-13 23:34:47 +02:00
if ( ! app . manifest . addons ? . localstorage ) continue ;
2019-08-19 13:50:44 -07:00
2022-10-12 09:42:14 +02:00
const dataDir = await apps . getStorageDir ( app ) ;
const [ , dfResult ] = await safe ( df . file ( dataDir ) ) ;
2022-12-08 08:52:50 +01:00
const filesystem = dfResult ? . filesystem || rootDisk . filesystem ;
if ( disks [ filesystem ] ) disks [ filesystem ] . contents . push ( { type : 'app' , id : app . id , path : dataDir } ) ;
2022-10-12 09:42:14 +02:00
}
2021-06-23 23:38:39 -07:00
2022-11-22 11:55:54 +01:00
const swaps = await getSwaps ( ) ;
for ( const k in swaps ) {
const swap = swaps [ k ] ;
if ( swap . type !== 'file' ) continue ;
const [ , dfResult ] = await safe ( df . file ( swap . name ) ) ;
disks [ dfResult ? . filesystem || rootDisk . filesystem ] . contents . push ( { type : 'swap' , id : swap . name , path : swap . name } ) ;
}
2022-10-12 09:42:14 +02:00
return disks ;
2021-08-25 19:41:46 -07:00
}
2019-08-19 13:50:44 -07:00
2021-08-25 19:41:46 -07:00
async function checkDiskSpace ( ) {
debug ( 'checkDiskSpace: checking disk space' ) ;
2019-08-19 13:50:44 -07:00
2021-08-25 19:41:46 -07:00
const disks = await getDisks ( ) ;
2021-06-23 23:38:39 -07:00
2021-08-25 19:41:46 -07:00
let markdownMessage = '' ;
2019-08-19 13:50:44 -07:00
2022-10-12 09:42:14 +02:00
for ( const filesystem of Object . keys ( disks ) ) {
const disk = disks [ filesystem ] ;
if ( disk . contents . length === 0 ) continue ; // ignore if nothing interesting here
2021-06-03 12:20:44 -07:00
2022-10-12 09:42:14 +02:00
if ( disk . available <= ( 1.25 * 1024 * 1024 * 1024 ) ) { // 1.5G
markdownMessage += ` * ${ disk . filesystem } is at ${ disk . capacity * 100 } % capacity. \n ` ;
2021-08-25 19:41:46 -07:00
}
2022-10-12 09:42:14 +02:00
}
2021-08-25 19:41:46 -07:00
debug ( ` checkDiskSpace: disk space checked. out of space: ${ markdownMessage || 'no' } ` ) ;
2023-03-26 17:02:44 +02:00
if ( markdownMessage ) {
const finalMessage = ` 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' , finalMessage , { persist : true } ) ;
} else {
await notifications . clearAlert ( notifications . ALERT _DISK _SPACE , 'Server is running out of disk space' ) ;
}
2019-08-19 13:50:44 -07:00
}
2019-11-21 12:55:17 -08:00
2022-11-04 15:09:37 +01:00
async function getSwapSize ( ) {
const swaps = await getSwaps ( ) ;
2022-12-05 12:20:42 +01:00
return Object . keys ( swaps ) . map ( n => swaps [ n ] . size ) . reduce ( ( acc , cur ) => acc + cur , 0 ) ;
2021-01-20 11:45:04 -08:00
}
2021-08-25 19:41:46 -07:00
async function getMemory ( ) {
return {
2019-11-21 12:55:17 -08:00
memory : os . totalmem ( ) ,
2022-11-04 15:09:37 +01:00
swap : await getSwapSize ( )
2021-08-25 19:41:46 -07:00
} ;
2019-11-21 12:55:17 -08:00
}
2021-01-20 11:45:04 -08:00
2022-11-04 15:09:37 +01:00
async function getMemoryAllocation ( limit ) {
2021-01-20 20:05:39 -08:00
let ratio = parseFloat ( safe . fs . readFileSync ( paths . SWAP _RATIO _FILE , 'utf8' ) , 10 ) ;
if ( ! ratio ) {
2022-11-04 15:09:37 +01:00
const pc = os . totalmem ( ) / ( os . totalmem ( ) + await getSwapSize ( ) ) ;
2021-01-20 20:05:39 -08:00
ratio = Math . round ( pc * 10 ) / 10 ; // a simple ratio
}
2021-01-20 11:45:04 -08:00
return Math . round ( Math . round ( limit * ratio ) / 1048576 ) * 1048576 ; // nearest MB
}
2022-10-12 10:26:21 +02:00
2022-10-12 10:35:12 +02:00
async function getDiskUsage ( ) {
return safe . JSON . parse ( safe . fs . readFileSync ( paths . DISK _USAGE _FILE , 'utf8' ) ) ;
2022-10-12 10:26:21 +02:00
}
async function updateDiskUsage ( progressCallback ) {
2022-10-12 10:35:12 +02:00
assert . strictEqual ( typeof progressCallback , 'function' ) ;
2022-10-12 10:26:21 +02:00
const disks = await getDisks ( ) ;
const filesystems = Object . keys ( disks ) ;
const now = Date . now ( ) ;
let percent = 1 ;
for ( const filesystem of filesystems ) {
const disk = disks [ filesystem ] ;
2023-01-27 21:05:25 +01:00
const [ speedError , speed ] = await safe ( hdparm ( filesystem ) ) ;
if ( speedError ) progressCallback ( { message : ` hdparm error: ${ speedError . message } ` } ) ;
disk . speed = speed ;
2022-10-12 10:26:21 +02:00
percent += ( 100 / filesystems . length ) ;
progressCallback ( { percent , message : ` Checking contents of ${ filesystem } ` } ) ;
for ( const content of disk . contents ) {
progressCallback ( { message : ` Checking du of ${ JSON . stringify ( content ) } ` } ) ;
2022-10-12 10:35:12 +02:00
if ( content . id === 'docker' ) {
content . usage = ( await docker . df ( ) ) . LayersSize ;
2022-10-12 10:26:21 +02:00
} else {
2022-10-13 23:34:47 +02:00
const [ error , usage ] = await safe ( du ( content . path ) ) ;
if ( error ) progressCallback ( { message : ` du error: ${ error . message } ` } ) ; // can happen if app is installing etc
content . usage = usage || 0 ;
2023-01-27 21:05:25 +01:00
2022-10-12 10:26:21 +02:00
}
progressCallback ( { message : ` du of ${ JSON . stringify ( content ) } : ${ content . usage } ` } ) ;
}
}
if ( ! safe . fs . writeFileSync ( paths . DISK _USAGE _FILE , JSON . stringify ( { ts : now , disks } ) , 'utf8' ) ) throw new BoxError ( BoxError . FS _ERROR , ` Could not write du cache file: ${ safe . error . message } ` ) ;
return disks ;
}