|
/** 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); |