diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5ebab6b --- /dev/null +++ b/.env.example @@ -0,0 +1,24 @@ +export DEBUG="hostr*" + +export NODE_ENV=development +export PORT=4040 +export WEB_BASE_URL=http://localhost:$PORT +export API_BASE_URL=http://localhost:$PORT/api +export UPLOAD_STORAGE_PATH=$HOME/.hostr/uploads +export COOKIE_KEY=INSECURE +export EMAIL_FROM= +export EMAIL_NAME= + +export STATSD_HOST=localhost +export MONGO_URL=mongodb://localhost:27017/hostr +export REDIS_URL=redis://localhost:6379 +export MANDRILL_KEY= +export STRIPE_SECRET_KEY= +export STRIPE_PUBLIC_KEY= + +# optional, some functionality will be disabled +export AWS_ACCESS_KEY_ID= +export AWS_SECRET_ACCESS_KEY= +export AWS_BUCKET= +export VIRUSTOTAL_KEY= +export SENTRY_DSN= diff --git a/README.md b/README.md index 2c6f67d..290bca7 100644 --- a/README.md +++ b/README.md @@ -19,39 +19,11 @@ You'll need `graphicsmagick` for image thumbnailing, everything else is taken ca ### Enviroment Variable Configuration -`AWS_ACCESS_KEY_ID` - -`AWS_SECRET_ACCESS_KEY` - -`AWS_BUCKET` - -`MANDRILL_KEY` - -`EMAIL_FROM` - defaults to `nobody@example.com` - -`REDIS_URL` - defaults to `redis://localhost:6379` - -`MONGO_URL` - defaults to `mongodb://localhost:27017/hostr` - -`LOCAL_PATH` - defaults to `~/.hostr/uploads`. - -`BASE_URL` - defaults to `https://localhost:4040` - -`FILE_HOST` - used by API for absolute file urls, defaults to `$BASE_URL` - -`API_URL` - defaults to `/api` - -`PORT` - defaults to `4040`. - -`VIRUSTOTAL` - API key enables Virustotal integration. - -`SENTRY_DSN` - DSN enables Sentry integration. - -Additionally, Hostr uses [debug](https://github.com/visionmedia/debug) so you can use the `DEBUG` environment variable something like `DEBUG=hostr*` to get debug output. +See [`.env.example`](.env.example). Copy it to `.env`, modify and `source .env` for development. [autoenv](https://github.com/kennethreitz/autoenv) is pretty nice for doing this automatically when you `cd` into your work directory. ### Deploying to Heroku -Because it uses iojs and graphicsmagick runtimes hostr needs an env variable for `BUILDPACK_URL` set to `https://github.com/ddollar/heroku-buildpack-multi.git`. +Because it uses iojs and graphicsmagick runtimes hostr needs an env variable for `BUILDPACK_URL` set to `https://github.com/ddollar/heroku-buildpack-multi.git` on Heroku. You'll also need to add Heroku Redis and a MongoDB addon. @@ -65,6 +37,14 @@ $ npm start This will install and build the frontend too. +Alternatively + +``` +$ npm run watch +``` + +Will watch your JS and CSS for changes, rebuild them, and reload the server. + ### Run the tests ``` diff --git a/api/app.js b/api/app.js index 480b46c..4ec16c0 100644 --- a/api/app.js +++ b/api/app.js @@ -10,7 +10,7 @@ const debug = debugname('hostr-api'); const router = new Router(); -const statsdOpts = {prefix: 'hostr-api', host: process.env.STATSD_HOST || 'localhost'}; +const statsdOpts = {prefix: 'hostr-api', host: process.env.STATSD_HOST}; router.use(stats(statsdOpts)); const statsd = new StatsD(statsdOpts); router.use(function* statsMiddleware(next) { diff --git a/api/routes/file.js b/api/routes/file.js index 74a04ef..e64dde3 100644 --- a/api/routes/file.js +++ b/api/routes/file.js @@ -13,11 +13,11 @@ import { formatFile } from '../../lib/format'; import debugname from 'debug'; const debug = debugname('hostr-api:file'); -const redisUrl = process.env.REDIS_URL || process.env.REDISTOGO_URL || 'redis://localhost:6379'; +const redisUrl = process.env.REDIS_URL; -const fileHost = process.env.FILE_HOST || 'http://localhost:4040'; +const baseURL = process.env.WEB_BASE_URL; -const storePath = process.env.STORE_PATH || path.join(process.env.HOME, '.hostr', 'uploads'); +const storePath = process.env.UPLOAD_STORAGE_PATH; export function* post(next) { if (!this.request.is('multipart/*')) { @@ -60,7 +60,7 @@ export function* post(next) { const fileId = yield hostrId(Files); // Fire an event to let the frontend map the GUID it sent to the real ID. Allows immediate linking to the file - const acceptedEvent = `{"type": "file-accepted", "data": {"id": "${fileId}", "guid": "${tempGuid}", "href": "${fileHost}/${fileId}"}}`; + const acceptedEvent = `{"type": "file-accepted", "data": {"id": "${fileId}", "guid": "${tempGuid}", "href": "${baseURL}/${fileId}"}}`; this.redis.publish('/user/' + this.user.id, acceptedEvent); this.statsd.incr('file.upload.accepted', 1); @@ -173,7 +173,7 @@ export function* post(next) { this.status = 201; this.body = formattedFile; - if (process.env.VIRUSTOTAL) { + if (process.env.VIRUSTOTAL_KEY) { // Check in the background process.nextTick(function* malwareScan() { debug('Malware Scan'); diff --git a/api/routes/user.js b/api/routes/user.js index 39c21c7..389f67a 100644 --- a/api/routes/user.js +++ b/api/routes/user.js @@ -6,7 +6,7 @@ import passwords from 'passwords'; import debugname from 'debug'; const debug = debugname('hostr-api:user'); -const redisUrl = process.env.REDIS_URL || process.env.REDISTOGO_URL || 'redis://localhost:6379'; +const redisUrl = process.env.REDIS_URL; export function* get() { this.body = this.user; diff --git a/app.js b/app.js index 7925bba..6ab8721 100644 --- a/app.js +++ b/app.js @@ -17,8 +17,10 @@ import web from './web/app'; import debugname from 'debug'; const debug = debugname('hostr'); +debug(process.env.COOKIE_KEY); + const app = websockify(koa()); -app.keys = [process.env.KEYS || 'INSECURE']; +app.keys = [process.env.COOKIE_KEY]; if (process.env.SENTRY_DSN) { const ravenClient = new raven.Client(process.env.SENTRY_DSN); diff --git a/lib/format.js b/lib/format.js index 9448fd2..4f87224 100644 --- a/lib/format.js +++ b/lib/format.js @@ -1,7 +1,7 @@ import moment from 'moment'; import { sniff } from './type'; -const fileHost = process.env.FILE_HOST || 'http://localhost:4040'; +const baseURL = process.env.WEB_BASE_URL; export function formatDate(timestamp) { return moment.unix(timestamp).format('D MMM YY [at] h:mm A'); @@ -25,7 +25,7 @@ export function formatFile(file) { added: moment.unix(file.time_added).format(), readableAdded: formatDate(file.time_added), downloads: file.downloads !== undefined ? file.downloads : 0, - href: fileHost + '/' + file._id, // eslint-disable-line no-underscore-dangle + href: baseURL + '/' + file._id, // eslint-disable-line no-underscore-dangle id: file._id, // eslint-disable-line no-underscore-dangle name: file.file_name, size: file.file_size, @@ -40,8 +40,8 @@ export function formatFile(file) { formattedFile.width = file.width; const ext = (file.file_name.split('.').pop().toLowerCase() === 'psd' ? '.png' : ''); formattedFile.direct = { - '150x': fileHost + '/file/150/' + file._id + '/' + file.file_name + ext, // eslint-disable-line no-underscore-dangle - '970x': fileHost + '/file/970/' + file._id + '/' + file.file_name + ext, // eslint-disable-line no-underscore-dangle + '150x': baseURL + '/file/150/' + file._id + '/' + file.file_name + ext, // eslint-disable-line no-underscore-dangle + '970x': baseURL + '/file/970/' + file._id + '/' + file.file_name + ext, // eslint-disable-line no-underscore-dangle }; } return formattedFile; diff --git a/lib/malware.js b/lib/malware.js index 9c7a37e..0fb1221 100644 --- a/lib/malware.js +++ b/lib/malware.js @@ -1,6 +1,6 @@ import virustotal from 'virustotal.js'; -virustotal.setKey(process.env.VIRUSTOTAL); +virustotal.setKey(process.env.VIRUSTOTAL_KEY); const extensions = ['EXE', 'PIF', 'APPLICATION', 'GADGET', 'MSI', 'MSP', 'COM', 'SCR', 'HTA', 'CPL', 'MSC', 'JAR', 'BAT', 'CMD', 'VB', 'VBS', 'VBE', 'JS', 'JSE', 'WS', 'WSF', 'WSC', 'WSH', 'PS1', 'PS1XML', 'PS2', diff --git a/lib/mongo.js b/lib/mongo.js index c1dad22..a1a9f84 100644 --- a/lib/mongo.js +++ b/lib/mongo.js @@ -3,11 +3,9 @@ const MongoClient = mongodb().MongoClient; import debugname from 'debug'; const debug = debugname('hostr:mongo'); -const uristring = process.env.MONGO_URL || process.env.MONGOLAB_URI || 'mongodb://localhost:27017/hostr'; - const configuredClient = new Promise((resolve, reject) => { debug('Connecting to Mongodb'); - return MongoClient.connect(uristring).then((client) => { + return MongoClient.connect(process.env.MONGO_URL).then((client) => { debug('Successfully connected to Mongodb'); client.Users = client.collection('users'); client.Files = client.collection('files'); diff --git a/lib/redis.js b/lib/redis.js index 94c7f67..94b6e84 100644 --- a/lib/redis.js +++ b/lib/redis.js @@ -5,7 +5,7 @@ import session from 'koa-generic-session'; import debugname from 'debug'; const debug = debugname('hostr:redis'); -const redisUrl = process.env.REDIS_URL || process.env.REDISTOGO_URL || 'redis://localhost:6379'; +const redisUrl = process.env.REDIS_URL; const connection = new Promise((resolve, reject) => { debug('Connecting to Redis'); diff --git a/lib/s3.js b/lib/s3.js index 724255e..eb79f37 100644 --- a/lib/s3.js +++ b/lib/s3.js @@ -3,17 +3,15 @@ import s3UploadStream from 's3-upload-stream'; import debugname from 'debug'; const debug = debugname('hostr:s3'); -const bucket = process.env.AWS_BUCKET || 'hostrdotcodev'; - const s3 = new aws.S3(); const s3Stream = s3UploadStream(s3); export function get(key) { - debug('fetching file: %s', 'hostr_files/' + key); - return s3.getObject({Bucket: bucket, Key: 'hostr_files/' + key}).createReadStream(); + debug('fetching from s3: %s', 'hostr_files/' + key); + return s3.getObject({Bucket: process.env.AWS_BUCKET, Key: 'hostr_files/' + key}).createReadStream(); } export function upload(key) { - debug('Uploading file: %s', 'hostr_files/' + key); - return s3Stream.upload({Bucket: bucket, Key: 'hostr_files/' + key}); + debug('sending to s3: %s', 'hostr_files/' + key); + return s3Stream.upload({Bucket: process.env.AWS_BUCKET, Key: 'hostr_files/' + key}); } diff --git a/lib/storage.js b/lib/storage.js index 7ad8d35..c533968 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -9,7 +9,7 @@ function range(start, stop) { return result; } -const storePath = process.env.FILE_PATH || path.join(process.env.HOME, '.hostr', 'uploads'); +const storePath = process.env.UPLOAD_STORAGE_PATH; const directories = range('A', 'Z').concat(range('a', 'z'), range('0', '9')); diff --git a/web/app.js b/web/app.js index 3d5aa17..2ff6f66 100644 --- a/web/app.js +++ b/web/app.js @@ -16,7 +16,7 @@ const router = new Router(); router.use(errors({template: path.join(__dirname, 'public', 'error.html')})); -const statsdOpts = {prefix: 'hostr-web', host: process.env.STATSD_HOST || 'localhost'}; +const statsdOpts = {prefix: 'hostr-web', host: process.env.STATSD_HOST}; router.use(stats(statsdOpts)); const statsd = new StatsD(statsdOpts); router.use(function* statsMiddleware(next) { @@ -29,8 +29,8 @@ router.use(redis.sessionStore()); router.use(function* stateMiddleware(next) { this.state = { session: this.session, - apiURL: process.env.API_URL, - baseURL: process.env.BASE_URL, + baseURL: process.env.WEB_BASE_URL, + apiURL: process.env.API_BASE_URL, stripePublic: process.env.STRIPE_PUBLIC_KEY, }; yield next; diff --git a/web/lib/auth.js b/web/lib/auth.js index 0bf3101..e2b4cc9 100644 --- a/web/lib/auth.js +++ b/web/lib/auth.js @@ -94,11 +94,11 @@ export function* signup(email, password, ip) { }; Users.insertOne(user); - const html = yield render('email/inlined/activate', {activationUrl: process.env.BASE_URL + '/activate/' + user.activationCode}); + const html = yield render('email/inlined/activate', {activationUrl: process.env.WEB_BASE_URL + '/activate/' + user.activationCode}); const text = `Thanks for signing up to Hostr! Please confirm your email address by clicking the link below. -${process.env.BASE_URL + '/activate/' + user.activationCode} +${process.env.WEB_BASE_URL + '/activate/' + user.activationCode} — Jonathan Cremin, Hostr Founder `; @@ -130,9 +130,9 @@ export function* sendResetToken(email) { 'token': token, 'created': Math.round(new Date().getTime() / 1000), }); - const html = yield render('email/inlined/forgot', {forgotUrl: process.env.BASE_URL + '/forgot/' + token}); + const html = yield render('email/inlined/forgot', {forgotUrl: process.env.WEB_BASE_URL + '/forgot/' + token}); const text = `It seems you've forgotten your password :( -Visit ${process.env.BASE_URL + '/forgot/' + token} to set a new one. +Visit ${process.env.WEB_BASE_URL + '/forgot/' + token} to set a new one. `; mandrill.messages.send({message: { html: html, diff --git a/web/routes/file.js b/web/routes/file.js index 9657d2c..4968fb7 100644 --- a/web/routes/file.js +++ b/web/routes/file.js @@ -3,7 +3,7 @@ import mime from 'mime-types'; import hostrFileStream from '../../lib/hostr-file-stream'; import { formatFile } from '../../lib/format'; -const storePath = process.env.STORE_PATH || path.join(process.env.HOME, '.hostr', 'uploads'); +const storePath = process.env.UPLOAD_STORAGE_PATH; function userAgentCheck(userAgent) { if (!userAgent) { diff --git a/web/routes/pro.js b/web/routes/pro.js index 391b79d..df09893 100644 --- a/web/routes/pro.js +++ b/web/routes/pro.js @@ -6,8 +6,8 @@ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY); import { Mandrill } from 'mandrill-api/mandrill'; const mandrill = new Mandrill(process.env.MANDRILL_KEY); -const fromEmail = process.env.EMAIL_FROM || 'nobody@example.com'; -const fromName = process.env.EMAIL_NAME || 'Nobody'; +const fromEmail = process.env.EMAIL_FROM; +const fromName = process.env.EMAIL_NAME; export function* create() { const Users = this.db.Users;