├── .gitignore ├── .profile ├── LICENSE ├── Procfile ├── README.md ├── app.json ├── bin ├── common │ └── env-values.js ├── create-config ├── init-deployment ├── move-storage-adapter └── wait-for-db ├── config.development.json ├── content ├── adapters │ └── storage │ │ └── ghost-storage-adapter-ipfs ├── data │ └── .gitkeep └── themes │ └── casper ├── package-lock.json ├── package.json └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log* 3 | 4 | content/data/*.db 5 | content/logs/ 6 | config.production.json 7 | content/data/ghost-local.db 8 | 9 | # asdf version manager - using node 14.15.1 10 | .tool.versions -------------------------------------------------------------------------------- /.profile: -------------------------------------------------------------------------------- 1 | # Generate the Ghost JSON config file when Heroku dyno starts-up. 2 | bin/create-config 3 | 4 | # Moves storage adapter from node_modules to content folder 5 | bin/move-storage-adapter -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Coby Chapple 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm start --production 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ghost Blogging on Heroku with IPFS 2 | 3 | Ghost is a free, open, simple blogging platform. Visit the project's website at , or read the docs on . 4 | 5 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/fission-suite/heroku-ipfs-ghost) 6 | 7 | ![Screencast demoing deployment to Heroku via button above](https://github.com/agentofuser/gifs/raw/main/fission/fission-heroku-ipfs-ghost-2x.gif) 8 | ## Ghost v3.x 9 | 10 | - Edited the `package.json` to include the newest 3.x Ghost release and the newest Casper theme 11 | - For file storage, uses the [IPFS Storage adapter for Ghost](https://github.com/fission-suite/ghost-storage-adapter-ipfs) connected to the [Fission IPFS Add-on for Heroku](https://elements.heroku.com/addons/interplanetary-fission) 12 | 13 | --- 14 | 15 | This has been forked from the [Ghost on Heroku by cobyism](https://github.com/cobyism/ghost-on-heroku), originally written for Ghost v1.x. You can read some other [tips in the README](https://github.com/cobyism/ghost-on-heroku/blob/master/README.md) there about running Ghost on Heroku. This version only needs your Heroku app name to be configured and it should work right away. 16 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ghost on Heroku", 3 | "description": "Just a blogging platform", 4 | "logo": "https://cdn.rawgit.com/TryGhost/Ghost-Admin/61fe83dff375a145d5272a5382aa2c72bd994830/public/assets/img/large.png", 5 | "repository": "https://github.com/cobyism/ghost-on-heroku", 6 | "scripts": { 7 | "postdeploy": "bin/init-deployment" 8 | }, 9 | "success_url": "/ghost", 10 | "addons": [ 11 | "jawsdb", 12 | "mailgun", 13 | { 14 | "plan": "interplanetary-fission:test" 15 | } 16 | ], 17 | "env": { 18 | "PUBLIC_URL": { 19 | "description": "The HTTPS URL of this app: either your custom domain or default 'herokuapp.com' hostname.", 20 | "value": "https://YOURAPPNAME.herokuapp.com" 21 | }, 22 | "IPFS_GATEWAY_URL": { 23 | "description": "The HTTPS URL of your prefered IPFS gateway (defaults to https://ipfs.io)", 24 | "value": "https://ipfs.runfission.com", 25 | "required": false 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /bin/common/env-values.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mysqlDatabaseUrl: process.env.MYSQL_DATABASE_URL || process.env.JAWSDB_URL || process.env.CLEARDB_DATABASE_URL 3 | } 4 | -------------------------------------------------------------------------------- /bin/create-config: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Ghost Configuration for Heroku 3 | 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | var url = require('url'); 7 | 8 | var envValues = require('./common/env-values'); 9 | var appRoot = path.join(__dirname, '..'); 10 | 11 | function createConfig() { 12 | var fileStorage, storage, storageOptions; 13 | 14 | // When deploying from git rather then a Heroku deploy button, Heroku doesn't 15 | // read app.json and doesn't set the environment variables configured there. 16 | // The `PUBLIC_URL` envvar is required, so if it's missing we know app.json 17 | // was skipped, and we read it explictly. 18 | if (!process.env.PUBLIC_URL) { 19 | const appJson = JSON.parse(fs.readFileSync(`${appRoot}/app.json`, 'utf8')); 20 | process.env.PUBLIC_URL = appJson.env.PUBLIC_URL.value; 21 | process.env.IPFS_GATEWAY_URL = appJson.env.IPFS_GATEWAY_URL.value; 22 | } 23 | 24 | if(!!process.env.INTERPLANETARY_FISSION_USERNAME && !!process.env.INTERPLANETARY_FISSION_PASSWORD) { 25 | fileStorage = true; 26 | storageOptions = { 27 | username: process.env.INTERPLANETARY_FISSION_USERNAME, 28 | password: process.env.INTERPLANETARY_FISSION_PASSWORD 29 | }; 30 | 31 | if(!!process.env.INTERPLANETARY_FISSION_URL) { 32 | storageOptions.apiURL = process.env.INTERPLANETARY_FISSION_URL; 33 | } 34 | 35 | if(!!process.env.IPFS_GATEWAY_URL) { 36 | storageOptions.gatewayURL = process.env.IPFS_GATEWAY_URL; 37 | } 38 | 39 | storage = { 40 | active: "ghost-storage-adapter-ipfs", 41 | "ghost-storage-adapter-ipfs": storageOptions 42 | }; 43 | } else { 44 | fileStorage = false 45 | storage = {} 46 | } 47 | 48 | config = { 49 | url: process.env.PUBLIC_URL, 50 | logging: { 51 | level: "info", 52 | transports: ["stdout"] 53 | }, 54 | mail: { 55 | transport: 'SMTP', 56 | options: { 57 | service: 'Mailgun', 58 | auth: { 59 | user: process.env.MAILGUN_SMTP_LOGIN, 60 | pass: process.env.MAILGUN_SMTP_PASSWORD 61 | } 62 | } 63 | }, 64 | fileStorage: fileStorage, 65 | storage: storage, 66 | database: { 67 | client: 'mysql', 68 | connection: getMysqlConfig(envValues.mysqlDatabaseUrl), 69 | pool: { min: 0, max: 5 }, 70 | debug: false 71 | }, 72 | server: { 73 | host: '0.0.0.0', 74 | port: process.env.PORT 75 | }, 76 | paths: { 77 | contentPath: path.join(appRoot, '/content/') 78 | } 79 | }; 80 | 81 | return config; 82 | } 83 | 84 | function getMysqlConfig(connectionUrl) { 85 | if (connectionUrl == null) { 86 | return {}; 87 | } 88 | 89 | var dbConfig = url.parse(connectionUrl); 90 | if (dbConfig == null) { 91 | return {}; 92 | } 93 | 94 | var dbAuth = dbConfig.auth ? dbConfig.auth.split(':') : []; 95 | var dbUser = dbAuth[0]; 96 | var dbPassword = dbAuth[1]; 97 | 98 | if (dbConfig.pathname == null) { 99 | var dbName = 'ghost'; 100 | } else { 101 | var dbName = dbConfig.pathname.split('/')[1]; 102 | } 103 | 104 | var dbConnection = { 105 | host: dbConfig.hostname, 106 | port: dbConfig.port || '3306', 107 | user: dbUser, 108 | password: dbPassword, 109 | database: dbName 110 | }; 111 | return dbConnection; 112 | } 113 | 114 | var configContents = JSON.stringify(createConfig(), null, 2); 115 | fs.writeFileSync(path.join(appRoot, 'config.production.json'), configContents); 116 | -------------------------------------------------------------------------------- /bin/init-deployment: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "Initializing the deployment…" 4 | echo "pwd →" 5 | pwd 6 | echo "config.production.json →" 7 | cat "config.production.json" 8 | 9 | bin/wait-for-db 10 | 11 | knex-migrator init --mgpath node_modules/ghost 12 | -------------------------------------------------------------------------------- /bin/move-storage-adapter: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "Moving Storage Adapter for Production" 4 | 5 | ADAPTER="ghost-storage-adapter-ipfs" 6 | SRC="./node_modules/$ADAPTER/" 7 | DEST="./content/adapters/storage/" 8 | SYMLINK="$DEST$ADAPTER" 9 | 10 | # Removes symbolic link 11 | rm $SYMLINK 12 | 13 | # Moves storage adapter 14 | mv $SRC $DEST -------------------------------------------------------------------------------- /bin/wait-for-db: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var mysql = require('../node_modules/mysql'); 4 | var envValues = require('./common/env-values'); 5 | 6 | console.error(`Awaiting MySQL database…`); 7 | pingDatabaseUntilConnected(); 8 | 9 | function pingDatabaseUntilConnected() { 10 | var connection = mysql.createConnection(envValues.mysqlDatabaseUrl); 11 | connection.query('SELECT 1', function (error, results, fields) { 12 | if (error) { 13 | console.error(`Database not yet available: ${error.message}`); 14 | setTimeout(pingDatabaseUntilConnected, 5000); 15 | } else { 16 | console.error('Database connected.'); 17 | connection.end(); 18 | process.exit(0); 19 | } 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /config.development.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "http://localhost:2368/", 3 | "server": { 4 | "port": 2368, 5 | "host": "127.0.0.1" 6 | }, 7 | "database": { 8 | "client": "sqlite3", 9 | "connection": { 10 | "filename": "content/data/ghost-local.db" 11 | } 12 | }, 13 | "mail": { 14 | "transport": "Direct" 15 | }, 16 | "logging": { 17 | "transports": [ 18 | "file", 19 | "stdout" 20 | ] 21 | }, 22 | "process": "local", 23 | "paths": { 24 | "contentPath": "content" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /content/adapters/storage/ghost-storage-adapter-ipfs: -------------------------------------------------------------------------------- 1 | ../../../node_modules/ghost-storage-adapter-ipfs/ -------------------------------------------------------------------------------- /content/data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fission-codes/heroku-ipfs-ghost/270f1b4a5dcf1bc6a57126cb92ef2ae277e5a811/content/data/.gitkeep -------------------------------------------------------------------------------- /content/themes/casper: -------------------------------------------------------------------------------- 1 | ../../node_modules/casper/ -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "heroku-ipfs-ghost", 3 | "description": "Ghost 3.x with compatability to deploy on Heroku", 4 | "license": "MIT", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/fission-suite/heroku-ipfs-ghost.git" 8 | }, 9 | "private": true, 10 | "dependencies": { 11 | "casper": "github:tryghost/Casper#3.1.2", 12 | "ghost": "^3.41.6", 13 | "ghost-storage-adapter-ipfs": "github:fission-suite/ghost-storage-adapter-ipfs", 14 | "mysql": "^2.18.1" 15 | }, 16 | "engines": { 17 | "node": "14.x" 18 | }, 19 | "scripts": { 20 | "start": "node server.js" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var ghost = require('ghost'); 2 | 3 | // Run a single Ghost process 4 | ghost() 5 | .then( ghostServer => ghostServer.start() ) 6 | .catch( error => { 7 | console.error(`Ghost server error: ${error.message} ${error.stack}`); 8 | process.exit(1); 9 | }); 10 | --------------------------------------------------------------------------------