-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
227 lines (199 loc) · 7.23 KB
/
index.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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
const exec = require('child_process').exec;
const express = require('express');
const fs = require('fs');
const path = require('path');
let Rollbar;
let rollbar;
try {
Rollbar = require('rollbar');
} catch (error) {
}
if (Rollbar) {
rollbar = new Rollbar({
accessToken: '006dc99967de4f7a86715216e779d6c3',
captureUncaught: true,
environment: 'production',
captureUnhandledRejections: true
});
}
const app = express();
app.set('view engine', 'ejs');
const getVersions = async () => {
return new Promise((resolve, reject) => {
fs.readdir(path.join(__dirname, 'versions'), {
encoding: 'utf-8'
}, (err, files) => {
if (err) {
if (rollbar) rollbar.error(err);
return reject(err);
}
resolve(files.map(file => file.replace('.json', '')));
});
});
};
const getServerVersion = async () => {
return new Promise((resolve) => {
execCmd('git rev-parse HEAD')
.then(resolve)
.catch(() => resolve('?'));
});
};
const getClientVersion = async () => {
return new Promise((resolve) => {
execCmd('cd /opt/evnotipi && sudo git rev-parse HEAD')
.then(resolve)
.catch(() => resolve('?'));
});
};
const getVersion = async (version) => {
return new Promise(async (resolve, reject) => {
const versions = await getVersions();
if (!versions.includes(version)) return reject(404);
fs.readFile(path.join(__dirname, 'versions', `${version}.json`), {
encoding: 'utf-8'
}, (err, data) => {
if (err || !data) {
if (rollbar) rollbar.error(err || 'no data for version');
return reject(err);
}
resolve(JSON.parse(data));
});
});
};
const execCmd = async (cmd) => {
return new Promise((resolve, reject) => {
exec(cmd, (err, stdout, stderr) => {
// some commands unfortunately go to stderr, even if there are no errors..
// if (err || stderr) {
// if (rollbar) rollbar.error(err || stderr);
// return reject(err || stderr);
// }
resolve(stdout);
});
});
};
// GET / -> html -> select versions
app.get('/', async (req, res, next) => {
try {
const [
versions,
serverVersion,
clientVersion
] = await Promise.all([getVersions(), getServerVersion(), getClientVersion()]);
res.render('index', {
versions: versions || [],
serverVersion: serverVersion || '?',
clientVersion: clientVersion || '?'
});
} catch (error) {
if (rollbar) rollbar.error(error);
setTimeout(async () => {
await execCmd('pm2 flush && pm2 restart all');
await execCmd('sudo systemctl restart pnpupdater.service');
}, 3000);
next(error);
}
});
// GET /client/status -> status of evnotipi service
app.get('/client/status', async (req, res, next) => {
try {
res.send(await execCmd('sudo systemctl status evnotipi.service'));
if (rollbar) rollbar.info('Client status requested');
} catch (error) {
if (rollbar) rollbar.error(error);
next(error);
}
});
// POST /update -> git pull updater
app.post('/update', async (req, res, next) => {
try {
await execCmd('sudo mount -o remount,rw /');
await execCmd('git pull');
await execCmd('npm i');
await execCmd('sync');
res.sendStatus(200);
await execCmd('sudo mount -o remount,ro /');
if (rollbar) rollbar.info('Server updated');
setTimeout(async() => {
await execCmd('pm2 flush && pm2 restart all');
await execCmd('sudo systemctl restart pnpupdater.service');
await execCmd('sudo systemctl restart systemd-journald');
}, 3000);
} catch (error) {
if (rollbar) rollbar.error(error);
next(error);
}
});
// POST /update/:version -> update evnotipi with cmd
app.post('/update/:version', async (req, res, next) => {
let currentCommit;
try {
let version;
await execCmd('sudo mount -o remount,rw /');
if (!(version = await getVersion(req.params.version))) return res.sendStatus(404);
currentCommit = await execCmd('cd /opt/evnotipi && sudo git rev-parse HEAD');
await execCmd('cd /opt/evnotipi && sudo git checkout master && sudo git pull --recurse-submodules');
await execCmd(`cd /opt/evnotipi && sudo git checkout ${version.commit}`);
for (const key in version.commands) {
if (version.commands.hasOwnProperty(key)) {
const command = version.commands[key];
await execCmd(command);
}
}
await execCmd('sync');
await execCmd('sudo systemctl restart evnotipi.service');
await execCmd('sudo mount -o remount,ro /');
res.sendStatus(200);
if (rollbar) rollbar.info('Client updated');
} catch (error) {
if (rollbar) rollbar.error(error);
if (currentCommit) {
// rollback to last working version
try {
await execCmd(`cd /opt/evnotipi && sudo git checkout ${currentCommit}`);
if (rollbar) rollbar.info('Client update failed, rolled back');
await execCmd('sync');
await execCmd('sudo systemctl restart evnotipi.service');
return next(new Error('Update failed. Rolled back to previous working version'));
} catch (error) {
if (rollbar) rollbar.error(error);
return next(error);
}
}
next(error);
}
});
app.post('/volatilestorage', async (req, res, next) => {
await execCmd('sudo mount -o remount,rw /');
await execCmd(`sudo sed -i 's/#Storage=auto/Storage=volatile/g' /etc/systemd/journald.conf`);
await execCmd(`sudo sed -i 's/#RuntimeMaxUse=/RuntimeMaxUse=10M/g' /etc/systemd/journald.conf`);
await execCmd('sudo mount -o remount,ro /');
res.sendStatus(200);
});
app.post('/debug/on', async (req, res, next) => {
await execCmd('sudo mount -o remount,rw /');
await execCmd(`sudo sed -i -e 's/loglevel: 30/loglevel: 10/g' /opt/evnotipi/config.yaml`);
await execCmd('sudo mount -o remount,ro /');
res.sendStatus(200);
});
app.post('/debug/off', async (req, res, next) => {
await execCmd('sudo mount -o remount,rw /');
await execCmd(`sudo sed -i -e 's/loglevel: 10/loglevel: 30/g' /opt/evnotipi/config.yaml`);
await execCmd('sudo mount -o remount,ro /');
res.sendStatus(200);
});
app.delete('/submodule', async (req, res, next) => {
await execCmd('sudo mount -o remount,rw /');
await execCmd(`sudo rm -rf /opt/evnotipi/EVNotifyAPI`);
await execCmd('sudo mount -o remount,ro /');
res.sendStatus(200);
});
app.post('/client/restart', async (req, res, next) => {
await execCmd(`sudo systemctl restart evnotipi.service`);
res.sendStatus(200);
});
app.get('/client/config', async (req, res, next) => {
res.send(await execCmd(`sudo cat /opt/evnotipi/config.yaml`));
});
app.listen(3333, () => console.log('[HTTP] Server started on port 3333'));
module.exports = app;