jetcheck 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/index.js ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+
3
+ const path = require("path")
4
+ const sc = require('subcommander');
5
+ const os = require('os');
6
+ const fs = require('fs');
7
+ const crypto = require('crypto');
8
+ const netcheck = require("../index")
9
+
10
+ // Early loading configuration file
11
+ const configFile = path.resolve(
12
+ process.cwd(),
13
+ process.env.CONFIG || `${os.homedir()}/.netcheck/config.js`
14
+ )
15
+ require("./systemd")(netcheck, sc, configFile)
16
+ require("./run")(netcheck, sc, configFile)
17
+
18
+ sc.parse()
19
+
20
+
package/bin/run.js ADDED
@@ -0,0 +1,25 @@
1
+
2
+
3
+ const fs = require('fs')
4
+ const path = require("path")
5
+
6
+ module.exports = (netcheck, sc, configFile) => {
7
+ sc.command('run', {
8
+ desc: 'Run the monitoring',
9
+ callback: async function (options) {
10
+ const kernel = new netcheck({ configFile })
11
+ await kernel.start()
12
+ }
13
+ })
14
+
15
+ sc.command('test', {
16
+ desc: 'Run the monitoring',
17
+ callback: async function (options) {
18
+ const kernel = new netcheck({ headless: true, configFile })
19
+ await kernel.start()
20
+ }
21
+
22
+ })
23
+ }
24
+
25
+
@@ -0,0 +1,15 @@
1
+ [Unit]
2
+ Description=Netcheck
3
+ After=network-online.target
4
+
5
+ [Service]
6
+ Type=simple
7
+ WorkingDirectory=<%- cwd %>
8
+ ExecStart=<%- runCmd %>
9
+ Restart=always
10
+ TimeoutStopSec=300
11
+ RestartSec=5s
12
+ Environment="<%- config %>"
13
+
14
+ [Install]
15
+ WantedBy=multi-user.target
package/bin/systemd.js ADDED
@@ -0,0 +1,63 @@
1
+
2
+
3
+ const fs = require('fs')
4
+ const path = require("path")
5
+ const ejs = require("ejs")
6
+
7
+ module.exports = (netcheck, sc, configFile) => {
8
+
9
+ const systemd = sc.command('systemd', {
10
+ desc: 'Linux systemd service management'
11
+ })
12
+
13
+ systemd.command('install', {
14
+ desc: 'Run the monitoring',
15
+ callback: async function (options) {
16
+ var installed = false
17
+ try {
18
+ fs.statSync("/etc/systemd/system/netcheck.service")
19
+ installed = true
20
+ } catch (e) { }
21
+
22
+ if (installed === true) {
23
+ console.log("Netcheck is already installed")
24
+ process.exit(-1)
25
+ }
26
+
27
+ const kernel = new netcheck({ configFile })
28
+
29
+ const runCmd = `${process.argv[0]} ${process.argv[1]} run`
30
+ const content = fs.readFileSync(__dirname + "/systemd.conf").toString()
31
+ const render = ejs.render(content, { configFile, runCmd, cwd: process.cwd(), config: `CONFIG=${configFile}` })
32
+ fs.writeFileSync("/etc/systemd/system/netcheck.service", render)
33
+
34
+ await kernel.lib.exec("systemctl daemon-reload", false)
35
+ await kernel.lib.exec("systemctl enable netcheck", false)
36
+ console.log("To start type: systemctl start netcheck")
37
+ }
38
+ })
39
+
40
+ systemd.command('deinstall', {
41
+ desc: 'Run the monitoring',
42
+ callback: async function (options) {
43
+ var installed = false
44
+ try {
45
+ fs.statSync("/etc/systemd/system/netcheck.service")
46
+ installed = true
47
+ } catch (e) { }
48
+
49
+ if (installed === false) {
50
+ console.log("Netcheck is not installed")
51
+ process.exit(-1)
52
+ }
53
+ const kernel = new netcheck({ configFile })
54
+
55
+ await kernel.lib.exec("systemctl stop netcheck", false)
56
+ await kernel.lib.exec("systemctl disable netcheck", false)
57
+ fs.unlinkSync("/etc/systemd/system/netcheck.service")
58
+ await kernel.lib.exec("systemctl daemon-reload", false)
59
+ console.log("Netcheck deinstalled")
60
+ }
61
+ })
62
+ }
63
+
@@ -0,0 +1,31 @@
1
+ module.exports = {
2
+ tests: {
3
+ myCheck: {
4
+ // start from degraded mode
5
+ init: "DEGRADED",
6
+
7
+ // test every 15 seconds
8
+ interval: 15,
9
+
10
+ // checker tasks
11
+ tasks: [
12
+ { type: "ping", host: "8.8.8.8", count: 2 },
13
+ // { type: "ping", host: "54.43.5.5" },
14
+ { type: "http", url: "https://8.8.8.8" },
15
+ { type: "http", url: "https://1.1.1.1" },
16
+ { type: "http", url: "https://1.2.4.1" },
17
+ ],
18
+
19
+ // when system fails
20
+ failed: [
21
+ { type: "cmd", cmd: `logger "TEST: Disconnected from HTTP"` }
22
+ ],
23
+
24
+ // when system succeed
25
+ succeed: [
26
+ { type: "cmd", cmd: `logger "TEST: Connected from HTTP"` },
27
+ { type: "cmd", cmd: `logger "TEST: secondary command"` }
28
+ ],
29
+ }
30
+ }
31
+ }
package/config-wan.js ADDED
@@ -0,0 +1,21 @@
1
+ module.exports = {
2
+ tests: {
3
+ wan: {
4
+ init: "WORKING",
5
+ interval: 5,
6
+ tasks: [
7
+ { type: "ping", host: "8.8.8.8", count: 3 },
8
+ { type: "http", url: "https://8.8.8.8" },
9
+ { type: "http", url: "https://1.1.1.1" },
10
+ ],
11
+
12
+ failed: [
13
+ { type: "cmd", cmd: `systemctl stop frr` }
14
+ ],
15
+ succeed: [
16
+ { type: "cmd", cmd: `systemctl start frr` },
17
+ ],
18
+ }
19
+ }
20
+
21
+ }
package/index.js ADDED
@@ -0,0 +1,72 @@
1
+ const path = require("path")
2
+
3
+ // internal requires
4
+ const pack = require("./package.json")
5
+ const aevent = require("./lib/aevent")
6
+
7
+ class netcheck {
8
+ constructor(options) {
9
+ this.options = options || {}
10
+ this.events = new aevent()
11
+ this.lib = require("./lib")
12
+ this.packages = {}
13
+
14
+ this.packageInfo(pack, ["service"])
15
+ }
16
+
17
+ packageInfo(pkg, log) {
18
+ var buffer = [`version=${pkg.version} -`]
19
+ for (var key in pkg.dependencies)
20
+ buffer.push(`${key}=${pkg.dependencies[key]}`)
21
+ this.packages[pkg.name] = pkg
22
+ }
23
+
24
+ async sleep(ms) {
25
+ return new Promise(resolve => setTimeout(resolve, ms));
26
+ }
27
+
28
+ async start(overwrite) {
29
+ this.version = pack.version
30
+
31
+ // load configuration
32
+ if (this.options.configFile) {
33
+ this.configFile = path.resolve(process.cwd(), this.options.configFile);
34
+ try {
35
+ this.config = require(this.configFile)
36
+ } catch (e) {
37
+ console.log(`Can not read configuration ${this.configFile}`)
38
+ process.exit(-1)
39
+ }
40
+ }
41
+ else
42
+ this.config = {}
43
+
44
+ // overwrite feature
45
+ if (overwrite)
46
+ this.config = { ...this.config, ...overwrite }
47
+
48
+ if (!Array.isArray(this.config.modules))
49
+ this.config.modules = []
50
+
51
+ // load basic services
52
+ await (require("./services/tests"))(this)
53
+
54
+ // load modules
55
+ if (this.config.modules) {
56
+ for (var module of this.config.modules) {
57
+ await require(module)(this)
58
+ }
59
+ }
60
+
61
+ await this.events.emit("kernel.init", this)
62
+ }
63
+
64
+ async stop() {
65
+
66
+ }
67
+
68
+ }
69
+
70
+ module.exports = netcheck
71
+
72
+
package/lib/aevent.js ADDED
@@ -0,0 +1,18 @@
1
+ class asEventEmitter {
2
+ constructor() {
3
+ this._acall = {}
4
+ }
5
+
6
+ async emit(ev, ...argv) {
7
+ if (!this._acall[ev]) return;
8
+ const list = this._acall[ev];
9
+ for (let call of list) await call(...argv);
10
+ }
11
+
12
+ on(ev, cb) {
13
+ if (!this._acall[ev]) this._acall[ev] = []
14
+ this._acall[ev].push(cb)
15
+ }
16
+ }
17
+
18
+ module.exports = asEventEmitter;
package/lib/exec.js ADDED
@@ -0,0 +1,61 @@
1
+ const { spawn } = require('child_process');
2
+
3
+ async function detachedExec(cmd, detached = true) {
4
+ return new Promise((resolve) => {
5
+ // Split the command into the executable and its arguments
6
+ const [command, ...args] = cmd.split(' ')
7
+
8
+ // Launch the detached process
9
+ const child = spawn(command, args, {
10
+ detached, // Detach the child process from the parent
11
+ stdio: ['ignore', 'pipe', 'pipe'], // Ignore stdin and stderr, capture stdout
12
+ })
13
+
14
+ let stdout = ''
15
+ let stderr = ''
16
+
17
+ // Capture stdout
18
+ child.stdout.on('data', (data) => {
19
+ stdout += data.toString();
20
+ })
21
+
22
+ child.stderr.on('data', (data) => {
23
+ stderr += data.toString();
24
+ })
25
+
26
+ // Handle process completion
27
+ child.on('close', (code) => {
28
+ resolve({
29
+ stdout: stdout.trim(),
30
+ stderr: stderr.trim(),
31
+ error: code, // If code is 0, no error; otherwise, return the error code
32
+ })
33
+ })
34
+
35
+ // Handle process errors
36
+ child.on('error', (err) => {
37
+ resolve({
38
+ stdout: '',
39
+ stderr: stderr.trim(),
40
+ error: err, // Default error code for execution failure
41
+ })
42
+ })
43
+
44
+ // Actually detach the process
45
+ if (detached === true)
46
+ child.unref()
47
+ })
48
+ }
49
+
50
+ module.exports = detachedExec
51
+
52
+ // async function init() {
53
+ // async function para() {
54
+ // const ret = await detachedExec("ping -c 5 9.9.9.9")
55
+ // console.log(ret)
56
+ // }
57
+ // para()
58
+ // para()
59
+ // }
60
+
61
+ // init()
package/lib/index.js ADDED
@@ -0,0 +1,4 @@
1
+ module.exports = {
2
+ exec: require("./exec"),
3
+ aevent: require("./aevent"),
4
+ }
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "jetcheck",
3
+ "version": "1.0.1",
4
+ "description": "Javascript based Network & System checker",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "bin": {
10
+ "jetcheck": "bin/index.js"
11
+ },
12
+ "repository": "https://github.com/mykiimike/jetcheck",
13
+ "author": "Michael VERGOZ",
14
+ "license": "GPL-3.0",
15
+ "dependencies": {
16
+ "axios": "^1.7.9",
17
+ "ejs": "^3.1.10",
18
+ "mkdirp": "^3.0.1",
19
+ "subcommander": "^1.0.0"
20
+ },
21
+ "devDependencies": {
22
+ "semver": "^7.6.3"
23
+ }
24
+ }
@@ -0,0 +1,13 @@
1
+ const axios = require('axios')
2
+
3
+ module.exports = async function (kernel) {
4
+ kernel.tests.plugins.cmd = {
5
+ toString: function (options) {
6
+ return (`cmd(${options.cmd})`)
7
+ },
8
+ trigger: async function (task, state) {
9
+ await kernel.lib.exec(task.cmd)
10
+ }
11
+ }
12
+ console.log("Loading CMD plugin")
13
+ }
@@ -0,0 +1,25 @@
1
+ const axios = require('axios');
2
+
3
+ module.exports = async function (kernel) {
4
+ kernel.tests.plugins.http = {
5
+ toString: function (options) {
6
+ return (`http(${options.url})`)
7
+ },
8
+ run: async function (options, task, state) {
9
+ async function check(url) {
10
+ try {
11
+ await axios.get(url, {
12
+ timeout: options.timeout ? options.timeout : 5000,
13
+ validateStatus: () => true,
14
+ });
15
+ return null;
16
+ } catch (error) {
17
+ return error.message;
18
+ }
19
+ }
20
+ return (await check(options.url))
21
+
22
+ }
23
+ }
24
+ console.log("Loading HTTP plugin")
25
+ }
@@ -0,0 +1,17 @@
1
+ const axios = require('axios');
2
+
3
+ module.exports = async function (kernel) {
4
+ kernel.tests.plugins.ping = {
5
+ toString: function (options) {
6
+ return (`ping(${options.host})`)
7
+ },
8
+ run: async function (options, task, state) {
9
+ const count = options.count ? options.count : 5
10
+ const ret = await kernel.lib.exec(`ping -c ${count} ${options.host}`)
11
+ if(ret.error > 0)
12
+ return("Host down")
13
+ return(null)
14
+ }
15
+ }
16
+ console.log("Loading PING plugin")
17
+ }
@@ -0,0 +1,146 @@
1
+ const path = require("path")
2
+ const { start } = require("repl")
3
+
4
+ module.exports = async function (kernel) {
5
+ kernel.tests = {
6
+ plugins: {},
7
+ states: {}
8
+ }
9
+
10
+ // tester
11
+ kernel.config.modules.push(__dirname + "/builtin/http.js")
12
+ kernel.config.modules.push(__dirname + "/builtin/ping.js")
13
+
14
+ // triggers
15
+ kernel.config.modules.push(__dirname + "/builtin/cmd.js")
16
+
17
+ kernel.events.on("kernel.init", async function () {
18
+
19
+ if (kernel.options.headless === true)
20
+ return
21
+
22
+
23
+
24
+ // prepare initial states
25
+ for (var testName in kernel.config.tests) {
26
+ const test = kernel.config.tests[testName]
27
+
28
+ if (!test.interval)
29
+ test.interval = 5
30
+ test.interval = test.interval * 1000
31
+
32
+ kernel.tests.states[testName] = {
33
+ status: "INIT",
34
+ mode: "INIT",
35
+ startTime: Date.now(),
36
+ lastTime: 0,
37
+ original: [],
38
+ lastSucceed: [],
39
+ lastFailed: [],
40
+ succeed: [],
41
+ failed: []
42
+ }
43
+ const state = kernel.tests.states[testName]
44
+
45
+ if(test.init.toLowerCase() === "working")
46
+ state.mode = "WORKING"
47
+ else if(test.init.toLowerCase() === "degraded")
48
+ state.mode = "DEGRADED"
49
+ else
50
+ state.mode = "WORKING"
51
+
52
+ // prepare test pipelines
53
+ for (var task of test.tasks) {
54
+ if (!kernel.tests.plugins[task.type]) {
55
+ console.log(`Test plugin not found "${task.type}"`)
56
+ continue
57
+ }
58
+ if (!kernel.tests.plugins[task.type].run) {
59
+ console.log(`Plugin "${task.type}" cannot run test`)
60
+ continue
61
+ }
62
+ state.original.push(task)
63
+ }
64
+
65
+ console.log(`Test "${testName}" loaded`)
66
+
67
+ }
68
+
69
+ async function check() {
70
+ for (let testName in kernel.config.tests) {
71
+ const test = kernel.config.tests[testName]
72
+ const state = kernel.tests.states[testName]
73
+
74
+ // console.log(`name=${testName} status=${state.status} mode=${state.mode} elapsed=${test.interval - (Date.now() - state.startTime)}ms succeed=${state.lastSucceed.length} failed=${state.lastFailed.length}`)
75
+
76
+ if (state.status === "RUNNING")
77
+ continue;
78
+
79
+ if (Date.now() - state.lastTime < test.interval)
80
+ continue;
81
+
82
+ state.startTime = Date.now()
83
+ state.status = "RUNNING"
84
+ state.current = [...state.original]
85
+ state.lastSucceed = state.succeed
86
+ state.lastFailed = state.failed
87
+ state.succeed = []
88
+ state.failed = []
89
+
90
+ async function pop() {
91
+ const task = state.current.pop()
92
+ if (!task) {
93
+
94
+ if(state.succeed.length === 0) {
95
+ if(state.mode === "WORKING") {
96
+ console.log(`System "${testName}" is degraded`)
97
+ if(test.failed) {
98
+ for(var item of test.failed) {
99
+ const module = kernel.tests.plugins[item.type]
100
+ if(module && module.trigger)
101
+ await module.trigger(item, state)
102
+ }
103
+ }
104
+ }
105
+ state.mode = "DEGRADED"
106
+ }
107
+ else {
108
+ if(state.mode === "DEGRADED") {
109
+ console.log(`System ${testName} is restored`)
110
+ if(test.succeed) {
111
+ for(var item of test.succeed) {
112
+ const module = kernel.tests.plugins[item.type]
113
+ if(module && module.trigger)
114
+ await module.trigger(item, state)
115
+ }
116
+ }
117
+ }
118
+ state.mode = "WORKING"
119
+ }
120
+
121
+ state.lastSucceed = state.succeed
122
+ state.lastFailed = state.failed
123
+
124
+ state.startTime = Date.now()
125
+ state.lastTime = Date.now()
126
+ state.status = "PENDING"
127
+ return
128
+ }
129
+
130
+ const module = kernel.tests.plugins[task.type]
131
+ // console.log(`Executing ${testName} > ${module.toString(task)}`)
132
+ const error = await module.run(task, test, state)
133
+ if (!error)
134
+ state.succeed.push(task)
135
+ else
136
+ state.failed.push({ ...task, error })
137
+
138
+ setTimeout(pop, 10)
139
+ }
140
+ setTimeout(pop, 10)
141
+ }
142
+ setTimeout(check, 1000)
143
+ }
144
+ check()
145
+ })
146
+ }
package/version.js ADDED
@@ -0,0 +1,34 @@
1
+ const fs = require("fs")
2
+ const util = require('util')
3
+ const semver = require('semver');
4
+ const exec = util.promisify(require('child_process').exec)
5
+
6
+ const gitRepo = "./"
7
+
8
+ const isoDate = new Date().toISOString().slice(0, 10);
9
+
10
+ async function safeExec(cmd, ignore) {
11
+ console.log(cmd)
12
+ try {
13
+ const ret = await exec(cmd)
14
+ if (ret.stderr.length > 0) return ({ error: ret.stderr.split("\n")[0] })
15
+ if (ret.stdout.length === 0) return ({})
16
+ return ({ data: JSON.parse(ret.stdout) })
17
+ } catch (e) {
18
+ if (ignore !== true) return ({ error: e.message })
19
+ return ({})
20
+ }
21
+ }
22
+
23
+ (async () => {
24
+ const packageData = require(gitRepo + '/package.json');
25
+ packageData.version = semver.inc(packageData.version, 'patch');
26
+ fs.writeFileSync(gitRepo + '/package.json', JSON.stringify(packageData, null, " "));
27
+
28
+ await safeExec(`cd ${gitRepo} && git commit -m "Release ${packageData.version}" -a`)
29
+
30
+ await safeExec(`cd ${gitRepo} && git push`)
31
+ await safeExec(`cd ${gitRepo} && git tag ${packageData.version}`)
32
+ await safeExec(`cd ${gitRepo} && git push --tags`)
33
+ await safeExec(`cd ${gitRepo} && npm publish`)
34
+ })();