Ghost/core/server/index.js
Nazar Gargol 5582d030e3 Added routes.yaml content checksum storage to the db
closes #11999

- When the routes.yaml file changes (manually or through API) we need
to store a checksum to be able to optimize routes reloads in the future
- Added mechanism to detect differences between stored and current routes.yaml hash value
- Added routes.yaml sync on server boot
- Added routes.yaml handling in controllers
- Added routes hash synchronization method in core settings. It lives in core settings
as it needs access to model layer. To avoid coupling with the frontend settings it accepts
a function which has to resolve to a routes hash
- Added note about settings validation side-effect. It mutates input!
- Added async check for currently loaded routes hash
- Extended frontend settings loader with async loader. The default behavior of the loader is
to load settings syncronously for reasons spelled in 0ac19dcf84
To avoid blocking the eventloop added async loading method
- Refactored frontend setting loader for reusability of  settings file path
- Added integrity check test for routes.yaml file
2020-09-10 10:54:57 +12:00

196 lines
6.7 KiB
JavaScript

/**
* make sure overrides get's called first!
* - keeping the overrides import here works for installing Ghost as npm!
*
* the call order is the following:
* - root index requires core module
* - core index requires server
* - overrides is the first package to load
*/
require('./overrides');
const debug = require('ghost-ignition').debug('boot:init');
const Promise = require('bluebird');
const config = require('../shared/config');
const {events, i18n} = require('./lib/common');
const logging = require('../shared/logging');
const migrator = require('./data/db/migrator');
const urlUtils = require('./../shared/url-utils');
let parentApp;
// Frontend Components
const themeService = require('../frontend/services/themes');
const appService = require('../frontend/services/apps');
const frontendSettings = require('../frontend/services/settings');
function initialiseServices() {
// CASE: When Ghost is ready with bootstrapping (db migrations etc.), we can trigger the router creation.
// Reason is that the routers access the routes.yaml, which shouldn't and doesn't have to be validated to
// start Ghost in maintenance mode.
// Routing is a bridge between the frontend and API
const routing = require('../frontend/services/routing');
// We pass the themeService API version here, so that the frontend services are less tightly-coupled
routing.bootstrap.start(themeService.getApiVersion());
const settings = require('./services/settings');
const permissions = require('./services/permissions');
const xmlrpc = require('./services/xmlrpc');
const slack = require('./services/slack');
const {mega} = require('./services/mega');
const webhooks = require('./services/webhooks');
const scheduling = require('./adapters/scheduling');
debug('`initialiseServices` Start...');
const getRoutesHash = () => frontendSettings.getCurrentHash('routes');
return Promise.join(
// Initialize the permissions actions and objects
permissions.init(),
xmlrpc.listen(),
slack.listen(),
mega.listen(),
webhooks.listen(),
settings.syncRoutesHash(getRoutesHash),
appService.init(),
scheduling.init({
// NOTE: When changing API version need to consider how to migrate custom scheduling adapters
// that rely on URL to lookup persisted scheduled records (jobs, etc.). Ref: https://github.com/TryGhost/Ghost/pull/10726#issuecomment-489557162
apiUrl: urlUtils.urlFor('api', {version: 'v3', versionType: 'admin'}, true)
})
).then(function () {
debug('XMLRPC, Slack, MEGA, Webhooks, Scheduling, Permissions done');
// Initialise analytics events
if (config.get('segment:key')) {
require('./analytics-events').init();
}
}).then(function () {
debug('...`initialiseServices` End');
});
}
/**
* - initialise models
* - initialise i18n
* - load all settings into settings cache (almost every component makes use of this cache)
* - load active theme
* - create our express apps (site, admin, api)
* - start the ghost server
* - enable maintenance mode if migrations are missing
*/
const minimalRequiredSetupToStartGhost = (dbState) => {
const settings = require('./services/settings');
const jobService = require('./services/jobs');
const models = require('./models');
const GhostServer = require('./ghost-server');
let ghostServer;
// Initialize Ghost core internationalization
i18n.init();
debug('Default i18n done for core');
models.init();
debug('Models done');
return settings.init()
.then(() => {
debug('Settings done');
return frontendSettings.init();
})
.then(() => {
debug('Frontend settings done');
return themeService.init();
})
.then(() => {
debug('Themes done');
parentApp = require('./web/parent/app')();
debug('Express Apps done');
return new GhostServer(parentApp);
})
.then((_ghostServer) => {
ghostServer = _ghostServer;
ghostServer.registerCleanupTask(async () => {
await jobService.shutdown();
});
// CASE: all good or db was just initialised
if (dbState === 1 || dbState === 2) {
events.emit('db.ready');
return initialiseServices()
.then(() => {
return ghostServer;
});
}
// CASE: migrations required, put blog into maintenance mode
if (dbState === 4) {
logging.info('Blog is in maintenance mode.');
config.set('maintenance:enabled', true);
migrator.migrate()
.then(() => {
return settings.reinit().then(() => {
events.emit('db.ready');
return initialiseServices();
});
})
.then(() => {
config.set('maintenance:enabled', false);
logging.info('Blog is out of maintenance mode.');
return GhostServer.announceServerReadiness();
})
.catch((err) => {
return GhostServer.announceServerReadiness(err)
.finally(() => {
logging.error(err);
setTimeout(() => {
process.exit(-1);
}, 100);
});
});
return ghostServer;
}
});
};
/**
* Connect to database.
* Check db state.
*/
const isDatabaseInitialisationRequired = () => {
const db = require('./data/db/connection');
let dbState;
return migrator.getState()
.then((state) => {
dbState = state;
// CASE: db initialisation required, wait till finished
if (dbState === 2) {
return migrator.dbInit();
}
// CASE: is db incompatible? e.g. you can't connect a 0.11 database with Ghost 1.0 or 2.0
if (dbState === 3) {
return migrator.isDbCompatible(db)
.then(() => {
dbState = 2;
return migrator.dbInit();
});
}
})
.then(() => {
return minimalRequiredSetupToStartGhost(dbState);
});
};
module.exports = isDatabaseInitialisationRequired;