]> code.delx.au - monosys/commitdiff
syncthing-healthcheck
authorJames Bunton <jamesbunton@delx.au>
Mon, 29 Jan 2024 20:38:43 +0000 (07:38 +1100)
committerJames Bunton <jamesbunton@delx.au>
Mon, 29 Jan 2024 20:38:43 +0000 (07:38 +1100)
hacks/syncthing-healthcheck [new file with mode: 0755]

diff --git a/hacks/syncthing-healthcheck b/hacks/syncthing-healthcheck
new file mode 100755 (executable)
index 0000000..ffc668e
--- /dev/null
@@ -0,0 +1,94 @@
+#!/usr/bin/env node
+
+/* eslint-env es2020 */
+
+const API_URL = process.env.API_URL;
+const API_KEY = process.env.API_KEY;
+const VERBOSE = !!process.env.VERBOSE;
+
+async function main() {
+    const config = await fetchConfig();
+    const devicesStatus = await fetchDevicesStatus();
+    const connectionsStatus = await fetchConnections();
+
+    for (const {deviceID, name} of config.devices) {
+        const {connected} = connectionsStatus.connections[deviceID];
+        const {lastSeen: lastSeenString} = devicesStatus[deviceID];
+        const lastSeenDate = new Date(lastSeenString);
+        const {completion, needItems} = await fetchCompletion(deviceID);
+        //console.log(name, await fetchCompletion(deviceID));
+
+        checkDevice({name, connected, lastSeenDate, completion, needItems});
+    }
+}
+
+async function checkDevice({name, connected, lastSeenDate, completion, needItems}) {
+    if (VERBOSE) {
+        console.error('checkDevice', {name, connected, lastSeenDate, completion, needItems});
+    }
+
+    if (lastSeenDate.getTime() === 0 || connected) {
+        return;
+    }
+
+    if (lastSeenDate < newDateDays(-10)) {
+        console.log(`Alert! '${name}' has been missing since ` +
+                lastSeenDate.toISOString().replace(/T.*/, ''));
+    }
+    if (needItems > 0 || completion < 100) {
+        console.log(`Alert! '${name}' is out of sync! ` +
+                `${Math.round(completion)}%, missing ${needItems} files.`);
+    }
+}
+
+function newDateDays(days) {
+    return new Date(Date.now() + days * 24 * 3600 * 1000);
+}
+
+async function fetchConfig() {
+    const response = await fetchSyncthing('/rest/config');
+    await assertResponseOk(response);
+    return response.json();
+}
+
+async function fetchDevicesStatus() {
+    const response = await fetchSyncthing('/rest/stats/device');
+    await assertResponseOk(response);
+    return response.json();
+}
+
+async function fetchConnections() {
+    const response = await fetchSyncthing('/rest/system/connections');
+    await assertResponseOk(response);
+    return response.json();
+}
+
+async function fetchCompletion(deviceID) {
+    const response = await fetchSyncthing(`/rest/db/completion?device=${deviceID}`);
+    await assertResponseOk(response);
+    return response.json();
+}
+
+async function fetchSyncthing(path) {
+    return fetch(`${API_URL}${path}`, {
+        headers: {
+            'X-API-Key': API_KEY,
+        }
+    });
+}
+
+async function assertResponseOk(response) {
+    if (response.ok) {
+        return;
+    }
+    throw new Error(
+        'Response error! ' +
+            response.status + ' ' + response.statusText +
+            ' -- ' + (await response.text())
+    );
+}
+
+main().catch((err) => {
+    console.error('Exiting due to error!', err);
+    process.exit(1);
+});