Skip to content

Commit

Permalink
Ghost 1.x compatibility for Heroku: new config adapter, MySQL databas…
Browse files Browse the repository at this point in the history
…e, updated S3 storage
  • Loading branch information
mars committed Aug 6, 2017
1 parent fce57eb commit bc97c33
Show file tree
Hide file tree
Showing 16 changed files with 155 additions and 112 deletions.
3 changes: 2 additions & 1 deletion .gitignore
@@ -1,4 +1,5 @@
node_modules/
npm-debug.log*

content/themes/casper/
content/logs/
config.production.json
2 changes: 2 additions & 0 deletions .profile
@@ -0,0 +1,2 @@
# Generate the Ghost JSON config file when Heroku dyno starts-up.
node config.js
42 changes: 24 additions & 18 deletions README.md
Expand Up @@ -4,33 +4,39 @@ Ghost is a free, open, simple blogging platform. Visit the project's website at

## Note regarding Ghost version 1.X

The latest releases of Ghost dropped support for a couple of things that complicate one-button deployment on Heroku (see issues #90 and #91 for background). Until we can establish the best way to support deploying the latest version of Ghost to Heroku, this repository will remain at v0.11.11. If you have suggestions, please open an issue or pull request. In the meantime, thank you for your patience.
The latest release of Ghost is now supported! Changes include:

* Requires MySQL database, available through two add-ons,
* [JawsDB](https://elements.heroku.com/addons/jawsdb)
* [ClearDB](https://elements.heroku.com/addons/cleardb)
* S3 storage adapter had been updated but should be compatible
* `HEROKU_URL` config var renamed to `PUBLIC_URL` to avoid using Heroku's namespace
* removed **Deploy to Heroku** button because it does not support cloning back to local machine

## Deploying on Heroku

To get your own Ghost blog running on Heroku, click the button below:
```bash
git clone https://github.com/cobyism/ghost-on-heroku
cd ghost-on-heroku

[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/cobyism/ghost-on-heroku)
heroku create YOURAPPNAME
heroku addons:create jawsdb
heroku addons:create mailgun
heroku config:set PUBLIC_URL=https://YOURAPPNAME.herokuapp.com

Fill out the form, and you should be cooking with gas in a few seconds.
git push heroku master

### Things you should know
heroku run 'knex-migrator init --mgpath node_modules/ghost
heroku restart
```
- After deployment, visit the admin area at `YOURAPPNAME.herokuapp.com/ghost` to set up your blog.
### Things you should know
- After deployment,
* you nay beed to upgrade the database add-on to have enough connections
* visit the admin area at `https://YOURAPPNAME.herokuapp.com/ghost` to set up your blog.
- Your blog will be publicly accessible at `YOURAPPNAME.herokuapp.com`.

- To make changes to your Ghost blog (like adding a theme to the `/content` directory, for instance), clone your blog locally using the [Heroku Toolbelt](https://toolbelt.heroku.com/):

```sh
heroku git:clone --app YOURAPPNAME
```

### What do I put in the deployment and environment variable fields?

- **App name (required)**. Pick a name for your application. Heroku says this field is optional, but it’s easier if you choose a name here, because you need to specify the URL of your blog in the first config field anyway. You can add a custom domain later if you want, but this is the name of the application you’ll see in your Heroku dashboard.

- **Heroku URL (required)**. Take the name of your Heroku application, and put it into URL form. For example, if you choose `my-ghost-blog` as the app name, the Heroku URL config value needs to be `http://my-ghost-blog.herokuapp.com` (no trailing slash). If you subsequently set up a [custom domain](https://devcenter.heroku.com/articles/custom-domains) for your blog, you’ll need to update your Ghost blog’s `HEROKU_URL` environment variable accordingly.
- If you subsequently set up a [custom domain](https://devcenter.heroku.com/articles/custom-domains) for your blog, you’ll need to update your Ghost blog’s `PUBLIC_URL` environment variable accordingly.
#### Using with file uploads disabled
Expand Down
11 changes: 3 additions & 8 deletions app.json
Expand Up @@ -5,12 +5,12 @@
"repository": "https://github.com/cobyism/ghost-on-heroku",
"success_url": "/ghost",
"addons": [
"heroku-postgresql",
"jawsdb",
"mailgun"
],
"env": {
"HEROKU_URL": {
"description": "The URL of this Heroku app.",
"PUBLIC_URL": {
"description": "The HTTPS URL of this app: either your custom domain or default 'herokuapp.com' hostname.",
"value": "https://YOURAPPNAME.herokuapp.com"
},
"S3_ACCESS_KEY_ID": {
Expand All @@ -32,11 +32,6 @@
"S3_ASSET_HOST_URL": {
"description": "Optional custom CDN asset host url, if using S3 file storage.",
"required": false
},
"PGSSLMODE": {
"description": "PostgreSQL SSL connection mode. Set to 'require' to force SSL. Unset it to use the default.",
"required": false,
"value": "require"
}
}
}
26 changes: 26 additions & 0 deletions config.development.json
@@ -0,0 +1,26 @@
{
"url": "http://localhost:2368/",
"server": {
"port": 2368,
"host": "127.0.0.1"
},
"database": {
"client": "sqlite3",
"connection": {
"filename": "content/data/ghost-local.db"
}
},
"mail": {
"transport": "Direct"
},
"logging": {
"transports": [
"file",
"stdout"
]
},
"process": "local",
"paths": {
"contentPath": "content"
}
}
129 changes: 72 additions & 57 deletions config.js
@@ -1,44 +1,47 @@
// Ghost Configuration for Heroku

var path = require('path'),
config,
fileStorage,
storage;
var fs = require('fs');
var path = require('path');
var url = require('url');

if (!!process.env.S3_ACCESS_KEY_ID) {
fileStorage = true
storage = {
active: 'ghost-s3',
'ghost-s3': {
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_ACCESS_SECRET_KEY,
bucket: process.env.S3_BUCKET_NAME,
region: process.env.S3_BUCKET_REGION,
assetHost: process.env.S3_ASSET_HOST_URL
function createConfig() {
var fileStorage, storage;

if (!!process.env.S3_ACCESS_KEY_ID) {
fileStorage = true
storage = {
active: 's3',
's3': {
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_ACCESS_SECRET_KEY,
bucket: process.env.S3_BUCKET_NAME,
region: process.env.S3_BUCKET_REGION,
assetHost: process.env.S3_ASSET_HOST_URL
}
}
}
} else if (!!process.env.BUCKETEER_AWS_ACCESS_KEY_ID) {
fileStorage = true
storage = {
active: 'ghost-s3',
'ghost-s3': {
accessKeyId: process.env.BUCKETEER_AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.BUCKETEER_AWS_SECRET_ACCESS_KEY,
bucket: process.env.BUCKETEER_BUCKET_NAME,
region: process.env.S3_BUCKET_REGION,
assetHost: process.env.S3_ASSET_HOST_URL
} else if (!!process.env.BUCKETEER_AWS_ACCESS_KEY_ID) {
fileStorage = true
storage = {
active: 's3',
's3': {
accessKeyId: process.env.BUCKETEER_AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.BUCKETEER_AWS_SECRET_ACCESS_KEY,
bucket: process.env.BUCKETEER_BUCKET_NAME,
region: process.env.S3_BUCKET_REGION,
assetHost: process.env.S3_ASSET_HOST_URL
}
}
} else {
fileStorage = false
storage = {}
}
} else {
fileStorage = false
storage = {}
}

config = {

// Production (Heroku)
production: {
url: process.env.HEROKU_URL,
config = {
url: process.env.PUBLIC_URL,
logging: {
level: "info",
transports: ["stdout"]
},
mail: {
transport: 'SMTP',
options: {
Expand All @@ -52,8 +55,8 @@ config = {
fileStorage: fileStorage,
storage: storage,
database: {
client: 'postgres',
connection: process.env.DATABASE_URL,
client: 'mysql',
connection: getMysqlConfig(process.env.JAWSDB_URL || process.env.CLEARDB_DATABASE_URL),
debug: false
},
server: {
Expand All @@ -63,28 +66,40 @@ config = {
paths: {
contentPath: path.join(__dirname, '/content/')
}
},
};

// Development
development: {
url: 'http://localhost:2368',
database: {
client: 'sqlite3',
connection: {
filename: path.join(__dirname, '/content/data/ghost-dev.db')
},
debug: false
},
server: {
host: '127.0.0.1',
port: '2368'
},
paths: {
contentPath: path.join(__dirname, '/content/')
}
return config;
}

function getMysqlConfig(connectionUrl) {
if (connectionUrl == null) {
return {};
}

};
var dbConfig = url.parse(connectionUrl);
if (dbConfig == null) {
return {};
}

var dbAuth = dbConfig.auth ? dbConfig.auth.split(':') : [];
var dbUser = dbAuth[0];
var dbPassword = dbAuth[1];

if (dbConfig.pathname == null) {
var dbName = 'ghost';
} else {
var dbName = dbConfig.pathname.split('/')[1];
}

var dbConnection = {
host: dbConfig.hostname,
port: dbConfig.port || '3306',
user: dbUser,
password: dbPassword,
database: dbName
};
return dbConnection;
}

// Export config
module.exports = config;
var configContents = JSON.stringify(createConfig(), null, 2);
fs.writeFileSync(path.join(__dirname, 'config.production.json'), configContents);
1 change: 1 addition & 0 deletions content/adapters/storage/s3
3 changes: 0 additions & 3 deletions content/apps/README.md

This file was deleted.

3 changes: 0 additions & 3 deletions content/data/README.md

This file was deleted.

Binary file added content/data/ghost-local.db
Binary file not shown.
3 changes: 0 additions & 3 deletions content/images/README.md

This file was deleted.

2 changes: 0 additions & 2 deletions content/storage/ghost-s3/index.js

This file was deleted.

3 changes: 0 additions & 3 deletions content/themes/README.md

This file was deleted.

1 change: 1 addition & 0 deletions content/themes/casper
13 changes: 5 additions & 8 deletions package.json
Expand Up @@ -8,19 +8,16 @@
},
"bugs": "https://github.com/TryGhost/Ghost/issues",
"private": true,
"version": "0.11.11",
"version": "1.5.0",
"dependencies": {
"casper": "TryGhost/Casper#1.3.7",
"ghost": "0.11.11",
"ghost-s3-storage-adapter": "3.0.4",
"ncp": "^2.0.0",
"pg": "latest"
"casper": "github:tryghost/Casper#2.0.2",
"ghost": "^1.5.0",
"ghost-storage-adapter-s3": "^2.1.0"
},
"engines": {
"node": "^4.8.4 || ^6.11.1"
"node": "6.11.x"
},
"scripts": {
"postinstall": "ncp node_modules/casper content/themes/casper",
"start": "node server.js"
}
}
25 changes: 19 additions & 6 deletions server.js
@@ -1,8 +1,21 @@
var path = require('path');
var ghost = require('ghost');
var cluster = require('cluster');

ghost({
config: path.join(__dirname, 'config.js')
}).then(function (ghostServer) {
ghostServer.start();
});
// Heroku sets `WEB_CONCURRENCY` to the number of available processor cores.
var WORKERS = process.env.WEB_CONCURRENCY || 1;

if (cluster.isMaster) {
// Master starts all workers and restarts them when they exit.
cluster.on('exit', (worker, code, signal) => {
console.log(`Starting a new worker because PID: ${worker.process.pid} exited code ${code} from ${signal} signal.`);
cluster.fork();
});
for (var i = 0; i < WORKERS; i++) {
cluster.fork();
}
} else {
// Run Ghost in each worker / processor core.
ghost().then(function (ghostServer) {
ghostServer.start();
});
}

0 comments on commit bc97c33

Please sign in to comment.