Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

added www server

  • Loading branch information...
commit f2daf13a1d3238087b64e8e299dd09f8fb1c751c 1 parent 4f5eb52
@caolan authored
View
34 bin/start
@@ -0,0 +1,34 @@
+#!/usr/bin/env node
+
+/* vim: set filetype=javascript : */
+
+/**
+ * Dependencies
+ */
+
+var config = require('../lib/config'),
+ app = require('../lib/app');
+
+
+/**
+ * Start a Hoodie server configured for the current
+ * platform and environment
+ */
+
+if (require.main === module) {
+ var project_dir = process.cwd();
+
+ var cfg = config.getConfig(
+ process.platform, // platform
+ process.env, // environment vars
+ project_dir // project directory
+ );
+
+ app.init(cfg, function (err) {
+ if (err) {
+ console.error(err);
+ process.exit(1);
+ }
+ console.log('Ready!');
+ });
+}
View
43 lib/app.js
@@ -0,0 +1,43 @@
+/**
+ * Initializes directories, installs dependencies then starts all
+ * configured servers for the Hoodie application
+ */
+
+var path = require('path'),
+ async = require('async'),
+ utils = require('./utils'),
+ www = require('./servers/www');
+
+
+/**
+ * Initializes and starts a new Hoodie app server
+ */
+
+exports.init = function (config, callback) {
+ console.log('Initializing...');
+ exports.ensurePaths(config, function (err) {
+ if (err) {
+ return callback(err);
+ }
+ console.log('Starting www server');
+ www.start(config, function (err) {
+ if (err) {
+ return callback(err);
+ }
+ callback();
+ });
+ });
+};
+
+/**
+ * Makes sure the appropriate app directories exists
+ */
+
+exports.ensurePaths = function (config, callback) {
+ var paths = [
+ config.hoodie_path,
+ config.apps_path,
+ path.join(config.apps_path, config.app.name)
+ ];
+ async.map(paths, utils.ensureDir, callback);
+};
View
107 lib/config.js
@@ -0,0 +1,107 @@
+/**
+ * This module returns appropriate config values for the provided
+ * platform, environment and project. It is used directly by the start
+ * script in the bin directory.
+ */
+
+var fs = require('fs'),
+ path = require('path'),
+ _ = require('underscore');
+
+
+/**
+ * Returns all config values for current environment
+ */
+
+exports.getConfig = function (platform, env, project_dir) {
+ // location of project's package.json
+ var pkgfile = path.resolve(project_dir, 'package.json');
+
+ // default platform-agnostic config
+ var config = {
+ project_dir: project_dir,
+ //host: '0.0.0.0',
+ app: JSON.parse(fs.readFileSync(pkgfile)),
+ domain: 'dev',
+ run_local_tld: false,
+ run_couch: true,
+ www_server: {
+ host: '127.0.0.1',
+ port: 8888
+ }
+ };
+
+ // add CouchDB paths to config
+ config = _.extend(config, exports.getCouch(env));
+ // add Hoodie paths to config
+ config = _.extend(config, exports.getHoodie(platform));
+
+ // do magic firewall stuff for .dev domains on mac
+ if (platform === 'darwin') {
+ config.run_local_tld = true;
+ }
+
+ // Nodejitsu config
+ if (exports.isNodejitsu(env)) {
+ config.run_couch = false;
+ config.domain = 'jit.su';
+ }
+
+ return config;
+}
+
+/**
+ * Attempts to detect a Nodejitsu environment
+ */
+
+exports.isNodejitsu = function (env) {
+ return !!(env.SUBDOMAIN);
+}
+
+/**
+ * Find CouchDB locations
+ */
+
+exports.getCouch = function (env) {
@janl
janl added a note

maybe use isaacs/node-which here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ var cfg = {};
+ // check filesystem for likely couch paths
+ if (fs.existsSync('/usr/local/bin/couchdb')) {
+ cfg.couch_bin = '/usr/local/bin/couchdb';
+ cfg.couch_default_ini = '/usr/local/etc/couchdb/default.ini';
+ }
+ else if (fs.existsSync('/usr/bin/couchdb')) {
+ cfg.couch_bin = '/usr/bin/couchdb';
+ cfg.couch_default_ini = '/etc/couchdb/default.ini';
+ }
+ // override if environment vars set
+ if (env.COUCH_BIN) {
+ cfg.couch_bin = env.COUCH_BIN;
+ }
+ if (env.COUCH_DEFAULT_INI) {
+ cfg.couch_defualt_ini = env.COUCH_DEFAULT_INI;
+ }
+ return cfg;
+}
+
+/**
+ * Find Hoodie locations
+ */
+
+exports.getHoodie = function (platform, env) {
+ var home = process.env.HOME,
+ cfg = {};
+
+ if (process.platform === 'darwin') {
@janl
janl added a note

Just as a note:

I could have picked ~/.hoodie for Mac OS X as well, but given that we store db data in there, I put it into ~/Library as Mac usuers generally don’t expect dot file-dirs to fill up a lot.

We still might want to revisit this at some point and do ~/.hoodie on all unixes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ cfg.hoodie_path = path.resolve(home, "Library/Hoodie");
+ cfg.apps_path = path.resolve(cfg.hoodie_path, 'Apps');
+ return cfg;
+ }
+ else if (process.platform === 'linux') {
+ cfg.hoodie_path = path.resolve(home, '.hoodie');
+ cfg.apps_path = path.resolve(cfg.hoodie_path, 'apps');
+ return cfg;
+ }
+ else {
+ throw new Error('Unsuported platform: ' + process.platform);
+ }
+}
View
317 lib/hoodie-app.js
@@ -1,317 +0,0 @@
-// Start a Hoodie app
-var fs = require("fs");
-var npm = require("npm");
-var path = require("path");
-var request = require("request");
-var readline = require("readline");
-var http_proxy = require("http-proxy");
-var MultiCouch = require("multicouch");
-var exec = require("child_process").exec;
-var hoodie_server = require("./hoodie-server");
-var HoodieInstaller = require("./hoodie-installer");
-
-
-var ltld;
-if (process.platform !== 'linux') {
- ltld = require("local-tld-lib");
-}
-
-var host = "0.0.0.0";
-var http_port = parseInt(process.env.port, 10) || (
- process.platform === 'linux' ? 8080: 80
-);
-var domain = "dev";
-
-var package_json = JSON.parse(fs.readFileSync("./package.json"));
-var name = package_json.name.toLowerCase();
-
-var couch_port = process.env.COUCH_PORT || 8081;
-var couch_url = "http://couch." + name + "." + domain + ":80";
-if (process.platform === 'linux') {
- couch_url = 'http://localhost:' + couch_port;
-}
-
-var hoodie = {};
-
-var home = process.env.HOME;
-var hoodie_path = path.resolve(home, "Library/Hoodie");
-if (process.platform === 'linux') {
- hoodie_path = path.resolve(home, '.hoodie');
-}
-var hoodie_apps_path = path.resolve(hoodie_path, 'Apps');
-if (process.platform === 'linux') {
- hoodie_apps_path = path.resolve(hoodie_path, 'apps');
-}
-
-var couch_bin = process.env.COUCH_BIN,
- couch_default_ini = process.env.COUCH_DEFAULT_INI;
-
-if (!couch_bin) {
- if (fs.existsSync('/usr/local/bin/couchdb')) {
- couch_bin = '/usr/local/bin/couchdb';
- if (!couch_default_ini) {
- couch_default_ini = '/usr/local/etc/couchdb/default.ini';
- }
- }
- else if (fs.existsSync('/usr/bin/couchdb')) {
- couch_bin = '/usr/bin/couchdb';
- if (!couch_default_ini) {
- couch_default_ini = '/etc/couchdb/default.ini';
- }
- }
- else {
- console.log('no couchdb installation found');
- process.exit(1);
- }
-}
-
-
-// set up domain names based on the environment we run in
-
-// if we are local, start a couch
-
-// run setup
-
-// start hoodie service
-
-// start workers
-
-// if we are on nodejitsu, we require couch_url.
-if(process.env.SUBDOMAIN) { // we are on nodejitsu
- domain = "jit.su";
- couch_url = process.env.COUCH_URL;
- // TODO: verify couchdb_url is reachable
- couch_port = 8081;
- if(!couch_url) {
- throw "FATAL: NO COUCH URL"
- }
-
- console.log("starting in nodejitsu. ENV debug:");
- console.log(process.env)
-
- setup();
-} else {
- if (ltld) {
- var http_port = ltld.getPort(name);
- couch_port = ltld.getPort("couch." + name);
- ltld.setAlias(name, "www");
- ltld.setAlias(name, "admin");
- ltld.setAlias(name, "api");
- }
-
- console.log("Start local couch on port: %d", couch_port);
- // prepare hoodir dirs if they don’t exist:
- // mkdir -p $HOME/Application Support/Hoodie/Apps/myapp/
- mkdir_p(hoodie_path);
- mkdir_p(hoodie_apps_path);
- mkdir_p(path.join(hoodie_apps_path, name));
-
- // if we are not on nodejitsu, make us a couch
- var couchdb = new MultiCouch({
- prefix: path.join(hoodie_apps_path, name),
- port: couch_port,
- couchdb_path: couch_bin,
- default_sys_ini: couch_default_ini
- });
-
- couchdb.on("start", function() {
- console.log("CouchDB Started");
- setTimeout(setup, 2000);
- });
-
- couchdb.on("error", function(error) {
- console.log("CouchDB Error: %j", error);
- });
-
- process.on("exit", function() {
- couchdb.stop();
- console.log("CouchDB stop triggered by exit");
- });
-
- // on ctrl-c, stop couchdb first, then exit.
- process.on("SIGINT", function() {
- couchdb.on("stop", function() {
- process.exit(0);
- });
- couchdb.stop();
- });
-
- couchdb.start();
-}
-
-var hoo = new hoodie_server(name, domain, couch_url);
-// start frontend proxy
-var server = http_proxy.createServer(hoo);
-server.listen(http_port, "0.0.0.0", function() {
- console.log("hoodie server started on port '%d'", http_port);
-});
-
-function start_workers() {
- var worker_names = [];
- var deps = package_json.dependencies;
- for(var dep in deps) {
- if(dep.substr(0, 7) == "worker-") {
- worker_names.push(dep);
- }
- }
-
- npm.load(function(error, npm) {
- var password = npm.config.get(name + "_admin_pass");
- // for each package_json/worker*
-
- // if port is not set, set it explicitely
- if( ! /:\d+$/.test(couch_url)) {
- couch_url = couch_url + ":80";
- }
- var config = {
- server: couch_url,
- admin: {
- user: "admin",
- pass: password || process.env["HOODIE_ADMIN_PASS"]
- },
- persistent_since_storage: false
- };
- var workers = worker_names.map(function(worker_name) {
- console.log("starting: '%s'", worker_name);
- // start worker
-
- var worker = require("hoodie-" + worker_name);
- config.name = worker_name.replace(/^worker-/, '');
-
- // temp debug
- console.log("initializing %s worker with: ", name)
- console.log(JSON.parse(JSON.stringify(config)));
- return new worker(JSON.parse(JSON.stringify(config)));
- });
- console.log("All workers started.");
- console.log("Your App is ready now.");
- });
-}
-
-function setup() {
- var installer = new HoodieInstaller(name, couch_url);
-
- installer.on("done", function() {
-
- // setup modules DB
- npm.load(function(error, npm) {
- if(error) {
- throw error;
- }
- var password = npm.config.get(name + "_admin_pass") || process.env["HOODIE_ADMIN_PASS"];
- //otherwise authentication with only digit passwords fails
- password = password.toString();
-
- request({
- url: couch_url + "/modules",
- method: "PUT",
- auth: {
- user: "admin",
- pass: password
- }
- }, function(error, response, body) {
- if(error) {
- throw error;
- }
-
- var object = {
- _id : "module/appconfig",
- config : {},
- name: name,
- createdAt : new Date(),
- updatedAt : new Date()
- }
-
- request({
- url: couch_url + "/modules/module%2Fappconfig",
- method: "PUT",
- auth: {
- user: "admin",
- pass: password
- },
- body: JSON.stringify(object)
- }, function(error, response, body) {
- if(error) {
- throw error;
- }
-
- console.log("setup done");
-
- // boom
- console.log("");
- console.log("open http://%s.%s in your browser", name, domain);
- console.log("");
- exec('open http://' + name + '.' + domain)
-
- var HoodieLog = require("./hoodie-log");
- hoodie.log = new HoodieLog(name, couch_url);
-
- start_workers();
- });
-
- request({
- url: couch_url + "/modules/_security",
- method: "PUT",
- auth: {
- user: "admin",
- pass: password
- },
- body: JSON.stringify({"admins": {"names": [],"roles": []},"members": {"names": [],"roles": ["_admin"]}})
- }, function(error, response, body) {
- if(error) {
- throw error;
- }
- });
- });
- });
-
- });
-
- installer.on("prompt_pass", function () {
- var rl = readline.createInterface({
- input: process.stdin,
- output: process.stdout
- });
-
- rl.question("Please set an admin password: ", function(password) {
- installer.emit("set_password", password);
- rl.close();
- });
- });
-
- installer.on("set_password", function(password) {
- request({
- url: couch_url + "/_config/admins/admin",
- method: "PUT",
- body: '"' + password + '"'
- }, function(error) {
- if(error !== null) {
- console.trace("set_password");
- console.log(error);
- throw error;
- }
- npm.load(function(error, npm) {
- if(error !== null) {
- console.log(error)
- throw error;
- }
- var config = {
- key: name + "_admin_pass",
- value: password
- };
- var result = npm.commands.config(["set", config.key, config.value]);
- });
- installer.emit("done");
- });
- });
-}
-
-// * * *
-
-function mkdir_p(dir) {
- try {
- fs.mkdirSync(dir);
- } catch(e) {
- // nope
- }
-}
View
35 lib/hoodie-installer.js
@@ -1,35 +0,0 @@
-var npm = require("npm");
-var util = require("util");
-var request = require("request");
-var EventEmitter = require("events").EventEmitter;
-
-module.exports = HoodieInstaller;
-util.inherits(HoodieInstaller, EventEmitter);
-
-function HoodieInstaller(name, couch_url) {
-
- EventEmitter.call(this);
-
- var couch_has_admin = function couch_has_admin(cb) {
- // let’s request /_users
- // if we get a 401, there is an admin
- request(couch_url + "/_users/_all_docs", function(error, response) {
- if(error !== null) {
- console.trace("couch_has_admin");
- console.log(couch_url + "/_users");
- console.log(error);
- throw error;
- }
- cb((response.statusCode == 403));
- });
- }
-
- var that = this;
- couch_has_admin(function(result) {
- if(result) {
- that.emit("done");
- } else {
- that.emit("prompt_pass");
- }
- });
-}
View
36 lib/hoodie-log.js
@@ -1,36 +0,0 @@
-var npm = require("npm");
-var util = require("util");
-var request = require("request");
-
-module.exports = function HoodieLog(name, couch_url) {
- var password;
- npm.load(function(error, npm) {
- password = npm.config.get(name + "_admin_pass");
- });
-
- return function hoodie_log() {
- // log as usual
- console.log.apply(this, arguments);
-
- // make a nice object!
- var log = {
- message: util.format.apply(this, arguments),
- time: new Date()
- };
-
- // send to couch
- request({
- url: couch_url + "/logs",
- method: "POST",
- auth: {
- user: "admin",
- pass: password
- },
- json: log
- }, function(error) {
- if(error) {
- throw error;
- }
- });
- };
-};
View
168 lib/hoodie-server.js
@@ -1,168 +0,0 @@
-var cors_http = require("corsproxy");
-var static_http = require("connect/lib/middleware/static");
-var url = require("url");
-var fs = require("fs");
-var os = require("os");
-
-module.exports = make_hoodie_server;
-
-function make_hoodie_server(name, host, couch_url, catchall) {
- this.couchdb = couch_url;
- this.catchall = catchall || true;
- this.name = name;
- this.host = host;
- this.dns_alias_host = "xip.io";
- this.local_ips = this.get_local_ips();
- return hoodie_server.bind(this);
-}
-
-make_hoodie_server.prototype.serve_static = function(host, name) {
- var match_hosts = [
- "www." + name + "." + this.host,
- name + "." + this.host
- ];
-
- this.local_ips.forEach(function(local_ip) {
- match_hosts.push("www." + name + "." + local_ip + "." + this.dns_alias_host);
- match_hosts.push(name + "." + local_ip + "." + this.dns_alias_host);
- }, this);
-
- if(match_hosts.indexOf(host) !== -1) {
- return true;
- } else {
- return false;
- }
-};
-
-make_hoodie_server.prototype.serve_cors = function(host, name) {
- var hosts = this.local_ips.map(function(local_ip) {
- return local_ip + "." + this.dns_alias_host;
- }, this);
- hosts.push(this.host);
- var suffix = name + "." + "(" + hosts.join("|") + ")";
- var matcher = new RegExp("api." + suffix + "$");
-
- return matcher.test(host);
-};
-
-make_hoodie_server.prototype.serve_admin = function(host, name) {
- var match_hosts = [
- "admin." + name + "." + this.host
- ];
-
- this.local_ips.forEach(function(local_ip) {
- match_hosts.push("admin." + name + "." + local_ip + "." + this.dns_alias_host);
- }, this);
-
- if(match_hosts.indexOf(host) !== -1) {
- return true;
- } else {
- return false;
- }
-};
-
-make_hoodie_server.prototype.serve_api = function(req) {
- return req.url.match(/^\/_api/);
-}
-
-var hoodie_server = function(req, res, proxy) {
-
- var host = req.headers.host;
- var static_server, admin_server;
-
- // if req.url == ^/_api*
- // serve couch request
- if(this.serve_api(req)) {
- var parsed_url = url.parse(this.couchdb);
- // proxy to this.couch_url
- var target = {
- host: parsed_url.hostname,
- port: parsed_url.port
- };
- var new_url = req.url.replace(/^\/_api/, "");
- console.log("[api req] ie fallback %s %s %j/%s", req.method, req.url, target, new_url);
- req.url = new_url;
- req.headers.host = "couch." + host;
- return proxy.proxyRequest(req, res, target);
- }
-
- // frontend proxy duties
- // if host == [www.]APPNAME.domain
- // serve ./www
- if(this.serve_static(host, this.name)) {
- console.log("[static req] %s %s", req.method, req.url);
- static_server = static_http("./www");
- var that = this;
- return static_server(req, res, function() {
- handle_static_404(res, "static", that);
- });
- }
-
- // if host == *api.APPNAME.domain
- // serve CORS
- if(this.serve_cors(host, this.name)) {
- console.log("[api req] %s %s", req.method, req.url);
- cors_http.options = {
- target: this.couchdb
- }
- return cors_http(req, res, proxy);
- }
-
- // launch httpd for Admin UI
- // if host == admin.APPNAME.domain
- // serve ./node-modules/hoodie-app/www
- if(this.serve_admin(host, this.name)) {
- console.log("[admin req] %s %s", req.method, req.url);
- admin_server = static_http("./node_modules/hoodie-app/node_modules/hoodie-pocket/www");
- var that = this;
- return admin_server(req, res, function() {
- handle_static_404(res, "admin", that);
- });
- }
-
- // default handler
- console.log("[unknown req] %s %s", req.method, req.url);
- res.writeHead(404, "Not Found");
- res.end("404 Not Found");
-};
-
-var handle_static_404 = function handle_static_404(res, type, that) {
- // if this.catchall is true, serve ./www/index.html
- if(that.catchall) {
- console.log("-> [%s req] redirect to index.html", type);
- res.writeHead(201, "OK");
- var file = fs.createReadStream("./www/index.html");
- file.pipe(res);
- } else {
- console.log("-> [%s req] 404", type);
- res.writeHead(404, "Not Found");
- res.end("Not Found");
- }
-}
-
-make_hoodie_server.prototype.get_local_ips = function get_local_ips()
-{
- var ifconfig = os.networkInterfaces();
- var local_ips = [];
- var if_name;
-
- for(if_name in ifconfig) {
- if(if_name.substr(0, 2) != "en") {
- continue; // for other unixes learn other interface names
- }
- var _if = ifconfig[if_name];
- _if.forEach(function(address_info) {
- if(address_info.internal) {
- return; // no loopback!
- }
-
- if(address_info.family == 'IPv6') {
- return; // no
- }
-
- local_ips.push(address_info.address);
-
- });
- }
- return local_ips;
-}
View
32 lib/servers/loggers.js
@@ -0,0 +1,32 @@
+/**
+ * ConnectJS compatible log functions
+ */
+
+var bytes = require('bytes');
+
+
+/**
+ * Development logger with color output and response times
+ */
+
+exports.dev = function (server_name) {
+ return function (tokens, req, res) {
+ var status = res.statusCode,
+ len = parseInt(res.getHeader('Content-Length'), 10),
+ color = 32;
+
+ if (status >= 500) color = 31
+ else if (status >= 400) color = 33
+ else if (status >= 300) color = 36;
+
+ len = isNaN(len) ? '' : len = ' - ' + bytes(len);
+
+ return '\033[90m' + '[' + server_name + '] ' + req.method
+ + ' ' + req.originalUrl + ' '
+ + '\033[' + color + 'm' + res.statusCode
+ + ' \033[90m'
+ + (new Date - req._startTime)
+ + 'ms' + len
+ + '\033[0m';
+ };
+};
View
38 lib/servers/www.js
@@ -0,0 +1,38 @@
+/**
+ * Serves the app's static assets from the www directory
+ */
+
+var connect = require('connect'),
+ loggers = require('./loggers'),
+ http = require('http'),
+ path = require('path');
+
+
+/**
+ * Starts a new HTTP server
+ */
+
+exports.start = function (config, callback) {
+ // project's favicon location
+ var static_dir = path.resolve(config.project_dir, 'www');
+
+ // Defines a new ConnectJS app
+ var app = connect(
+ connect.logger(loggers.dev('www')),
+ connect.compress(),
+ connect.static(static_dir)
+ );
+ return http.createServer(app).listen(
+ config.www_server.port,
+ config.www_server.host,
+ callback
+ );
+};
+
+/**
+ * Stops the provided HTTP server
+ */
+
+exports.stop = function (server, callback) {
+ server.close(callback);
+};
View
23 lib/utils.js
@@ -0,0 +1,23 @@
+var child_process = require('child_process');
+
+/**
+ * Ensures a directory exists using mkdir -p.
+ *
+ * @param {String} path
+ * @param {Function} callback
+ * @api public
+ */
+
+exports.ensureDir = function (path, callback) {
+ var mkdir = child_process.spawn('mkdir', ['-p', path]);
+ var err_data = '';
+ mkdir.stderr.on('data', function (data) {
+ err_data += data.toString();
+ });
+ mkdir.on('exit', function (code) {
+ if (code !== 0) {
+ return callback(new Error(err_data));
+ }
+ callback();
+ });
+};
View
46 package.json
@@ -1,24 +1,26 @@
{
- "name": "hoodie-app",
- "version": "0.4.13",
- "dependencies": {
- "http-proxy": "*",
- "connect": "*",
- "corsproxy": "git://github.com/gr2m/CORS-Proxy.git",
- "multicouch": ">=0.2.5",
- "request": ">2.0.0",
- "npm": "*",
- "hoodie-pocket": "git://github.com/hoodiehq/pocket.git",
- "local-tld-lib": "*"
- },
- "scripts": {
- "start": "node lib/hoodie-app.js",
- "test": "mocha"
- },
- "engines": {
- "node": "0.8.x"
- },
- "devDependencies": {
- "mocha": "*"
- }
+ "name": "hoodie-app",
+ "version": "0.4.13",
@janl
janl added a note

you wanna bump this :)

@caolan Owner
caolan added a note

But I barely changed anything! ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ "dependencies": {
+ "http-proxy": "*",
+ "connect": "2.7.4",
+ "corsproxy": "git://github.com/gr2m/CORS-Proxy.git",
+ "multicouch": ">=0.2.5",
+ "hoodie-pocket": "git://github.com/hoodiehq/pocket.git",
+ "local-tld-lib": "*",
+ "underscore": "1.4.4",
+ "async": "0.2.6",
+ "bytes": "0.2.1",
+ "npm": "*"
+ },
+ "scripts": {
+ "start": "bin/start",
+ "test": "mocha"
+ },
+ "engines": {
+ "node": "0.8.x"
+ },
+ "devDependencies": {
+ "mocha": "*"
+ }
}
Please sign in to comment.
Something went wrong with that request. Please try again.