2022-03-10 11:06:17 +00:00
|
|
|
import cluster from 'cluster'
|
|
|
|
|
|
|
|
import { Low } from 'lowdb'
|
2022-02-04 09:33:03 +00:00
|
|
|
import Application from './application.mjs'
|
|
|
|
import Util from './util.mjs'
|
2022-02-11 13:59:10 +00:00
|
|
|
import getLog from './log.mjs'
|
|
|
|
import StaticProvider from './providers/static.mjs'
|
|
|
|
import GitProvider from './providers/git.mjs'
|
2022-02-04 09:33:03 +00:00
|
|
|
|
|
|
|
export default class Core {
|
|
|
|
static providers = new Map()
|
|
|
|
static addProvider(name, provider) {
|
|
|
|
if (!name || typeof(name) !== 'string')
|
|
|
|
throw new Error('addProvider name must be a string')
|
|
|
|
if (typeof(provider) !== 'function')
|
|
|
|
throw new Error(`addProvider ${name} provider must be a class`)
|
|
|
|
|
|
|
|
let test = new provider({})
|
|
|
|
if (typeof(test.checkConfig) !== 'function')
|
|
|
|
throw new Error(`addProvider ${name} provider class missing checkConfig`)
|
|
|
|
if (typeof(test.getLatestVersion) !== 'function')
|
|
|
|
throw new Error(`addProvider ${name} provider class missing getLatestVersion`)
|
|
|
|
|
|
|
|
Core.providers.set(name, provider)
|
|
|
|
}
|
|
|
|
|
2022-02-18 13:31:56 +00:00
|
|
|
constructor(db, util, log, restart = function() {}) {
|
2022-02-04 09:33:03 +00:00
|
|
|
// some sanity checks
|
|
|
|
if (!log || typeof(log) !== 'object') throw new Error('log parameter was invalid')
|
|
|
|
if (typeof(log.event) !== 'object') throw new Error('log parameter was invalid')
|
|
|
|
if (typeof(log.info) !== 'function'
|
|
|
|
|| typeof(log.warn) !== 'function'
|
|
|
|
|| typeof(log.error) !== 'function'
|
|
|
|
|| typeof(log.event.info) !== 'function'
|
|
|
|
|| typeof(log.event.warn) !== 'function'
|
|
|
|
|| typeof(log.event.error) !== 'function') throw new Error('log parameter was invalid')
|
|
|
|
if (!util || !(util instanceof Util)) throw new Error('util not instance of Util')
|
|
|
|
if (!db || !(db instanceof Low)) throw new Error('db not instance of Low')
|
2022-02-18 13:31:56 +00:00
|
|
|
if (typeof(restart) !== 'function') throw new Error('restart was not a function')
|
2020-09-07 00:47:53 +00:00
|
|
|
|
2022-02-14 08:15:50 +00:00
|
|
|
this.running = false
|
2020-09-09 15:41:05 +00:00
|
|
|
this.db = db
|
2022-02-04 09:33:03 +00:00
|
|
|
this.util = util
|
2020-09-09 15:41:05 +00:00
|
|
|
this.log = log
|
2022-02-18 13:31:56 +00:00
|
|
|
this.restart = restart
|
2022-02-04 09:33:03 +00:00
|
|
|
this.applications = []
|
|
|
|
this.applicationMap = new Map()
|
2022-02-15 11:28:30 +00:00
|
|
|
this._applicationFatalCrash = null
|
2022-03-10 11:06:17 +00:00
|
|
|
this.isSlave = cluster.isWorker
|
2020-09-07 00:47:53 +00:00
|
|
|
}
|
|
|
|
|
2022-02-04 09:33:03 +00:00
|
|
|
getApplication(name) {
|
|
|
|
return this.applicationMap.get(name)
|
2020-09-12 20:31:36 +00:00
|
|
|
}
|
|
|
|
|
2022-02-04 09:33:03 +00:00
|
|
|
async init() {
|
2022-02-11 13:59:10 +00:00
|
|
|
this.log.info(`Verifying config`)
|
2020-09-07 00:47:53 +00:00
|
|
|
|
2022-02-11 13:59:10 +00:00
|
|
|
this.util.verifyConfig(this.db.config)
|
2022-02-04 09:33:03 +00:00
|
|
|
let names = this.util.getAppNames(this.db.config)
|
2020-09-13 04:36:33 +00:00
|
|
|
|
2022-02-11 13:59:10 +00:00
|
|
|
this.log.info(`Found applications: ${names.join(', ')}.`)
|
|
|
|
|
2022-04-01 09:59:47 +00:00
|
|
|
let hasCluster = false
|
|
|
|
|
2022-02-04 09:33:03 +00:00
|
|
|
for (let name of names) {
|
2022-03-10 11:06:17 +00:00
|
|
|
if (this.isSlave && process.env.CLUSTER_APP_NAME !== name) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-02-08 06:35:36 +00:00
|
|
|
try {
|
|
|
|
let provConstructor = Core.providers.get(this.db.config[name].provider)
|
|
|
|
let provider = new provConstructor(this.db.config[name])
|
2022-03-10 11:06:17 +00:00
|
|
|
|
|
|
|
if (!this.isSlave) {
|
|
|
|
await provider.checkConfig(this.db.config[name])
|
|
|
|
}
|
|
|
|
|
|
|
|
let logName = name
|
|
|
|
if (this.isSlave && process.env.CLUSTER_APP_INDEX) {
|
|
|
|
logName += '-' + process.env.CLUSTER_APP_INDEX
|
|
|
|
}
|
2022-02-08 06:35:36 +00:00
|
|
|
|
|
|
|
let application = new Application({
|
|
|
|
db: this.db,
|
|
|
|
util: this.util,
|
2022-04-01 09:59:47 +00:00
|
|
|
log: getLog(logName, this.db.config[name].log || null, { name: name }),
|
2022-02-08 06:35:36 +00:00
|
|
|
core: this,
|
|
|
|
}, provider, name)
|
|
|
|
this.applications.push(application)
|
|
|
|
this.applicationMap.set(name, application)
|
2022-04-01 09:59:47 +00:00
|
|
|
|
|
|
|
if (this.db.config[name].cluster) {
|
|
|
|
hasCluster = true
|
|
|
|
}
|
2022-02-08 06:35:36 +00:00
|
|
|
} catch (err) {
|
|
|
|
this.log.error(err, `Error creating application ${name} with provider ${this.db.config[name].provider}: ${err.message}`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-01 09:59:47 +00:00
|
|
|
if (hasCluster && !this.isSlave) {
|
|
|
|
cluster.on('message', (worker, message) => {
|
|
|
|
// Some sanity checking
|
|
|
|
if (!message
|
|
|
|
|| typeof(message) !== 'object'
|
|
|
|
|| typeof(message.apptarget) !== 'string'
|
|
|
|
|| typeof(message.type) !== 'string'
|
|
|
|
|| typeof(message.payload) !== 'object'
|
|
|
|
|| !message.payload
|
|
|
|
) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let app = this.getApplication(message.apptarget)
|
|
|
|
let targetLog = null
|
|
|
|
if (app) {
|
|
|
|
targetLog = app.ctx.log
|
|
|
|
} else if (message.apptarget === this.db.config.name) {
|
|
|
|
targetLog = this.log
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!targetLog) return
|
|
|
|
|
|
|
|
if (message.type === 'newlog') {
|
|
|
|
targetLog.emit('newlog', message.payload)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-02-08 06:35:36 +00:00
|
|
|
if (names.length && !this.applications.length) {
|
2022-02-14 08:15:50 +00:00
|
|
|
return Promise.reject(new Error('None of the application were successful in running'))
|
2020-09-13 04:36:33 +00:00
|
|
|
}
|
2020-09-07 00:47:53 +00:00
|
|
|
}
|
2022-02-08 06:35:36 +00:00
|
|
|
|
2022-02-11 13:59:10 +00:00
|
|
|
async run() {
|
2022-02-14 08:15:50 +00:00
|
|
|
if (this.running) return
|
|
|
|
this.running = true
|
|
|
|
|
2022-03-10 11:06:17 +00:00
|
|
|
if (!this.isSlave) {
|
|
|
|
this.log.info(`Running updater on ${this.applications.length} apps`)
|
|
|
|
await Promise.all(this.applications.map((app) => {
|
|
|
|
return app.update().catch(err => {
|
|
|
|
app.ctx.log.error(err, `Error updating: ${err.message}`)
|
|
|
|
})
|
|
|
|
}))
|
|
|
|
}
|
2022-02-11 13:59:10 +00:00
|
|
|
|
|
|
|
let found = false
|
2022-03-10 11:06:17 +00:00
|
|
|
|
|
|
|
if (this.isSlave) {
|
|
|
|
let app = this.getApplication(process.env.CLUSTER_APP_NAME)
|
|
|
|
try {
|
|
|
|
await app.runVersion(process.env.CLUSTER_APP_VERSION)
|
|
|
|
} catch (err) {
|
|
|
|
app.ctx.log.fatal(err)
|
|
|
|
return Promise.reject(err)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2022-02-11 13:59:10 +00:00
|
|
|
|
|
|
|
for (let app of this.applications) {
|
|
|
|
app.startAutoupdater()
|
|
|
|
|
|
|
|
await this.runApplication(app).then(
|
|
|
|
() => {
|
|
|
|
found = true
|
|
|
|
},
|
|
|
|
err => {
|
2022-02-14 08:15:50 +00:00
|
|
|
app.ctx.log.error(err, `Error running: ${err.message}`)
|
2022-02-11 13:59:10 +00:00
|
|
|
}
|
|
|
|
)
|
2022-02-14 08:15:50 +00:00
|
|
|
|
|
|
|
app.on('updated', this.runApplication.bind(this, app))
|
2022-02-11 13:59:10 +00:00
|
|
|
}
|
2022-02-08 06:35:36 +00:00
|
|
|
|
2022-02-11 13:59:10 +00:00
|
|
|
if (!found) {
|
|
|
|
throw new Error('No stable application was found')
|
2022-02-08 06:35:36 +00:00
|
|
|
}
|
2022-02-11 13:59:10 +00:00
|
|
|
}
|
2022-02-08 06:35:36 +00:00
|
|
|
|
2022-02-15 11:28:30 +00:00
|
|
|
criticalError(application, version, errorCode) {
|
|
|
|
application.ctx.log.fatal(`Critical error ${errorCode} running ${version.version}`)
|
|
|
|
version.stable = -2
|
|
|
|
this.db.writeSync()
|
|
|
|
}
|
|
|
|
|
2022-02-11 13:59:10 +00:00
|
|
|
async runApplication(application) {
|
|
|
|
let name = application.name
|
2022-02-08 06:35:36 +00:00
|
|
|
let found = false
|
|
|
|
|
2022-02-11 13:59:10 +00:00
|
|
|
if (!this.db.data.core[name].versions.length) {
|
|
|
|
return Promise.reject(new Error(`No versions were found`))
|
|
|
|
}
|
|
|
|
|
2022-02-08 06:35:36 +00:00
|
|
|
for (let i = 0; i < this.db.data.core[name].versions.length; i++) {
|
|
|
|
let version = this.db.data.core[name].versions[i]
|
2022-02-11 13:59:10 +00:00
|
|
|
if (!version.installed || version.stable < -1) continue
|
2022-02-18 13:31:56 +00:00
|
|
|
if (version.stable < 0 && !application.fresh) {
|
|
|
|
application.ctx.log.warn(`Restarting for ${version.version} due to last run failing while not being in fresh state`)
|
|
|
|
return this.restart(`Application ${name} has fresh false while attempting to run ${version.version} with stable -1`)
|
|
|
|
}
|
2022-03-10 11:06:17 +00:00
|
|
|
|
2022-02-08 06:35:36 +00:00
|
|
|
await application.closeServer()
|
|
|
|
|
2022-02-15 11:28:30 +00:00
|
|
|
this._applicationFatalCrash = this.criticalError.bind(this, application, version)
|
|
|
|
process.once('exit', this._applicationFatalCrash)
|
|
|
|
|
2022-02-18 13:31:56 +00:00
|
|
|
let wasFresh = application.fresh
|
|
|
|
|
2022-02-08 06:35:36 +00:00
|
|
|
try {
|
2022-02-14 08:15:50 +00:00
|
|
|
application.ctx.log.info(`Attempting to run version ${version.version}`)
|
2022-02-11 13:59:10 +00:00
|
|
|
await application.runVersion(version.version)
|
|
|
|
found = true
|
2022-02-14 08:15:50 +00:00
|
|
|
version.stable = 1
|
|
|
|
await this.db.write()
|
2022-03-10 11:06:17 +00:00
|
|
|
application.ctx.log.info(`${version.version} is up and running`)
|
2022-02-11 13:59:10 +00:00
|
|
|
break
|
2022-02-08 06:35:36 +00:00
|
|
|
} catch(err) {
|
2022-02-18 13:31:56 +00:00
|
|
|
application.ctx.log.error(err, `Error starting ${version.version}: ${err.message}`)
|
|
|
|
process.off('exit', this._applicationFatalCrash)
|
|
|
|
|
|
|
|
if (version.stable < 1) {
|
|
|
|
if (wasFresh) {
|
|
|
|
version.stable = -2
|
|
|
|
} else {
|
|
|
|
version.stable = -1
|
|
|
|
}
|
|
|
|
await this.db.write()
|
|
|
|
if (version.stable === -1) {
|
|
|
|
return this.restart(`Application ${name} version ${version.version} failed to start but application was dirty, check if restarting fixes it`)
|
|
|
|
}
|
2022-02-15 11:28:30 +00:00
|
|
|
} else {
|
2022-02-18 13:31:56 +00:00
|
|
|
await this.db.write()
|
|
|
|
return this.restart(`Application ${name} version ${version.version} previously stable but now failing`)
|
2022-02-15 11:28:30 +00:00
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
process.off('exit', this._applicationFatalCrash)
|
2022-02-08 06:35:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-11 13:59:10 +00:00
|
|
|
if (!found) {
|
|
|
|
return Promise.reject(Error(`No stable versions were found`))
|
|
|
|
}
|
2022-02-08 06:35:36 +00:00
|
|
|
}
|
2020-09-13 04:36:33 +00:00
|
|
|
}
|
2022-02-11 13:59:10 +00:00
|
|
|
|
|
|
|
Core.addProvider('static', StaticProvider)
|
|
|
|
Core.addProvider('git', GitProvider)
|