Skip to content

Instantly share code, notes, and snippets.

@BananaAcid
Last active September 9, 2024 21:37
Show Gist options
  • Save BananaAcid/7447c433fe7fa685adefa22f0434c0c3 to your computer and use it in GitHub Desktop.
Save BananaAcid/7447c433fe7fa685adefa22f0434c0c3 to your computer and use it in GitHub Desktop.

free-space-json-api-server

Expose basic disk usage

{
    "free": 15024480256,
    "available": 15024480256,
    "freePercent": 3.01,
    "warnMin": false,

    "human": {
        "free": "13.99 GB",
        "available": "13.99 GB",
        "freePercent": "3.01%"
    }
}

Uptime-services can check for MinWarn to show a warning.

Config ENVs

Variable Default Info
PATH_TO_CHECK '/host' Where the external volume will be mounted internally
must be mounted path, not a filesystem identifier
CACHE_SECS 60 Any shorter request will get the cached values (in seconds, 0 will default to 60)
WARN_MIN_FREE_BYTES 10 * (/GB/ 1024 * 1024 * 1024) Use to calculate if warnMin should be show true or false
PORT NODE_PORT ?? 3000 Port to expose to

Run

PORT=3000 npx --yes tsx index.ts

Dev:

DEBUG=true npx --yes tsx --watch index.ts

deploy

Docker Mount the hosts root to /host in the container for readonly access ( --volume /:/host:ro ) --- Docker host should not be running as root anyways.

/** expose a simple free-space-json-api-server
*
* run: PORT=3000 npx --yes tsx index.ts
* dev run: DEBUG=true npx --yes tsx --watch index.ts
* in browser test cors: console.log( await fetch('https://otherdomain.local/').then(r => r.json()) )
*
* example result:
* {
* "free": 15047938048,
* "available": 15047938048,
* "freePercent": 3.01,
* "human": {
* "free": "14.01 GB",
* "available": "14.01 GB",
* "freePercent": "3.01%"
* }
* }
**/
const PATH_TO_CHECK = process.env.PATH_TO_CHECK || '/host'; // must be mounted path, not a filesystem identifier
const CACHE_SECS = Number( process.env.CACHE_SECS || 60 );
const WARN_MIN_FREE_BYTES = Number(process.env.WARN_MIN_FREE_BYTES || 10 * (/*GB*/ 1024 * 1024 * 1024));
import { createServer } from 'node:http';
import { statfs } from 'node:fs/promises';
function bytesToHumanReadable(bytes: number) {
const units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
let unitIndex = 0;
while (bytes >= 1024 && unitIndex < units.length - 1) {
bytes /= 1024;
unitIndex++;
}
return `${bytes.toFixed(2)} ${units[unitIndex]}`;
}
let cache = {
timestamp: 0,
result: null as any,
};
async function getSizes() {
if (process.env.DEBUG) {
console.log('chached time left:', Date.now() - cache.timestamp);
console.log('cache expired', !cache.timestamp || Date.now() - cache.timestamp > (CACHE_SECS * 1000));
}
if (!cache.timestamp || Date.now() - cache.timestamp > (CACHE_SECS * 1000)) {
const stats = await statfs(PATH_TO_CHECK);
if (process.env.DEBUG) {
console.log('Warn ninimum free space', bytesToHumanReadable(WARN_MIN_FREE_BYTES));
console.log('Total free space', bytesToHumanReadable(stats.bsize * stats.bfree));
console.log('Available for user', bytesToHumanReadable(stats.bsize * stats.bavail));
}
cache.result = {
free: stats.bsize * stats.bfree, // system free space
available: stats.bsize * stats.bavail, // space available for current user
freePercent: Number( ((stats.bfree / stats.blocks) * 100).toFixed(2) ),
warnMin: WARN_MIN_FREE_BYTES > (stats.bsize * stats.bavail),
human: {
free: bytesToHumanReadable(stats.bsize * stats.bfree),
available: bytesToHumanReadable(stats.bsize * stats.bavail),
// DO NOT EXPOSE: used: bytesToHumanReadable((stats.blocks - stats.bfree) * stats.bsize), // used space
freePercent: ((stats.bfree / stats.blocks) * 100).toFixed(2) + '%',
warnMin: WARN_MIN_FREE_BYTES > (stats.bsize * stats.bavail) ? 'Minimum has been reached' : 'Enough free space left',
},
};
cache.timestamp = Date.now();
}
return cache;
}
const server = createServer(async function (req, res) {
const sizeInfo = await getSizes();
Object.entries({
'Content-Type':'application/json',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': '*',
'Cache-Changed': sizeInfo.timestamp,
}).forEach(([key, value]) => {
res.setHeader(key, value);
});
res.writeHead(200);
res.end(JSON.stringify(sizeInfo.result));
});
server.listen(process.env.NODE_PORT || process.env.PORT || 3000);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment