From 410dc39f3e562164c2d927f9496bf7e9508eb3e1 Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Mon, 1 Sep 2014 19:46:37 +0000 Subject: [PATCH] Add checks for packages and contentPath to startup Refs #3864 - Make sure that require() is able to resolve all dependencies listed in package.json. If packages are missing halt the bootstrap process and display an error and help message for user. - Check that contentPath and its subdirectories exist with the correct permissions. - Check sqlite3 database file is set for read/write access. --- core/server/utils/startup-check.js | 162 +++++++++++++++++++++++++++++ index.js | 20 +++- 2 files changed, 177 insertions(+), 5 deletions(-) create mode 100644 core/server/utils/startup-check.js diff --git a/core/server/utils/startup-check.js b/core/server/utils/startup-check.js new file mode 100644 index 0000000000..4c481cf9cd --- /dev/null +++ b/core/server/utils/startup-check.js @@ -0,0 +1,162 @@ +var packages = require('../../../package.json'), + path = require('path'), + crypto = require('crypto'), + fs = require('fs'), + mode = process.env.NODE_ENV === undefined ? 'development' : process.env.NODE_ENV, + appRoot = path.resolve(__dirname, '../../../'), + configFilePath = process.env.GHOST_CONFIG || path.join(appRoot, 'config.js'), + checks; + +checks = { + check: function check() { + this.packages(); + this.contentPath(); + this.sqlite(); + }, + + // Make sure package.json dependencies have been installed. + packages: function checkPackages() { + if (mode !== 'production' && mode !== 'development') { + return; + } + + var errors = []; + + Object.keys(packages.dependencies).forEach(function (p) { + try { + require.resolve(p); + } catch (e) { + errors.push(e.message); + } + }); + + if (!errors.length) { + return; + } + + errors = errors.join('\n '); + + console.error('\x1B[31mERROR: Ghost is unable to start due to missing dependencies:\033[0m\n ' + errors); + console.error('\x1B[32m\nPlease run `npm install --production` and try starting Ghost again.'); + console.error('\x1B[32mHelp and documentation can be found at http://support.ghost.org.\033[0m\n'); + + process.exit(0); + }, + + // Check content path permissions + contentPath: function checkContentPaths() { + if (mode !== 'production' && mode !== 'development') { + return; + } + + var configFile, + config, + contentPath, + contentSubPaths = ['apps', 'data', 'images', 'themes'], + fd, + errorHeader = '\x1B[31mERROR: Unable to access Ghost\'s content path:\033[0m', + errorHelp = '\x1B[32mCheck that the content path exists and file system permissions are correct.' + + '\nHelp and documentation can be found at http://support.ghost.org.\033[0m'; + + // Get the content path to test. If it's defined in config.js use that, if not use the default + try { + configFile = require(configFilePath); + config = configFile[mode]; + + if (config && config.paths && config.paths.contentPath) { + contentPath = config.paths.contentPath; + } else { + contentPath = path.join(appRoot, 'content'); + } + } catch (e) { + // If config.js doesn't exist yet, check the default content path location + contentPath = path.join(appRoot, 'content'); + } + + // Use all sync io calls so that we stay in this function until all checks are complete + + // Check the root content path + try { + fd = fs.openSync(contentPath, 'r'); + fs.closeSync(fd); + } catch (e) { + console.error(errorHeader); + console.error(' ' + e.message); + console.error('\n' + errorHelp); + + process.exit(0); + } + + // Check each of the content path subdirectories + try { + contentSubPaths.forEach(function (sub) { + var dir = path.join(contentPath, sub), + randomFile = path.join(dir, crypto.randomBytes(8).toString('hex')); + + fd = fs.openSync(dir, 'r'); + fs.closeSync(fd); + + // Check write access to directory by attempting to create a random file + fd = fs.openSync(randomFile, 'wx+'); + fs.closeSync(fd); + fs.unlinkSync(randomFile); + }); + } catch (e) { + console.error(errorHeader); + console.error(' ' + e.message); + console.error('\n' + errorHelp); + + process.exit(0); + } + }, + + // Make sure sqlite3 database is available for read/write + sqlite: function checkSqlite() { + if (mode !== 'production' && mode !== 'development') { + return; + } + + var configFile, + config, + appRoot = path.resolve(__dirname, '../../../'), + dbPath, + fd; + + try { + configFile = require(configFilePath); + config = configFile[mode]; + + // Abort check if database type is not sqlite3 + if (config && config.database && config.database.client !== 'sqlite3') { + return; + } + + if (config && config.database && config.database.connection) { + dbPath = config.database.connection.filename; + } + } catch (e) { + // If config.js doesn't exist, use the default path + dbPath = path.join(appRoot, 'content', 'data', mode === 'production' ? 'ghost.db' : 'ghost-dev.db'); + } + + // Check for read/write access on sqlite db file + try { + fd = fs.openSync(dbPath, 'r+'); + fs.closeSync(fd); + } catch (e) { + // Database file not existing is not an error as sqlite will create it. + if (e.code === 'ENOENT') { + return; + } + + console.error('\x1B[31mERROR: Unable to open sqlite3 database file for read/write\033[0m'); + console.error(' ' + e.message); + console.error('\n\x1B[32mCheck that the sqlite3 database file permissions allow read and write access.'); + console.error('Help and documentation can be found at http://support.ghost.org.\033[0m'); + + process.exit(0); + } + } +}; + +module.exports = checks; diff --git a/index.js b/index.js index 70c93fb560..29b5fd7c9b 100644 --- a/index.js +++ b/index.js @@ -2,11 +2,21 @@ // Orchestrates the loading of Ghost // When run from command line. -var express = require('express'), - ghost = require('./core'), - errors = require('./core/server/errors'), - // Create our parent express app instance. - parentApp = express(); +var express, + ghost, + parentApp, + errors; + +// Make sure dependencies are installed and file system permissions are correct. +require('./core/server/utils/startup-check').check(); + +// Proceed with startup +express = require('express'); +ghost = require('./core'); +errors = require('./core/server/errors'); + +// Create our parent express app instance. +parentApp = express(); ghost().then(function (ghostServer) { // Mount our ghost instance on our desired subdirectory path if it exists.