Fixed last few issues and bugs

This commit is contained in:
Jonatan Nilsson 2020-09-12 20:31:36 +00:00
parent aca7e0d535
commit 8952c93f2c
8 changed files with 101 additions and 20 deletions

1
.gitignore vendored
View file

@ -107,6 +107,7 @@ dist
db.json db.json
package-lock.json package-lock.json
daemon
app/* app/*
manage/* manage/*
dev/public/main.js dev/public/main.js

Binary file not shown.

View file

@ -1,10 +1,14 @@
import http from 'http' import http from 'http'
import https from 'https' import https from 'https'
import fs from 'fs' import fs from 'fs'
import { URL } from 'url' import url from 'url'
export function request(path, filePath = null, redirects = 0) { export function request(path, filePath = null, redirects, returnText = false) {
let parsed = new URL(path) let newRedirects = redirects + 1
if (!path || !path.startsWith('http')) {
return Promise.reject(new Error('URL was empty or missing http in front'))
}
let parsed = new url.URL(path)
let h let h
if (parsed.protocol === 'https:') { if (parsed.protocol === 'https:') {
@ -25,6 +29,7 @@ export function request(path, filePath = null, redirects = 0) {
'User-Agent': 'TheThing/service-core', 'User-Agent': 'TheThing/service-core',
Accept: 'application/vnd.github.v3+json' Accept: 'application/vnd.github.v3+json'
}, },
timeout: returnText ? 5000 : 60000,
hostname: parsed.hostname hostname: parsed.hostname
}, function(res) { }, function(res) {
let output = '' let output = ''
@ -38,12 +43,19 @@ export function request(path, filePath = null, redirects = 0) {
} }
res.on('end', function() { res.on('end', function() {
if (res.statusCode >= 300 && res.statusCode < 400) { if (res.statusCode >= 300 && res.statusCode < 400) {
if (redirects > 5) { if (newRedirects > 5) {
return reject(new Error(`Too many redirects (last one was ${res.headers.location})`)) return reject(new Error(`Too many redirects (last one was ${res.headers.location})`))
} }
return resolve(request(res.headers.location, filePath, redirects + 1)) if (!res.headers.location) {
return reject(new Error('Redirect returned no path in location header'))
}
if (res.headers.location.startsWith('http')) {
return resolve(request(res.headers.location, filePath, newRedirects, returnText))
} else {
return resolve(request(url.resolve(path, res.headers.location), filePath, newRedirects, returnText))
}
} else if (res.statusCode >= 400) { } else if (res.statusCode >= 400) {
return reject(new Error(`HTTP Error ${statusCode}: ${output}`)) return reject(new Error(`HTTP Error ${res.statusCode}: ${output}`))
} }
resolve({ resolve({
statusCode: res.statusCode, statusCode: res.statusCode,
@ -54,10 +66,13 @@ export function request(path, filePath = null, redirects = 0) {
}) })
}) })
req.on('error', reject) req.on('error', reject)
req.on('timeout', function(err) {
reject(err)
})
}) })
req.end() req.end()
}).then(function(res) { }).then(function(res) {
if (!filePath) { if (!filePath && !returnText) {
try { try {
res.body = JSON.parse(res.body) res.body = JSON.parse(res.body)
} catch(e) { } catch(e) {

View file

@ -8,20 +8,24 @@ const fsp = fs.promises
export default class Core extends EventEmitter{ export default class Core extends EventEmitter{
constructor(util, config, db, log, closeCb) { constructor(util, config, db, log, closeCb) {
super() super()
process.stdin.resume()
this.http = new HttpServer() this.http = new HttpServer()
this.util = util this.util = util
this.config = config this.config = config
this.db = db this.db = db
this.log = log this.log = log
this._close = closeCb this._close = closeCb
this._activeCrashHandler = null
this.appRunning = false this.appRunning = false
this.manageRunning = false this.manageRunning = false
this._appUpdating = { this._appUpdating = {
fresh: true,
updating: false, updating: false,
starting: false, starting: false,
logs: '', logs: '',
} }
this._manageUpdating = { this._manageUpdating = {
fresh: true,
updating: false, updating: false,
starting: false, starting: false,
logs: '', logs: '',
@ -114,6 +118,9 @@ export default class Core extends EventEmitter{
if (fs.existsSync(this.util.getPathFromRoot(`./${name}/` + version.name))) { if (fs.existsSync(this.util.getPathFromRoot(`./${name}/` + version.name))) {
await this.util.runCommand('rmdir', ['/S', '/Q', `"${this.util.getPathFromRoot(`./${name}/` + version.name)}"`]) await this.util.runCommand('rmdir', ['/S', '/Q', `"${this.util.getPathFromRoot(`./${name}/` + version.name)}"`])
} }
if (!fs.existsSync(this.util.getPathFromRoot(`./${name}/`))) {
await fsp.mkdir(this.util.getPathFromRoot(`./${name}/`))
}
try { try {
await fsp.mkdir(this.util.getPathFromRoot(`./${name}/` + version.name)) await fsp.mkdir(this.util.getPathFromRoot(`./${name}/` + version.name))
} catch(err) { } catch(err) {
@ -195,7 +202,8 @@ export default class Core extends EventEmitter{
this.logActive(name, active, `[${name}] Finding available version of ${name}\n`) this.logActive(name, active, `[${name}] Finding available version of ${name}\n`)
for (let i = 0; i < history.length; i++) { for (let i = 0; i < history.length; i++) {
if (history[i].stable < 0) { if ((history[i].stable === -1 && !active.fresh)
|| (history[i].stable < -1)) {
this.logActive(name, active, `[${name}] Skipping version ${history[i].name} due to marked as unstable\n`) this.logActive(name, active, `[${name}] Skipping version ${history[i].name} due to marked as unstable\n`)
continue continue
} }
@ -208,11 +216,16 @@ export default class Core extends EventEmitter{
if (running) { if (running) {
history[i].stable = 1 history[i].stable = 1
} else { } else {
history[i].stable = -1 if (active.fresh || history[i].stable === -1) {
history[i].stable = -2
} else {
history[i].stable = -1
}
await this.db.set(`core.${name}Active`, null) await this.db.set(`core.${name}Active`, null)
.write() .write()
this.emit('dbupdated', {}) this.emit('dbupdated', {})
} }
active.fresh = false
await this.db.get(`core_${name}History`).updateById(history[i].id, history[i].stable).write() await this.db.get(`core_${name}History`).updateById(history[i].id, history[i].stable).write()
if (history[i].stable > 0) break if (history[i].stable > 0) break
@ -227,6 +240,17 @@ export default class Core extends EventEmitter{
active.starting = false active.starting = false
} }
programCrashed(name, version, active, oldStable) {
let newStable = -2
console.log('EXITING:', oldStable, active)
if (oldStable === 0 && !active.fresh) {
newStable = -1
}
let temp = this.db.get(`core_${name}History`).getById(version).set('stable', newStable )
temp.value() // Trigger update on __wrapped__
fs.writeFileSync(this.db.adapterFilePath, JSON.stringify(temp.__wrapped__, null, 2))
}
async tryStartProgramVersion(name, active, version) { async tryStartProgramVersion(name, active, version) {
if (!version) return false if (!version) return false
this.logActive(name, active, `[${name}] Attempting to start ${version}\n`) this.logActive(name, active, `[${name}] Attempting to start ${version}\n`)
@ -242,10 +266,15 @@ export default class Core extends EventEmitter{
this.log.error(err, `Failed to load ${indexPath}`) this.log.error(err, `Failed to load ${indexPath}`)
return false return false
} }
let checkTimeout = null let checkTimeout = null
let oldStable = this.db.get(`core_${name}History`).getById(version).value().stable
this._activeCrashHandler = this.programCrashed.bind(this, name, version, active, oldStable)
process.once('exit', this._activeCrashHandler)
try { try {
let port = name === 'app' ? this.config.port : this.config.managePort
await new Promise((res, rej) => { await new Promise((res, rej) => {
let checkTimeout = setTimeout(function() { checkTimeout = setTimeout(function() {
rej(new Error('Program took longer than 60 seconds to resolve promise')) rej(new Error('Program took longer than 60 seconds to resolve promise'))
}, 60 * 1000) }, 60 * 1000)
@ -253,14 +282,20 @@ export default class Core extends EventEmitter{
try { try {
this.http.setContext(name) this.http.setContext(name)
this.startModule(module, name === 'app' ? this.config.port : this.config.managePort) this.startModule(module, port)
.then(res, rej) .then(res, rej)
} catch (err) { } catch (err) {
rej(err) rej(err)
} }
}) })
clearTimeout(checkTimeout)
this.logActive(name, active, `[${name}] Testing out module port ${version}\n`)
await this.checkProgramRunning(name, active, port)
process.off('exit', this._activeCrashHandler)
} catch (err) { } catch (err) {
clearTimeout(checkTimeout) clearTimeout(checkTimeout)
process.off('exit', this._activeCrashHandler)
await this.http.closeServer(name) await this.http.closeServer(name)
this.logActive(name, active, `[${name}] Error starting\n`, true) this.logActive(name, active, `[${name}] Error starting\n`, true)
@ -268,15 +303,12 @@ export default class Core extends EventEmitter{
this.log.error(err, `Failed to start ${name}`) this.log.error(err, `Failed to start ${name}`)
return false return false
} }
clearTimeout(checkTimeout) this._activeCrashHandler = null
this.logActive(name, active, `[${name}] Successfully started version ${version}\n`) this.logActive(name, active, `[${name}] Successfully started version ${version}\n`)
await this.db.set(`core.${name}Active`, version) await this.db.set(`core.${name}Active`, version)
.write() .write()
let port = name === 'app' ? this.config.port : this.config.managePort
this.logActive(name, active, `[${name}] Checking if listening to port ${port}\n`)
if (name === 'app') { if (name === 'app') {
this.appRunning = true this.appRunning = true
} else { } else {
@ -289,6 +321,26 @@ export default class Core extends EventEmitter{
return true return true
} }
async checkProgramRunning(name, active, port) {
let start = new Date()
let error = null
let success = false
while (new Date() - start < 10 * 1000) {
try {
let check = await request(`http://localhost:${port}`, null, 0, true)
success = true
break
} catch(err) {
this.logActive(name, active, `[${name}:${port}] ${err.message}, retrying in 3 seconds\n`)
error = err
await new Promise(function(res) { setTimeout(res, 3000)})
}
}
if (success) return true
throw error || new Error('Checking server failed')
}
async updateProgram(name) { async updateProgram(name) {
if (!this.config[name + 'Repository']) { if (!this.config[name + 'Repository']) {
if (name === 'app') { if (name === 'app') {
@ -302,6 +354,11 @@ export default class Core extends EventEmitter{
} }
let active = this.getActive(name) let active = this.getActive(name)
let oldLogs = active.logs || ''
if (oldLogs) {
oldLogs += '\n'
}
active.logs = ''
active.updating = true active.updating = true
this.emit('statusupdated', {}) this.emit('statusupdated', {})
@ -329,7 +386,7 @@ export default class Core extends EventEmitter{
this.logActive(name, active, '\n', true) this.logActive(name, active, '\n', true)
this.logActive(name, active, `[Error] Exception occured while updating ${name}\n`, true) this.logActive(name, active, `[Error] Exception occured while updating ${name}\n`, true)
this.logActive(name, active, err.stack, true) this.logActive(name, active, err.stack, true)
this.log.error(err, 'Error while updating ' + name) this.log.error('Error while updating ' + name, err)
} }
active.updating = false active.updating = false
if (version && !found) { if (version && !found) {
@ -341,9 +398,11 @@ export default class Core extends EventEmitter{
description: version.description, description: version.description,
logs: active.logs, logs: active.logs,
stable: 0, stable: 0,
installed: installed, installed: installed && installed.toISOString(),
}).write() }).write()
} }
active.logs = oldLogs + active.logs
this.emit(name + 'log', active)
this.emit('statusupdated', {}) this.emit('statusupdated', {})
} }

View file

@ -137,6 +137,7 @@ export default function GetDB(util, log) {
return lowdb(adapter) return lowdb(adapter)
.then(function(db) { .then(function(db) {
db._.mixin(lodashId) db._.mixin(lodashId)
db.adapterFilePath = util.getPathFromRoot('./db.json')
db.defaults({ db.defaults({
core: { core: {

View file

@ -19,6 +19,7 @@ export default class HttpServer {
if (name !== 'app' && name !== 'manage' && name !== 'dev') { if (name !== 'app' && name !== 'manage' && name !== 'dev') {
throw new Error('Cannot call setContext with values other than app or manage') throw new Error('Cannot call setContext with values other than app or manage')
} }
this._context = name
} }
createServer(opts, listener) { createServer(opts, listener) {
@ -45,7 +46,7 @@ export default class HttpServer {
} }
closeServer(name) { closeServer(name) {
if (!this.active[name]) return if (!this.active[name]) return console.log('no active found with name', name, this.active)
return new Promise((res, rej) => { return new Promise((res, rej) => {
this.sockets[name].forEach(function(socket) { this.sockets[name].forEach(function(socket) {
@ -55,7 +56,9 @@ export default class HttpServer {
this.active[name].close(function(err) { this.active[name].close(function(err) {
if (err) return rej(err) if (err) return rej(err)
res()
// Waiting 1 second for it to close down
setTimeout(res, 1000)
}) })
}) })
} }

View file

@ -1 +1,2 @@
node service\install.mjs node service\install.mjs
PAUSE

View file

@ -1,5 +1,6 @@
import path from 'path' import path from 'path'
import { readFileSync } from 'fs' import { readFileSync } from 'fs'
import { fileURLToPath } from 'url'
import nodewindows from 'node-windows' import nodewindows from 'node-windows'
function getPathFromRoot(add) { function getPathFromRoot(add) {