-
Notifications
You must be signed in to change notification settings - Fork 3
/
resetHandler.js
136 lines (110 loc) · 5.05 KB
/
resetHandler.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// separate script for sudo permisions if necessary
// can run at start with npm run start-all and this in package.json to give just this script sudo access
// "scripts": {
// "start": "npm link balanceofsatoshis && node index.js",
// "start-all": "sudo -b node resetHandler > /dev/null 2>&1 && sudo -k && npm run start",
// "start-all-log": "sudo -b node resetHandler >resetLogs.txt 2>&1 && sudo -k && npm run start"
// },
import { spawn } from 'child_process'
import fs from 'fs'
const RESET_RESULT_PATH = 'resetDone.json' // file has info on last job done
const RESET_HANDLER_ID_PATH = 'resetID.json' // keeps track of latest loop so just 1 runs
const RESET_REQUEST_PATH = 'resetRequest.json' // this file will stop and start node to reset it
const SHUTDOWN_REQUEST_PATH = 'shutdownRequest.json' // this will stop node and will not start it again
const RESET_STOP_PATH = 'resetStop.json' // this file's existence will always terminate this script
const LOOP_TIME_MS = 15 * 1000 // how often to check for request
const MINUTES_FOR_SHELL_TIMEOUT = 20 // minutes before shell process terminates
// handle resetting services by running the following
const COMMAND_TO_RESET_NODE = 'sudo /home/me/Umbrel/scripts/stop && sleep 10 && sudo /home/me/Umbrel/scripts/start'
// this is 1 time graceful shut down for low power scenario
const COMMAND_TO_SHUTDOWN_NODE = 'sudo /home/me/Umbrel/scripts/stop'
// id = timestamp at initialization
// avoid duplicate handlers
// older handler seeing new handler exists
const id = Date.now()
console.log(`${id} resetHandler() starting`)
// run the check in a loop
const triggerCheckLoop = async () => {
await runCheck()
await new Promise(resolve => setTimeout(resolve, LOOP_TIME_MS))
triggerCheckLoop()
}
// checks if reset needed
const runCheck = async () => {
const now = Date.now()
const nowISO = new Date(now).toISOString()
// check if stop file exists
if (fs.existsSync(RESET_STOP_PATH)) {
console.log(`${nowISO} ${RESET_STOP_PATH} file observed, terminating`)
process.exit(0)
}
// make sure this is the oldest process
try {
const idFile = JSON.parse(fs.readFileSync(RESET_HANDLER_ID_PATH))
// if this process is older than one in file, terminate to avoid doubles
if (idFile.id && id < idFile.id) {
console.log(`${nowISO} ${RESET_HANDLER_ID_PATH} file has newer id, terminating`)
process.exit(0)
}
} catch (e) {}
// write surviving id to file
fs.writeFileSync(RESET_HANDLER_ID_PATH, JSON.stringify({ id }))
// check if requests to reset node exists
const resetRequestFound = fs.existsSync(RESET_REQUEST_PATH)
// new request so take action
if (resetRequestFound) {
console.log(`${nowISO} reset request file found ${RESET_REQUEST_PATH}`)
const res = await runShellCommand(COMMAND_TO_RESET_NODE, { log: true })
// record result
fs.writeFileSync(RESET_RESULT_PATH, JSON.stringify({ id, now, nowISO, res }, null, 2))
console.log(`${nowISO} reset done by ${id}`, res)
// get rid of request to show its done
fs.unlinkSync(RESET_REQUEST_PATH)
console.log(`${nowISO} reset request removed by ${id}`)
}
// check if requests to reset node exists
const shutdownRequestFound = fs.existsSync(SHUTDOWN_REQUEST_PATH)
// new request so take action
if (shutdownRequestFound) {
console.log(`${nowISO} shutdown request file found ${SHUTDOWN_REQUEST_PATH}`)
const res = await runShellCommand(COMMAND_TO_SHUTDOWN_NODE, { log: true })
// record result
fs.writeFileSync(RESET_RESULT_PATH, JSON.stringify({ id, now, nowISO, res }, null, 2))
console.log(`${nowISO} shutdown done by ${id}`, res)
// get rid of request to show its done
fs.unlinkSync(SHUTDOWN_REQUEST_PATH)
console.log(`${nowISO} shutdown request removed by ${id}`)
}
return null
}
// handles shell commands
const runShellCommand = async (command, { log = true, timeout = MINUTES_FOR_SHELL_TIMEOUT * 60 * 1000 } = {}) => {
let stdout = []
const stderr = []
return new Promise((resolve, reject) => {
const execShell = spawn(command, {
shell: true,
// stdio: 'inherit', // write out outputs
timeout
})
console.log(`Running "${command}" w/ PID ${execShell.pid} and ${timeout} ms timeout`)
execShell.stdout.on('data', d => {
stdout = [...stdout, ...String(d).split('\n').slice(0, -1)]
log && console.log(`Data :: PID ${execShell.pid} .stdout.on('data', d)=${typeof d}: ${d}`)
})
execShell.stderr.on('data', d => {
// stderr = [...stderr, ...String(d).split('\n').slice(0, -1)]
stdout = [...stdout, ...String(d).split('\n').slice(0, -1)]
log && console.log(`STDERR :: PID ${execShell.pid} .stderr.on('data', d)=${typeof d}: ${d}`)
})
execShell.on('close', c => {
log && console.log(`CLOSE :: PID ${execShell.pid} .on('close', c) closed with code: ${c}`)
resolve({ status: c, stdout, stderr })
})
execShell.on('error', d => {
log && console.log(`ERROR :: PID ${execShell.pid} .stderr.on('error', d)=${typeof d}: ${d}`)
reject(new Error({ status: d, stdout, stderr }))
})
})
}
triggerCheckLoop()