2021-04-26 21:56:48 -04:00
|
|
|
|
2021-04-24 00:42:01 -04:00
|
|
|
// imports that arent installed...
|
2021-04-11 19:53:39 -04:00
|
|
|
import { execSync, spawn } from 'child_process';
|
|
|
|
|
import { config } from '../src/lib/config/index.js';
|
2021-04-24 00:42:01 -04:00
|
|
|
import http from 'http';
|
|
|
|
|
import os from 'os';
|
|
|
|
|
import EventEmitter from 'events';
|
2021-04-11 19:53:39 -04:00
|
|
|
|
2021-04-24 00:42:01 -04:00
|
|
|
let external = {};
|
|
|
|
|
async function doExternalImports() {
|
|
|
|
|
external.Signale = (await import('signale')).default;
|
|
|
|
|
external.Datastore = (await import('nedb')).default;
|
|
|
|
|
external.express = (await import('express')).default;
|
|
|
|
|
external.Volatile = (await import('volatile')).default;
|
|
|
|
|
external.expressWs = (await import('express-ws')).default;
|
|
|
|
|
}
|
2021-04-11 20:36:31 -04:00
|
|
|
|
2021-04-24 00:42:01 -04:00
|
|
|
(async function bootloader() {
|
2021-04-26 21:56:48 -04:00
|
|
|
console.log('===================================');
|
|
|
|
|
console.log(' Valnet Relay Service Bootloader');
|
|
|
|
|
console.log('===================================');
|
|
|
|
|
console.log('These messages won\'t appear in the logs...')
|
|
|
|
|
console.log('installing packages...');
|
2021-04-24 00:42:01 -04:00
|
|
|
let yarnOutput = "";
|
|
|
|
|
try {
|
|
|
|
|
// sanity install packages...
|
|
|
|
|
// happens if a package is installed
|
|
|
|
|
// and used in this script, without
|
|
|
|
|
// restarting the service entirely
|
|
|
|
|
// between updates from github.
|
|
|
|
|
// this is also why we dynamically
|
|
|
|
|
// load the dependencies...
|
|
|
|
|
yarnOutput += execSync(`yarn`);
|
2021-04-26 21:56:48 -04:00
|
|
|
console.log(yarnOutput);
|
2021-04-24 00:42:01 -04:00
|
|
|
} catch {
|
|
|
|
|
enterRecoveryMode(yarnOutput);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
await doExternalImports();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
enterRecoveryMode(e.toString());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
startService();
|
|
|
|
|
})();
|
2021-04-11 19:53:39 -04:00
|
|
|
|
2021-04-24 00:42:01 -04:00
|
|
|
async function startService() {
|
|
|
|
|
const logLock = new external.Volatile({});
|
|
|
|
|
const log = external.Signale.scope('SRVC');
|
|
|
|
|
const branch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim();
|
|
|
|
|
let proc;
|
2021-04-26 21:56:48 -04:00
|
|
|
let suppressRestart = false;
|
|
|
|
|
process.on('SIGTERM', () => {
|
|
|
|
|
if(proc) {
|
|
|
|
|
logp('killing child process');
|
|
|
|
|
suppressRestart = true;
|
|
|
|
|
proc.kill();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
process.exit(0);
|
|
|
|
|
});
|
2021-04-24 00:42:01 -04:00
|
|
|
const logs = new external.Datastore({
|
|
|
|
|
filename: 'svc.log',
|
|
|
|
|
autoload: true
|
|
|
|
|
});
|
|
|
|
|
const app = external.express();
|
|
|
|
|
external.expressWs(app);
|
|
|
|
|
const logEvents = new EventEmitter();
|
2021-04-11 20:36:31 -04:00
|
|
|
|
2021-04-26 21:56:48 -04:00
|
|
|
logp('===================================');
|
2021-04-24 00:42:01 -04:00
|
|
|
logp('Starting Valnet Node as a Service!');
|
|
|
|
|
logp('Syncing to branch: ' + branch);
|
|
|
|
|
logp('===================================');
|
2021-04-26 21:56:48 -04:00
|
|
|
logp('Logging service started...');
|
|
|
|
|
|
|
|
|
|
(function update() {
|
|
|
|
|
try {
|
|
|
|
|
const remoteHash = execSync('git ls-remote https://github.com/marcus13345/valnet.git').toString()
|
|
|
|
|
.split('\n')
|
|
|
|
|
.filter(test => {
|
|
|
|
|
return test.trim().endsWith(branch);
|
|
|
|
|
})[0]
|
|
|
|
|
.split('\t')[0]
|
|
|
|
|
.trim();
|
|
|
|
|
const localHash = execSync(`git rev-parse ${branch}`).toString().trim();
|
|
|
|
|
if(remoteHash !== localHash) {
|
|
|
|
|
logp(`remote hash: ${remoteHash}`);
|
|
|
|
|
logp(`local hash: ${localHash}`);
|
|
|
|
|
|
|
|
|
|
logp('killing relay...');
|
|
|
|
|
try {
|
|
|
|
|
proc.kill();
|
|
|
|
|
} catch (e) {
|
|
|
|
|
logp('failed to kill active relay...', 'error');
|
|
|
|
|
logp(e, 'error');
|
|
|
|
|
}
|
2021-04-24 00:42:01 -04:00
|
|
|
}
|
2021-04-26 21:56:48 -04:00
|
|
|
} catch {}
|
|
|
|
|
setTimeout(update, 5000);
|
|
|
|
|
})();
|
2020-11-11 19:38:39 -05:00
|
|
|
|
2021-04-24 00:42:01 -04:00
|
|
|
(function keepAlive() {
|
|
|
|
|
proc = spawn('node', ['./relay/index.mjs'], {
|
|
|
|
|
stdio: 'pipe',
|
|
|
|
|
env: {
|
2021-04-26 21:56:48 -04:00
|
|
|
...process.env,
|
2021-04-24 00:42:01 -04:00
|
|
|
FORCE_COLOR: true
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
appendLogs('relay', 'STARTED', 'event');
|
2021-02-07 15:53:48 -05:00
|
|
|
|
2021-04-24 00:42:01 -04:00
|
|
|
proc.stdout.on('data', (data) => {
|
|
|
|
|
process.stdout.write(data);
|
|
|
|
|
appendLogs('relay', data.toString(), 'stdout');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
proc.stderr.on('data', (data) => {
|
|
|
|
|
process.stderr.write(data);
|
|
|
|
|
appendLogs('relay', data.toString(), 'stderr');
|
|
|
|
|
});
|
2020-11-11 19:38:39 -05:00
|
|
|
|
2021-04-24 00:42:01 -04:00
|
|
|
proc.on('exit', () => {
|
|
|
|
|
appendLogs('relay', 'STOPPED', 'event');
|
|
|
|
|
logp('relay exitted');
|
|
|
|
|
logp('attempting to fetch new version');
|
2020-11-11 22:25:08 -05:00
|
|
|
|
2021-04-24 00:42:01 -04:00
|
|
|
appendLogs('fetch', execSync(`git fetch`));
|
|
|
|
|
appendLogs('update', execSync(`git pull`));
|
|
|
|
|
appendLogs('yarn', execSync(`yarn`));
|
2020-11-11 22:25:08 -05:00
|
|
|
|
2021-04-26 21:56:48 -04:00
|
|
|
if(!suppressRestart) {
|
|
|
|
|
logp('restarting...');
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
keepAlive();
|
|
|
|
|
}, 1000);
|
|
|
|
|
}
|
2021-04-24 00:42:01 -04:00
|
|
|
})
|
|
|
|
|
})();
|
2020-11-11 19:38:39 -05:00
|
|
|
|
2021-04-24 00:42:01 -04:00
|
|
|
function logp(message, type = 'info') {
|
|
|
|
|
log[type](message);
|
|
|
|
|
appendLogs('service', message + '\n')
|
|
|
|
|
}
|
2020-11-11 19:38:39 -05:00
|
|
|
|
2021-04-24 00:42:01 -04:00
|
|
|
function appendLogs(source, data, type = 'output') {
|
|
|
|
|
logLock.lock(function(lock) {
|
|
|
|
|
const newDoc = {
|
2021-04-23 00:24:09 -04:00
|
|
|
message: data.toString(),
|
|
|
|
|
type: type,
|
|
|
|
|
src: source,
|
|
|
|
|
timestamp: new Date().getTime()
|
2021-04-24 00:42:01 -04:00
|
|
|
};
|
|
|
|
|
logEvents.emit('all', [newDoc]);
|
|
|
|
|
return new Promise(res => {
|
|
|
|
|
logs.insert(newDoc, (err, doc) => {
|
|
|
|
|
res(lock);
|
|
|
|
|
})
|
2021-04-23 00:24:09 -04:00
|
|
|
})
|
|
|
|
|
})
|
2021-04-24 00:42:01 -04:00
|
|
|
}
|
2020-11-11 19:38:39 -05:00
|
|
|
|
2021-04-24 00:42:01 -04:00
|
|
|
function getSessions() {
|
|
|
|
|
return new Promise(res => {
|
|
|
|
|
logs.find({
|
|
|
|
|
type: 'event',
|
|
|
|
|
message: { $in: ['STARTED', 'STOPPED'] }
|
|
|
|
|
}, {}, (err, docs) => {
|
|
|
|
|
const sessions = [];
|
|
|
|
|
let start = null;
|
|
|
|
|
for(const event of docs) {
|
|
|
|
|
if(event.message === 'STARTED') {
|
|
|
|
|
if (start !== null) {
|
|
|
|
|
sessions.push({
|
|
|
|
|
started: start,
|
|
|
|
|
stopped: event.timestamp
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
start = event.timestamp;
|
|
|
|
|
}
|
|
|
|
|
if(event.message === 'STOPPED' && start !== null) {
|
2021-04-23 00:24:09 -04:00
|
|
|
sessions.push({
|
|
|
|
|
started: start,
|
|
|
|
|
stopped: event.timestamp
|
|
|
|
|
});
|
2021-04-24 00:42:01 -04:00
|
|
|
start = null;
|
2021-04-23 00:24:09 -04:00
|
|
|
}
|
|
|
|
|
}
|
2021-04-24 00:42:01 -04:00
|
|
|
sessions.sort((a, b) => a.started > b.started);
|
|
|
|
|
res(sessions);
|
|
|
|
|
});
|
2021-04-23 00:24:09 -04:00
|
|
|
});
|
2021-04-24 00:42:01 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
app.get('/', (req, res) => {
|
|
|
|
|
res.end(`
|
|
|
|
|
<a href="/logs">Logs</a><br>
|
|
|
|
|
<a href="/api/sessions">Sessions</a><br>
|
|
|
|
|
<a href="/restart">Restart</a>
|
|
|
|
|
`);
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
app.get('/restart', async (req, res) => {
|
|
|
|
|
proc.kill();
|
|
|
|
|
res.redirect('/');
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
app.get('/logs', (req, res) => {
|
2021-04-26 21:56:48 -04:00
|
|
|
const sessions = getSessions();
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.get('/logs/:start', (req, res) => {
|
2021-04-24 00:42:01 -04:00
|
|
|
res.end(Template.realtimeLogs());
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
app.get('/logs/:start/:end', (req, res) => {
|
|
|
|
|
|
|
|
|
|
logs.find({
|
|
|
|
|
timestamp: { $gt: parseInt(req.params.time) }
|
|
|
|
|
}, {}).sort({
|
|
|
|
|
timestamp: -1
|
|
|
|
|
}).limit(100).exec((err, docs) => {
|
|
|
|
|
|
|
|
|
|
res.end(Template.logs(docs.reverse().map(v => v.message)));
|
|
|
|
|
|
|
|
|
|
if(err) {
|
|
|
|
|
res.end(err.toString());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// ${new Date(logItem.timestamp).toLocaleString().padStart(40)}:
|
|
|
|
|
res.end();
|
|
|
|
|
})
|
2021-04-23 00:24:09 -04:00
|
|
|
});
|
|
|
|
|
|
2021-04-24 00:42:01 -04:00
|
|
|
|
|
|
|
|
app.get('/api/sessions', async (req, res) => {
|
|
|
|
|
res.json(await getSessions());
|
2021-04-22 23:42:49 -04:00
|
|
|
})
|
2021-04-24 00:42:01 -04:00
|
|
|
|
|
|
|
|
app.ws('/api/logs', async (ws) => {
|
|
|
|
|
logEvents.on('all', broadcast);
|
|
|
|
|
function broadcast(docs) {
|
|
|
|
|
for(const doc of docs)
|
|
|
|
|
ws.send(doc.message);
|
|
|
|
|
}
|
|
|
|
|
ws.on('close', _ => {
|
|
|
|
|
logEvents.off('all', broadcast);
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.listen(config.ports.service);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
function enterRecoveryMode(message) {
|
|
|
|
|
http.createServer((req, res) => {
|
|
|
|
|
res.end(Template.recoveryMode(message));
|
|
|
|
|
}).listen(config.ports.service);
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-22 23:42:49 -04:00
|
|
|
const Template = {
|
2021-04-26 21:56:48 -04:00
|
|
|
style() {
|
|
|
|
|
return `<style> html { background: #0E1419; color: #F8F8F2; } </style>`
|
|
|
|
|
},
|
2021-04-22 23:42:49 -04:00
|
|
|
logs(messages) {
|
2021-04-24 00:42:01 -04:00
|
|
|
return `
|
|
|
|
|
<pre>
|
2021-04-22 23:42:49 -04:00
|
|
|
${messages.join('').replace(/\u001B\[.*?[A-Za-z]/g, '')}
|
2021-04-24 00:42:01 -04:00
|
|
|
</pre>
|
|
|
|
|
<br><br><br><br><br><br>
|
|
|
|
|
`;
|
|
|
|
|
},
|
|
|
|
|
realtimeLogs() {
|
|
|
|
|
return `
|
|
|
|
|
<pre></pre>
|
|
|
|
|
<br><br><br><br><br><br>
|
|
|
|
|
<script>
|
|
|
|
|
setInterval(_ => {
|
|
|
|
|
window.scrollTo(0,document.body.scrollHeight);
|
|
|
|
|
}, 100);
|
|
|
|
|
|
|
|
|
|
function connect() {
|
|
|
|
|
const pipe = new WebSocket(\`ws://\${location.host}/api/logs\`);
|
|
|
|
|
pipe.onclose = _ => {
|
|
|
|
|
setTimeout(connect, 100);
|
|
|
|
|
};
|
|
|
|
|
pipe.onmessage = evt => {
|
|
|
|
|
document.querySelector('pre').innerHTML += evt.data;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
connect();
|
|
|
|
|
|
|
|
|
|
</script>`;
|
2021-04-23 00:24:09 -04:00
|
|
|
},
|
|
|
|
|
Json(obj) {
|
|
|
|
|
return `<pre>
|
|
|
|
|
${JSON.stringify(obj, null, 2)}
|
|
|
|
|
</pre>`
|
2021-04-24 00:42:01 -04:00
|
|
|
},
|
|
|
|
|
recoveryMode(message) {
|
|
|
|
|
return `<h1>${os.hostname()} has entered recovery mode...</h1>
|
|
|
|
|
<h3>Last words:</h3>
|
|
|
|
|
<pre>${message}</pre>`
|
2021-04-22 23:42:49 -04:00
|
|
|
}
|
2021-04-24 00:42:01 -04:00
|
|
|
};
|