Skip to content

Commit d5e5ed3

Browse files
authored
feat(doctor): add free disk space check (#1284)
closes #1282 - adds doctor check for system free space
1 parent ee93d0f commit d5e5ed3

File tree

3 files changed

+129
-1
lines changed

3 files changed

+129
-1
lines changed
+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const sysinfo = require('systeminformation');
2+
const {SystemError} = require('../../../errors');
3+
4+
const MB_IN_BYTES = 1048576;
5+
6+
// one version of Ghost takes ~400mb of space currently, to be safe we're going to say 1gb
7+
const MIN_FREE_SPACE = 1024;
8+
9+
async function checkFreeSpace(ctx) {
10+
const dir = ctx.instance ? ctx.instance.dir : process.cwd();
11+
12+
const disks = await sysinfo.fsSize();
13+
14+
// filter out disks with matching mount points, then sort in descending order by mount point length
15+
// to get the mount point with greatest specificity
16+
const [disk] = disks.filter(d => dir.startsWith(d.mount)).sort((a, b) => b.mount.length - a.mount.length);
17+
18+
if (!disk) {
19+
// couldn't find a matching disk, early return
20+
// TODO: maybe throw a warning of some sort here?
21+
return;
22+
}
23+
24+
const available = (disk.size - disk.used) / MB_IN_BYTES;
25+
if (available < MIN_FREE_SPACE) {
26+
throw new SystemError(`You are recommended to have at least ${MIN_FREE_SPACE} MB of free storage space available for smooth operation. It looks like you have ~${available} MB available`);
27+
}
28+
}
29+
30+
module.exports = {
31+
title: 'Checking free space',
32+
task: checkFreeSpace,
33+
category: ['install', 'update']
34+
};

lib/commands/doctor/checks/index.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const filePermissions = require('./file-permissions');
1212
const contentFolder = require('./content-folder');
1313
const checkMemory = require('./check-memory');
1414
const binaryDeps = require('./binary-deps');
15+
const freeSpace = require('./free-space');
1516

1617
module.exports = [
1718
nodeVersion,
@@ -26,5 +27,6 @@ module.exports = [
2627
filePermissions,
2728
contentFolder,
2829
checkMemory,
29-
binaryDeps
30+
binaryDeps,
31+
freeSpace
3032
];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
const {expect} = require('chai');
2+
const sinon = require('sinon');
3+
4+
const sysinfo = require('systeminformation');
5+
const {SystemError} = require('../../../../../lib/errors');
6+
7+
const check = require('../../../../../lib/commands/doctor/checks/free-space');
8+
9+
describe('Unit: Doctor Checks > Free Space', function () {
10+
afterEach(() => {
11+
sinon.restore();
12+
});
13+
14+
it('exports proper task', function () {
15+
expect(check.title).to.equal('Checking free space');
16+
expect(check.task).to.be.a('function');
17+
expect(check.category).to.deep.equal(['install', 'update']);
18+
});
19+
20+
it('handles error from systeminformation', function () {
21+
const stub = sinon.stub(sysinfo, 'fsSize').rejects(new Error('test-error'));
22+
const cwdStub = sinon.stub(process, 'cwd').returns('/test/directory');
23+
24+
return check.task({}).catch((error) => {
25+
expect(error).to.be.an('error');
26+
expect(error.message).to.equal('test-error');
27+
expect(stub.calledOnce).to.be.true;
28+
expect(cwdStub.calledOnce).to.be.true;
29+
});
30+
});
31+
32+
it('handles error from systeminformation', function () {
33+
const stub = sinon.stub(sysinfo, 'fsSize').rejects(new Error('test-error'));
34+
const cwdStub = sinon.stub(process, 'cwd').returns('/test/directory');
35+
36+
return check.task({}).catch((error) => {
37+
expect(error).to.be.an('error');
38+
expect(error.message).to.equal('test-error');
39+
expect(stub.calledOnce).to.be.true;
40+
expect(cwdStub.calledOnce).to.be.true;
41+
});
42+
});
43+
44+
it('does nothing if no matching mount points found', async function () {
45+
const stub = sinon.stub(sysinfo, 'fsSize').resolves([{
46+
mount: '/not/matching/dir',
47+
size: 0,
48+
used: 0
49+
}]);
50+
const cwdStub = sinon.stub(process, 'cwd').returns('/not/matching/dir');
51+
52+
await check.task({instance: {dir: '/test/dir'}});
53+
expect(stub.calledOnce).to.be.true;
54+
expect(cwdStub.called).to.be.false;
55+
});
56+
57+
it('errors if not enough space available', function () {
58+
const stub = sinon.stub(sysinfo, 'fsSize').resolves([{
59+
mount: '/',
60+
size: 1000000000000,
61+
used: 1000000000
62+
}, {
63+
mount: '/test/dir',
64+
size: 0,
65+
used: 0
66+
}]);
67+
const cwdStub = sinon.stub(process, 'cwd').returns('/test/dir');
68+
69+
return check.task({}).catch((error) => {
70+
expect(error).to.be.an.instanceOf(SystemError);
71+
expect(stub.calledOnce).to.be.true;
72+
expect(cwdStub.calledOnce).to.be.true;
73+
});
74+
});
75+
76+
it('succeeds if enough space available', async function () {
77+
const stub = sinon.stub(sysinfo, 'fsSize').resolves([{
78+
mount: '/',
79+
size: 1000000000000,
80+
used: 1000000000
81+
}, {
82+
mount: '/test/dir',
83+
size: 0,
84+
used: 0
85+
}]);
86+
const cwdStub = sinon.stub(process, 'cwd').returns('/test/dir');
87+
88+
await check.task({instance: {dir: '/'}});
89+
expect(stub.calledOnce).to.be.true;
90+
expect(cwdStub.called).to.be.false;
91+
});
92+
});

0 commit comments

Comments
 (0)