[med-svn] [shiny-server] 01/02: New upstream version 1.5.0.831+dfsg

Andreas Tille tille at debian.org
Fri Mar 24 14:11:11 UTC 2017


This is an automated email from the git hooks/post-receive script.

tille pushed a commit to branch master
in repository shiny-server.

commit a9c690d07365f9fd4197405cf6b2af9b914a4aeb
Author: Andreas Tille <tille at debian.org>
Date:   Fri Mar 24 15:10:14 2017 +0100

    New upstream version 1.5.0.831+dfsg
---
 .gitignore                                     |   11 +
 CMakeLists.txt                                 |  185 ++
 COPYING                                        |  667 ++++++++
 NEWS                                           |  187 ++
 NOTICE.md                                      | 2166 ++++++++++++++++++++++++
 R/SockJSAdapter.R                              |  246 +++
 README.md                                      |   43 +
 assets/shiny-server.css                        |   69 +
 bin/deploy-example.in                          |   91 +
 bin/node                                       |    5 +
 bin/npm                                        |    5 +
 binding.gyp                                    |    8 +
 config.html                                    |  894 ++++++++++
 config/default.config                          |   21 +
 config/init.d/debian/shiny-server              |  106 ++
 config/init.d/redhat/shiny-server              |  100 ++
 config/init.d/suse/shiny-server                |   98 ++
 config/logrotate                               |    7 +
 config/multi-server.config                     |   46 +
 config/shiny-server-rules.config               |  218 +++
 config/systemd/shiny-server.debian.service     |   18 +
 config/systemd/shiny-server.service            |   18 +
 config/upstart/shiny-server.conf               |   26 +
 config/user-dirs.config                        |   20 +
 docker/README.md                               |   85 +
 docker/ubuntu16.04/Dockerfile                  |   80 +
 external/install-dependencies-debian           |   15 +
 external/install-dependencies-redhat5          |   24 +
 external/install-dependencies-redhat6          |   17 +
 external/node/CMakeLists.txt                   |   50 +
 external/pandoc/CMakeLists.txt                 |   64 +
 lib/config/app-config.js                       |  118 ++
 lib/config/config.js                           |  224 +++
 lib/config/lexer.js                            |  315 ++++
 lib/config/parser.js                           |  152 ++
 lib/config/schema.js                           |  230 +++
 lib/core/connect-util.js                       |   25 +
 lib/core/errors.js                             |   22 +
 lib/core/fsutil.js                             |  133 ++
 lib/core/log.js                                |   16 +
 lib/core/map.js                                |   33 +
 lib/core/paths.js                              |   21 +
 lib/core/permissions.js                        |   39 +
 lib/core/qutil.js                              |  177 ++
 lib/core/re-quote.js                           |   17 +
 lib/core/render.js                             |  146 ++
 lib/core/shutdown.js                           |   13 +
 lib/events/simple-event-bus.js                 |   25 +
 lib/main.js                                    |  349 ++++
 lib/proxy/errorcode.js                         |   77 +
 lib/proxy/http.js                              |  330 ++++
 lib/proxy/multiplex.js                         |  194 +++
 lib/proxy/robust-sockjs.js                     |  307 ++++
 lib/proxy/sockjs.js                            |  239 +++
 lib/router/config-router-util.js               |  115 ++
 lib/router/config-router.js                    |  501 ++++++
 lib/router/directory-router.js                 |  449 +++++
 lib/router/local-config-router.js              |   63 +
 lib/router/router.js                           |  333 ++++
 lib/router/squash-run-as-router.js             |   48 +
 lib/router/user-dirs-router.js                 |   85 +
 lib/scheduler/scheduler-registry.js            |   91 +
 lib/scheduler/scheduler.js                     |  400 +++++
 lib/scheduler/simple-scheduler.js              |   71 +
 lib/server/server.js                           |  203 +++
 lib/transport/shared.js                        |   29 +
 lib/transport/tcp.js                           |  154 ++
 lib/transport/unix-socket.js                   |  129 ++
 lib/worker/app-spec.js                         |   36 +
 lib/worker/app-worker-handle.js                |   40 +
 lib/worker/app-worker.js                       |  430 +++++
 lib/worker/run-as.js                           |   56 +
 manual.test/config/bad1.config                 |    2 +
 manual.test/config/bad2.config                 |    1 +
 manual.test/config/bad3.config                 |    1 +
 manual.test/config/good.config                 |   46 +
 manual.test/config/schemaBad1.config           |    3 +
 manual.test/config/schemaBad2.config           |    4 +
 manual.test/loadtest-xhr.js                    |   56 +
 manual.test/loadtest.js                        |  264 +++
 manual.test/phantomjs/loadtest-user.js         |   51 +
 manual.test/phantomjs/loadtest.js              |   52 +
 manual.test/phantomjs/loadtime.js              |   32 +
 manual.test/test-app-worker.js                 |   17 +
 manual.test/test-config-config.js              |   50 +
 manual.test/test-config-lexer.js               |  116 ++
 manual.test/test-config-parser.js              |   15 +
 manual.test/test-proxy.js                      |   15 +
 manual.test/test-serialized.js                 |   16 +
 manual.test/test-worker-registry-leak.js       |   31 +
 manual.test/test-worker-registry.js            |   19 +
 npm-shrinkwrap.json                            |  885 ++++++++++
 package.json                                   |   47 +
 packaging/debian-control/postinst.in           |  115 ++
 packaging/debian-control/postrm.in             |   18 +
 packaging/debian-control/prerm.in              |   30 +
 packaging/make-package-jenkins.sh              |   79 +
 packaging/make-package.sh                      |   83 +
 packaging/rpm-script/postinst.sh.in            |   94 +
 packaging/rpm-script/postrm.sh.in              |   16 +
 packaging/rpm-script/prerm.sh.in               |   21 +
 samples/sample-apps/hello/server.R             |   21 +
 samples/sample-apps/hello/ui.R                 |   22 +
 samples/sample-apps/rmd/index.Rmd              |   25 +
 samples/welcome.html                           |  129 ++
 scripts/mkdir.sh                               |   23 +
 src/CMakeLists.txt                             |    2 +
 src/launcher.cc                                |  114 ++
 src/launcher.h.in                              |    1 +
 src/posix.cc                                   |  274 +++
 templates/config.html                          |   48 +
 templates/directoryIndex.html                  |   39 +
 templates/error.html                           |   47 +
 test/app-config.js                             |  148 ++
 test/apps/01_hello/server.R                    |   20 +
 test/apps/01_hello/ui.R                        |   22 +
 test/config-router-util.js                     |  160 ++
 test/configs/bad1.config                       |   13 +
 test/configs/bad2.config                       |   11 +
 test/configs/testapps.config.in                |   11 +
 test/configs/valid.config                      |   29 +
 test/mocha.opts                                |    4 +
 test/nested-locations.js                       |   80 +
 test/proxy-events.js                           |   68 +
 test/render.js                                 |  191 +++
 test/robust-sockjs.js                          |   97 ++
 test/scheduler-registry.js                     |  114 ++
 test/scheduler.js                              |  155 ++
 test/simple-scheduler.js                       |  177 ++
 test/squash-run-as-router.js                   |   67 +
 tools/_setup-devenv-common.sh                  |   30 +
 tools/check-licenses.js                        |  120 ++
 tools/check-upstream.sh                        |   10 +
 tools/is-merged.sh                             |   17 +
 tools/makedocs.js                              |  138 ++
 tools/memlog-view.R                            |   22 +
 tools/preflight.sh                             |   10 +
 tools/setup-devenv-debian.sh                   |    9 +
 tools/setup-devenv-redhat.sh                   |    9 +
 tools/test-config.sh                           |   23 +
 upstream.txt                                   |    6 +
 vagrant/build-servers/ubuntu12.04/Vagrantfile  |   70 +
 vagrant/build-servers/ubuntu12.04/build-sso.sh |    9 +
 vagrant/build-servers/ubuntu12.04/setup.sh     |   27 +
 vagrant/centos6/Vagrantfile                    |   74 +
 vagrant/centos6/setup.sh                       |   25 +
 vagrant/centos7/Vagrantfile                    |   82 +
 vagrant/centos7/setup.sh                       |   26 +
 vagrant/nfs/README.md                          |   15 +
 vagrant/nfs/Vagrantfile                        |   29 +
 vagrant/nfs/bootstrap-debian.sh                |   24 +
 vagrant/nfs/provision-nfs.sh                   |   17 +
 vagrant/nfs/provision-sso.sh                   |   30 +
 vagrant/nfs/shiny-server.conf                  |   20 +
 vagrant/ubuntu12.04/Vagrantfile                |   69 +
 vagrant/ubuntu12.04/setup.sh                   |   23 +
 vagrant/ubuntu14.04/Vagrantfile                |   70 +
 vagrant/ubuntu14.04/setup.sh                   |   23 +
 vagrant/ubuntu15.04/Vagrantfile                |   66 +
 vagrant/ubuntu15.04/setup.sh                   |   19 +
 160 files changed, 17428 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c7e9262
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+build/
+npm-debug.log
+*.swp
+.DS_Store
+bin/shiny-server
+external/node/ex-node1234/
+external/node/node-*
+ext/
+src/launcher.h
+.vagrant
+node_modules/
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..5201fe7
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,185 @@
+cmake_minimum_required(VERSION 2.8.10)
+
+project(shiny-server)
+
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin")
+
+if ("$ENV{BUILD_NUMBER}" STREQUAL "")
+  set(BUILD_NUMBER "0")
+else()
+  set(BUILD_NUMBER $ENV{BUILD_NUMBER})
+endif()
+
+# Extract the shiny-server version number from package.json
+execute_process(COMMAND sed -n "s/\\s*\"version\": \"\\(.*\\)\",\\s*/\\1/p"
+                INPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/package.json"
+                OUTPUT_VARIABLE NPM_PACKAGE_VERSION
+                OUTPUT_STRIP_TRAILING_WHITESPACE)
+# Parse major, minor, and patch values from NPM_PACKAGE_VERSION
+STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)"
+       "\\1" CPACK_PACKAGE_VERSION_MAJOR ${NPM_PACKAGE_VERSION})
+STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)"
+       "\\2" CPACK_PACKAGE_VERSION_MINOR ${NPM_PACKAGE_VERSION})
+STRING(REGEX REPLACE "([0-9]+)\\.([0-9]+)\\.([0-9]+)"
+       "\\3" CPACK_PACKAGE_VERSION_PATCH ${NPM_PACKAGE_VERSION})
+
+set(CPACK_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}.${BUILD_NUMBER}")
+
+execute_process(COMMAND git describe --tags --dirty
+                WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
+                OUTPUT_FILE "${CMAKE_CURRENT_BINARY_DIR}/GIT_VERSION")
+execute_process(COMMAND echo "${CPACK_PACKAGE_VERSION}"
+                WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
+                OUTPUT_FILE "${CMAKE_CURRENT_BINARY_DIR}/VERSION")
+
+add_subdirectory(src)
+add_subdirectory(external/node)
+add_subdirectory(external/pandoc)
+
+install(DIRECTORY assets
+                  samples                  
+                  build
+                  config
+                  ext
+                  lib
+                  manual.test
+                  node_modules
+                  R
+                  scripts
+                  templates
+                  test
+                  tools
+        USE_SOURCE_PERMISSIONS DESTINATION shiny-server)
+
+
+# Configure and assemble /bin directory.
+configure_file(bin/deploy-example.in bin/deploy-example)
+install(PROGRAMS bin/node
+                 bin/npm
+                 bin/shiny-server
+                 "${CMAKE_CURRENT_BINARY_DIR}/bin/deploy-example"
+        DESTINATION shiny-server/bin)
+
+# Render and rename NOTICE.md
+execute_process(COMMAND cat 
+                INPUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/NOTICE.md
+                OUTPUT_FILE ${CMAKE_CURRENT_BINARY_DIR}/NOTICE)
+
+install(FILES binding.gyp
+              config.html
+              COPYING
+              NEWS
+              package.json
+              README.md
+              "${CMAKE_CURRENT_BINARY_DIR}/NOTICE"
+              "${CMAKE_CURRENT_BINARY_DIR}/VERSION"
+              "${CMAKE_CURRENT_BINARY_DIR}/GIT_VERSION"
+        DESTINATION shiny-server)
+
+
+set(CPACK_PACKAGE_NAME "shiny-server")
+set(CPACK_PACKAGE_DESCRIPTION "Shiny Server")
+set(CPACK_PACKAGE_VENDOR "RStudio, Inc.")
+set(CPACK_PACKAGE_CONTACT "RStudio <info at rstudio.com>")
+#set(CPACK_PACKAGE_INSTALL_DIRECTORY "Shiny Server")
+
+# == Linux packaging directives ==
+
+# configure cpack install location
+set(CPACK_SET_DESTDIR "ON")
+set(CPACK_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}")
+
+# detect architecture (packaging platform specific)
+find_program(DPKG_EXECUTABLE dpkg)
+find_program(RPM_EXECUTABLE rpm)
+if (NOT PACKAGE_ARCHITECTURE)
+   if(DPKG_EXECUTABLE)
+      exec_program(dpkg ARGS --print-architecture
+                   OUTPUT_VARIABLE PACKAGE_ARCHITECTURE)
+      set(PACKAGE_DIST "")
+   elseif(RPM_EXECUTABLE)
+      #RHEL/CentOS
+      exec_program(arch OUTPUT_VARIABLE PACKAGE_ARCHITECTURE) 
+      execute_process(COMMAND sed -nr "s/.*\\ ([0-9]+)\\.[0-9]+(\\.[0-9]+)?\\ .*/\\1/p"
+                INPUT_FILE "/etc/redhat-release"
+                OUTPUT_VARIABLE RH_VER
+                OUTPUT_STRIP_TRAILING_WHITESPACE)
+      set(PACKAGE_DIST "-rh${RH_VER}")
+   endif()
+endif()
+
+# debian control files
+set(DEBIAN_POSTINST postinst.in)
+set(DEBIAN_PRERM prerm.in)
+set(DEBIAN_POSTRM postrm.in)
+
+# rpm scripts
+set(RPM_POSTINST postinst.sh.in)
+set(RPM_POSTRM postrm.sh.in)
+
+# debian dependencies -- to install the .deb from the command line with
+# automatic dependency resolution use e.g.
+#   sudo apt-get install gdebi-core
+#   sudo gdebi shiny-server-<version>-amd64.deb
+
+# define package suffix
+set(SHINY_SERVER_PACKAGE_SUFFIX "-")
+
+# include overlay if it exists
+if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CMakeOverlay.txt")
+   include(CMakeOverlay.txt)
+endif()
+
+# dynamically configured debian control scripts
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/packaging/debian-control/${DEBIAN_POSTINST}
+               ${CMAKE_CURRENT_BINARY_DIR}/packaging/debian-control/postinst)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/packaging/debian-control/${DEBIAN_PRERM}
+               ${CMAKE_CURRENT_BINARY_DIR}/packaging/debian-control/prerm)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/packaging/debian-control/${DEBIAN_POSTRM}
+               ${CMAKE_CURRENT_BINARY_DIR}/packaging/debian-control/postrm)
+
+set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_BINARY_DIR}/packaging/debian-control/postinst;${CMAKE_CURRENT_BINARY_DIR}/packaging/debian-control/prerm;${CMAKE_CURRENT_BINARY_DIR}/packaging/debian-control/postrm")
+
+# dynamically configured rpm scripts (only works with cmake 2.8.1 or higher). 
+# alternatively you can get CPackRPM.cmake from the cmake tip and copy it into
+# your local cmake modules directory -- this is what we currently do
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/packaging/rpm-script/${RPM_POSTINST}
+               ${CMAKE_CURRENT_BINARY_DIR}/packaging/rpm-script/postinst.sh)
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/packaging/rpm-script/${RPM_POSTRM}
+               ${CMAKE_CURRENT_BINARY_DIR}/packaging/rpm-script/postrm.sh)
+
+# Magic to prevent static pandoc binaries from breaking
+set(CPACK_RPM_SPEC_INSTALL_POST "/bin/true")
+
+set(CPACK_RPM_PACKAGE_AUTOREQPROV " no")
+
+# Work-around bug in CMake 2.8.10.2		
+set(CPACK_RPM_SPEC_MORE_DEFINE "%define ignore \#")		
+set(CPACK_RPM_USER_FILELIST    "%ignore /opt")
+
+set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${CMAKE_CURRENT_BINARY_DIR}/packaging/rpm-script/postinst.sh")
+set(CPACK_RPM_POST_UNINSTALL_SCRIPT_FILE "${CMAKE_CURRENT_BINARY_DIR}/packaging/rpm-script/postrm.sh")
+
+# package file name
+set(CPACK_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}${SHINY_SERVER_PACKAGE_SUFFIX}${CPACK_PACKAGE_VERSION}${PACKAGE_DIST}-${PACKAGE_ARCHITECTURE}")
+string(TOLOWER "${CPACK_PACKAGE_FILE_NAME}" CPACK_PACKAGE_FILE_NAME)
+
+# variables to be re-used in package description fields
+set(PACKAGE_LONG_DESCRIPTION "Shiny Server is a server program from RStudio, Inc. that makes Shiny applications available over the web. Shiny is a web application framework for the R statistical computation language.")
+
+# debian-specific
+set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION}\n ${PACKAGE_LONG_DESCRIPTION}")
+set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "${PACKAGE_ARCHITECTURE}")
+set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.7)")
+set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "r-base (>= 2.15.1)")
+
+# rpm-specific
+set(CPACK_RPM_PACKAGE_SUMMARY "${CPACK_PACKAGE_NAME}")
+set(CPACK_RPM_PACKAGE_DESCRIPTION "${PACKAGE_LONG_DESCRIPTION}")
+set(CPACK_RPM_PACKAGE_LICENSE "AGPL v.3.0")
+set(CPACK_RPM_PACKAGE_GROUP "System Environment/Daemons")
+set(CPACK_RPM_PACKAGE_ARCHITECTURE "${PACKAGE_ARCHITECTURE}")
+set(CPACK_RPM_PACKAGE_REQUIRES "libffi")
+
+
+include(CPack)
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..463666d
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,667 @@
+
+shiny-server is licensed to you under the AGPLv3, the terms of which are
+included below.  You can obtain the source code at:
+
+  https://github.com/rstudio/shiny-server
+
+                    GNU AFFERO GENERAL PUBLIC LICENSE
+                       Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+  A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate.  Many developers of free software are heartened and
+encouraged by the resulting cooperation.  However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+  The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community.  It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server.  Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+  An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals.  This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU Affero General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Remote Network Interaction; Use with the GNU General Public License.
+
+  Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software.  This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time.  Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source.  For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code.  There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+<http://www.gnu.org/licenses/>.
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..456a53b
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,187 @@
+shiny-server 1.5.0
+--------------------------------------------------------------------------------
+
+* Upgrade to Node.js v6.6.0, and upgrade all npm dependencies. While no distinct
+  features or significant bug fixes result from this upgrade, catching up to the
+  current Node.js release is critical for the long-term health of our codebase.
+
+shiny-server 1.4.7
+--------------------------------------------------------------------------------
+
+* Upgrade to Node.js v0.10.47 (security patches).
+
+* Fix a minor bug where subapps being created after a disrupted-then-restored
+  SockJS connection, might result in a duplicate connection.
+
+* The bookmarkable state feature in Shiny v0.14 is now officially supported.
+  Use the `bookmark_state_dir` directive to store bookmarked sessions in a
+  specific location (default is /var/lib/shiny-server/bookmarks).
+
+shiny-server 1.4.6
+--------------------------------------------------------------------------------
+(Skipped 1.4.5 for parity with Shiny Server Pro; 1.4.5 was a security release
+that was Shiny Server Pro specific.)
+
+* Fix a bug where a 404 response on some URLs could cause the server to exit
+  with an unhandled exception.
+
+shiny-server 1.4.4
+--------------------------------------------------------------------------------
+
+* Upgrade to Node.js v0.10.46 (security patches).
+
+shiny-server 1.4.3
+--------------------------------------------------------------------------------
+* Add auto-reconnect capabilities. Can be disabled via `reconnect false;`
+  config option (replaces `disable_reconnect true;`).
+
+* Upgrade to Node.js v0.10.45 (primarily for updated OpenSSL).
+
+shiny-server 1.4.2
+--------------------------------------------------------------------------------
+* Improve disconnected UI by adding modal with description.
+
+* Added experimental support for reconnecting disconnected sessions. Set 
+  `disable_reconnect false;` to enable the feature.
+
+shiny-server 1.4.0
+--------------------------------------------------------------------------------
+* Added support for Red Hat Enterprise Linux 7 and Ubuntu 15.04.
+
+* Added `disabled_protocols` to allow administrators to disable arbitrary 
+  SockJS protocols.
+
+* Capture Upstart failures to start Shiny Server successfully.
+
+* Various bug fixes around RHEL/CentOS 7 installers.
+
+* Bug fix: Load fonts over HTTPS.
+
+* Bug fix: Fix installer locale issue for Ubuntu 14.04.
+
+* Bug fix: RH6 uses a statically linked Pandoc.
+
+* Support app_idle_timeout of 0.
+
+shiny-server 1.3.0
+--------------------------------------------------------------------------------
+* Added support for SUSE Linux Enterprise Server 11.
+
+shiny-server 1.2.1
+--------------------------------------------------------------------------------
+* Added support for single-file `app.R` deployment released in Shiny 0.10.2.
+
+shiny-server 1.2.0
+--------------------------------------------------------------------------------
+
+* Added experimental support for Interactive Documents (Shiny + Rmd) via the 
+  rmarkdown package.
+* Leverage site_dir when hosting in user_dirs mode; user_dirs will now respect 
+  the directory_index setting and host static assets other than Shiny 
+  applications.
+* Provide a more sane handling of LANG by ensuring it's passed through in all 
+  spawning modes and set an environment variable in the startup script on 
+  Ubuntu.
+* Bug fix: Restored functionality of sspasswd's `-v` switch.
+
+shiny-server 1.1.0
+--------------------------------------------------------------------------------
+
+* Added support for custom page templates -- exposing the ability to customize
+  the static pages generated by Shiny Server for directory listings or errors.
+
+* Created 'user_dirs' mode and the special ':HOME_USER:' run_as user to replace
+  'user_apps'.
+
+* Leverage bash when spawning Shiny processes on behalf of other users, as in 
+  'user_dirs' mode.
+
+* Bug fix: Make compatible with loading content from Shiny Server in an iframe 
+  with third-party cookies blocked.
+
+* Bug fix: Restored compatibility with IE8 Standards Mode 
+
+shiny-server 1.0.0
+--------------------------------------------------------------------------------
+
+* Additional reliability testing.
+
+shiny-server 0.5.0
+--------------------------------------------------------------------------------
+
+* Added various quick-start configurations as described at: 
+  http://rstudio.github.io/shiny-server/latest/#quick-start
+
+shiny-server 0.4.2
+--------------------------------------------------------------------------------
+
+* BREAKING CHANGE: Deprecatedd `application` setting in favor of nested 
+  ``location` blocks.
+
+* Allow client to configure which network techniques should be used to connect
+  to the server using the keyboard shortcut 'ctrl+shift+A'.
+
+* Properly set working directory of spawned Shiny Processes to the associated
+  application's directory to honor local .Renviron and family.
+
+* Provide a logrotate configuration for /var/log/shiny-server.log where 
+  logrotate is available.
+
+* Various memory leak and stability improvements.
+
+shiny-server 0.4.0
+--------------------------------------------------------------------------------
+
+* Use UNIX domain sockets for data transfer instead of TCP/IP for enhanced 
+  security.
+
+* Added scheduler and traffic direction which require shiny >= 0.6.0.99 to be
+  compatible.
+
+shiny-server 0.3.6
+--------------------------------------------------------------------------------
+
+* Support for node-webkit-agent (https://github.com/c4milo/node-webkit-agent).
+  Use by setting DEBUG_PORT environment variable to a port number, then follow
+  the instructions on the node-webkit-agent GitHub page under "Connecting to the
+  agent". (At the time of this writing, node-webkit-agent only supports Node
+  v0.8.x, not v0.10.x.)
+
+* Fix slow memory leak when checking for restart.txt that doesn't exist.
+
+shiny-server 0.3.5
+--------------------------------------------------------------------------------
+
+* Fix crash on Node 0.10.x when serving static files.
+
+* Fix slow memory leak and log file descriptor leak.
+
+shiny-server 0.3.4
+--------------------------------------------------------------------------------
+
+* You can now force an app to restart by calling "touch restart.txt" in the app
+  directory root. Existing sessions will not be terminated, but the next session
+  to be initiated will cause a new R process to be launched.
+
+* shiny-server now passes its version number to R processes it launches.
+
+shiny-server 0.3.3
+--------------------------------------------------------------------------------
+
+* Remove pausing which is causing corruption in proxied HTTP request bodies.
+
+shiny-server 0.3.2
+--------------------------------------------------------------------------------
+
+* Make shiny-server compatible with httpuv package which we are introducing to
+  Shiny.
+
+shiny-server 0.3.1
+--------------------------------------------------------------------------------
+
+* Fix crashing bug when "req" object has no address.
+
+shiny-server 0.3.0
+--------------------------------------------------------------------------------
+
+* Initial release.
diff --git a/NOTICE.md b/NOTICE.md
new file mode 100644
index 0000000..3ad36f6
--- /dev/null
+++ b/NOTICE.md
@@ -0,0 +1,2166 @@
+Shiny Server includes other open source software components. The following is a list of these components (full copies of the license agreements used by these components are included below):
+
+ - faye-websocket-node
+ - node-http-proxy
+ - sockjs-client
+ - sockjs-node
+ - handlebars
+ - underscore
+ - q
+ - bash
+ - log4js-node
+ - unixgroups
+ - moment
+ - stable
+ - optimist
+ - pause
+ - send
+ - connect
+ - webkit-devtools-agent
+ - client-sessions
+ - should
+ - sinon
+ - qs
+ - split
+ - node-graceful-fs
+ - mocha
+ - rewire
+ - browserify
+ - inherits
+ - pinkySwear
+ - Node.js
+
+## Licenses
+
+### faye-websocket-node
+
+```
+(The MIT License)
+
+Copyright (c) 2010-2013 James Coglan
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of 
+this software and associated documentation files (the 'Software'), to deal in 
+the Software without restriction, including without limitation the rights to 
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 
+the Software, and to permit persons to whom the Software is furnished to do so, 
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all 
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+```
+
+### node-http-proxy
+
+```
+  node-http-proxy
+
+  Copyright (c) Nodejitsu 2013
+
+  Permission is hereby granted, free of charge, to any person obtaining
+  a copy of this software and associated documentation files (the
+  "Software"), to deal in the Software without restriction, including
+  without limitation the rights to use, copy, modify, merge, publish,
+  distribute, sublicense, and/or sell copies of the Software, and to
+  permit persons to whom the Software is furnished to do so, subject to
+  the following conditions:
+
+  The above copyright notice and this permission notice shall be
+  included in all copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+```
+
+### sockjs-client
+
+```
+The MIT License (MIT)
+
+Copyright (c) 2011-2012 VMware, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+```
+
+### sockjs-node
+
+```
+Copyright (C) 2011 VMware, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+```
+
+### handlebars
+
+```
+Copyright (C) 2011-2014 by Yehuda Katz
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+```
+
+### underscore
+
+```
+Copyright (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative
+Reporters & Editors
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+```
+
+### q
+
+```
+Copyright 2009–2014 Kristopher Michael Kowal. All rights reserved.
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
+```
+
+### bash
+
+```
+Copyright (c) 2011 Felix Geisendörfer (felix at debuggable.com)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+```
+
+### log4js-node
+
+> "The original log4js was distributed under the Apache 2.0 License, and so is 
+> this. I've tried to keep the original copyright and author credits in place, 
+> except in sections that I have rewritten extensively."
+
+Original License:
+
+```
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+```
+
+### unixgroups
+
+```
+Copyright 2012 Joe Rozner
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+```
+
+### moment
+
+```
+Copyright (c) 2011-2014 Tim Wood, Iskren Chernev, Moment.js contributors
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+```
+
+### stable
+
+```
+Copyright (C) 2014 Angry Bytes and contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+```
+
+### optimist
+
+```
+Copyright 2010 James Halliday (mail at substack.net)
+
+This project is free software released under the MIT/X11 license:
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+```
+
+### pause
+
+```
+(The MIT License)
+
+Copyright (c) 2012 TJ Holowaychuk <tj at vision-media.ca>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of 
+this software and associated documentation files (the 'Software'), to deal in 
+the Software without restriction, including without limitation the rights to 
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 
+the Software, and to permit persons to whom the Software is furnished to do so, 
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all 
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+```
+
+### send
+
+```
+Copyright (c) 2012 TJ Holowaychuk <tj at vision-media.ca>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+```
+
+### connect
+
+```
+(The MIT License)
+
+Copyright (c) 2010 Sencha Inc.
+Copyright (c) 2011 LearnBoost
+Copyright (c) 2011-2014 TJ Holowaychuk
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+```
+
+### webkit-devtools-agent
+
+```
+(The MIT License)
+
+Copyright 2014 Camilo Aguilar. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in 
+the Software without restriction, including without limitation the rights to 
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 
+the Software, and to permit persons to whom the Software is furnished to do so, 
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all 
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+```
+
+### client-sessions
+
+```
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+    means each individual or legal entity that creates, contributes to
+    the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+    means the combination of the Contributions of others (if any) used
+    by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+    means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+    means Source Code Form to which the initial Contributor has attached
+    the notice in Exhibit A, the Executable Form of such Source Code
+    Form, and Modifications of such Source Code Form, in each case
+    including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+    means
+
+    (a) that the initial Contributor has attached the notice described
+        in Exhibit B to the Covered Software; or
+
+    (b) that the Covered Software was made available under the terms of
+        version 1.1 or earlier of the License, but not also under the
+        terms of a Secondary License.
+
+1.6. "Executable Form"
+    means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+    means a work that combines Covered Software with other material, in 
+    a separate file or files, that is not Covered Software.
+
+1.8. "License"
+    means this document.
+
+1.9. "Licensable"
+    means having the right to grant, to the maximum extent possible,
+    whether at the time of the initial grant or subsequently, any and
+    all of the rights conveyed by this License.
+
+1.10. "Modifications"
+    means any of the following:
+
+    (a) any file in Source Code Form that results from an addition to,
+        deletion from, or modification of the contents of Covered
+        Software; or
+
+    (b) any new file in Source Code Form that contains any Covered
+        Software.
+
+1.11. "Patent Claims" of a Contributor
+    means any patent claim(s), including without limitation, method,
+    process, and apparatus claims, in any patent Licensable by such
+    Contributor that would be infringed, but for the grant of the
+    License, by the making, using, selling, offering for sale, having
+    made, import, or transfer of either its Contributions or its
+    Contributor Version.
+
+1.12. "Secondary License"
+    means either the GNU General Public License, Version 2.0, the GNU
+    Lesser General Public License, Version 2.1, the GNU Affero General
+    Public License, Version 3.0, or any later versions of those
+    licenses.
+
+1.13. "Source Code Form"
+    means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+    means an individual or a legal entity exercising rights under this
+    License. For legal entities, "You" includes any entity that
+    controls, is controlled by, or is under common control with You. For
+    purposes of this definition, "control" means (a) the power, direct
+    or indirect, to cause the direction or management of such entity,
+    whether by contract or otherwise, or (b) ownership of more than
+    fifty percent (50%) of the outstanding shares or beneficial
+    ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+    Licensable by such Contributor to use, reproduce, make available,
+    modify, display, perform, distribute, and otherwise exploit its
+    Contributions, either on an unmodified basis, with Modifications, or
+    as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+    for sale, have made, import, and otherwise transfer either its
+    Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+    or
+
+(b) for infringements caused by: (i) Your and any other third party's
+    modifications of Covered Software, or (ii) the combination of its
+    Contributions with other software (except as part of its Contributor
+    Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+    its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+    Form, as described in Section 3.1, and You must inform recipients of
+    the Executable Form how they can obtain a copy of such Source Code
+    Form by reasonable means in a timely manner, at a charge no more
+    than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+    License, or sublicense it under different terms, provided that the
+    license for the Executable Form does not attempt to limit or alter
+    the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+*                                                                      *
+*  6. Disclaimer of Warranty                                           *
+*  -------------------------                                           *
+*                                                                      *
+*  Covered Software is provided under this License on an "as is"       *
+*  basis, without warranty of any kind, either expressed, implied, or  *
+*  statutory, including, without limitation, warranties that the       *
+*  Covered Software is free of defects, merchantable, fit for a        *
+*  particular purpose or non-infringing. The entire risk as to the     *
+*  quality and performance of the Covered Software is with You.        *
+*  Should any Covered Software prove defective in any respect, You     *
+*  (not any Contributor) assume the cost of any necessary servicing,   *
+*  repair, or correction. This disclaimer of warranty constitutes an   *
+*  essential part of this License. No use of any Covered Software is   *
+*  authorized under this License except under this disclaimer.         *
+*                                                                      *
+************************************************************************
+
+************************************************************************
+*                                                                      *
+*  7. Limitation of Liability                                          *
+*  --------------------------                                          *
+*                                                                      *
+*  Under no circumstances and under no legal theory, whether tort      *
+*  (including negligence), contract, or otherwise, shall any           *
+*  Contributor, or anyone who distributes Covered Software as          *
+*  permitted above, be liable to You for any direct, indirect,         *
+*  special, incidental, or consequential damages of any character      *
+*  including, without limitation, damages for lost profits, loss of    *
+*  goodwill, work stoppage, computer failure or malfunction, or any    *
+*  and all other commercial damages or losses, even if such party      *
+*  shall have been informed of the possibility of such damages. This   *
+*  limitation of liability shall not apply to liability for death or   *
+*  personal injury resulting from such party's negligence to the       *
+*  extent applicable law prohibits such limitation. Some               *
+*  jurisdictions do not allow the exclusion or limitation of           *
+*  incidental or consequential damages, so this exclusion and          *
+*  limitation may not apply to You.                                    *
+*                                                                      *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+  This Source Code Form is subject to the terms of the Mozilla Public
+  License, v. 2.0. If a copy of the MPL was not distributed with this
+  file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+  This Source Code Form is "Incompatible With Secondary Licenses", as
+  defined by the Mozilla Public License, v. 2.0.
+```
+
+### should
+
+```
+Copyright (c) 2010-2014 TJ Holowaychuk <tj at vision-media.ca>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+```
+
+### sinon
+
+```
+(The BSD License)
+
+Copyright (c) 2010-2014, Christian Johansen, christian at cjohansen.no
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+    * Neither the name of Christian Johansen nor the names of his contributors
+      may be used to endorse or promote products derived from this software
+      without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+```
+
+### qs
+
+```
+Copyright (c) 2014 Nathan LaFreniere and other contributors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+    * The names of any contributors may not be used to endorse or promote
+      products derived from this software without specific prior written
+      permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+                                  *   *   *
+
+The complete list of contributors can be found at: https://github.com/hapijs/qs/graphs/contributors
+```
+
+### split
+
+```
+Copyright (c) 2011 Dominic Tarr
+
+Permission is hereby granted, free of charge, 
+to any person obtaining a copy of this software and 
+associated documentation files (the "Software"), to 
+deal in the Software without restriction, including 
+without limitation the rights to use, copy, modify, 
+merge, publish, distribute, sublicense, and/or sell 
+copies of the Software, and to permit persons to whom 
+the Software is furnished to do so, 
+subject to the following conditions:
+
+The above copyright notice and this permission notice 
+shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 
+ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+```
+
+### node-graceful-fs
+
+```
+Copyright (c) Isaac Z. Schlueter ("Author")
+All rights reserved.
+
+The BSD License
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+```
+
+### mocha
+
+```
+(The MIT License)
+
+Copyright (c) 2011-2014 TJ Holowaychuk <tj at vision-media.ca>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+```
+
+### rewire
+
+```
+Copyright (c) 2012 Johannes Ewald
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+```
+
+### browserify
+
+```
+Copyright browserify authors
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to permit
+persons to whom the Software is furnished to do so, subject to the
+following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+----
+
+Some pieces from builtins/ taken from node core under this license:
+
+----
+
+Copyright Joyent, Inc. and other Node contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to permit
+persons to whom the Software is furnished to do so, subject to the
+following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+----
+
+buffer_ieee754.js has this license in it:
+
+----
+
+Copyright (c) 2008-2015, Fair Oaks Labs, Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+   this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+ * Neither the name of Fair Oaks Labs, Inc. nor the names of its contributors
+   may be used to endorse or promote products derived from this software
+   without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+
+Modifications to writeIEEE754 to support negative zeroes made by Brian White
+
+----
+```
+
+### inherits
+
+```
+The ISC License
+
+Copyright (c) Isaac Z. Schlueter
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+```
+
+### pinkySwear
+
+```
+Public Domain. Use, modify and distribute it any way you like. No attribution
+required. To the extent possible under law, Tim Jansen has waived all copyright
+and related or neighboring rights to PinkySwear. Please see
+http://creativecommons.org/publicdomain/zero/1.0/
+```
+
+### Node.js
+
+```
+Node's license follows:
+
+====
+
+Copyright Joyent, Inc. and other Node contributors. All rights reserved.
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
+
+====
+
+This license applies to all parts of Node that are not externally
+maintained libraries. The externally maintained libraries used by Node are:
+
+- V8, located at deps/v8. V8's license follows:
+  """
+    This license applies to all parts of V8 that are not externally
+    maintained libraries.  The externally maintained libraries used by V8
+    are:
+
+      - PCRE test suite, located in
+        test/mjsunit/third_party/regexp-pcre.js.  This is based on the
+        test suite from PCRE-7.3, which is copyrighted by the University
+        of Cambridge and Google, Inc.  The copyright notice and license
+        are embedded in regexp-pcre.js.
+
+      - Layout tests, located in test/mjsunit/third_party.  These are
+        based on layout tests from webkit.org which are copyrighted by
+        Apple Computer, Inc. and released under a 3-clause BSD license.
+
+      - Strongtalk assembler, the basis of the files assembler-arm-inl.h,
+        assembler-arm.cc, assembler-arm.h, assembler-ia32-inl.h,
+        assembler-ia32.cc, assembler-ia32.h, assembler-x64-inl.h,
+        assembler-x64.cc, assembler-x64.h, assembler-mips-inl.h,
+        assembler-mips.cc, assembler-mips.h, assembler.cc and assembler.h.
+        This code is copyrighted by Sun Microsystems Inc. and released
+        under a 3-clause BSD license.
+
+      - Valgrind client API header, located at third_party/valgrind/valgrind.h
+        This is release under the BSD license.
+
+    These libraries have their own licenses; we recommend you read them,
+    as their terms may differ from the terms below.
+
+    Copyright 2006-2012, the V8 project authors. All rights reserved.
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions are
+    met:
+
+        * Redistributions of source code must retain the above copyright
+          notice, this list of conditions and the following disclaimer.
+        * Redistributions in binary form must reproduce the above
+          copyright notice, this list of conditions and the following
+          disclaimer in the documentation and/or other materials provided
+          with the distribution.
+        * Neither the name of Google Inc. nor the names of its
+          contributors may be used to endorse or promote products derived
+          from this software without specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+    OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+  """
+
+- C-Ares, an asynchronous DNS client, located at deps/cares. C-Ares license
+  follows:
+  """
+    /* Copyright 1998 by the Massachusetts Institute of Technology.
+     *
+     * Permission to use, copy, modify, and distribute this
+     * software and its documentation for any purpose and without
+     * fee is hereby granted, provided that the above copyright
+     * notice appear in all copies and that both that copyright
+     * notice and this permission notice appear in supporting
+     * documentation, and that the name of M.I.T. not be used in
+     * advertising or publicity pertaining to distribution of the
+     * software without specific, written prior permission.
+     * M.I.T. makes no representations about the suitability of
+     * this software for any purpose.  It is provided "as is"
+     * without express or implied warranty.
+  """
+
+- OpenSSL located at deps/openssl. OpenSSL is cryptographic software written
+  by Eric Young (eay at cryptsoft.com) to provide SSL/TLS encryption. OpenSSL's
+  license follows:
+  """
+    /* ====================================================================
+     * Copyright (c) 1998-2011 The OpenSSL Project.  All rights reserved.
+     *
+     * Redistribution and use in source and binary forms, with or without
+     * modification, are permitted provided that the following conditions
+     * are met:
+     *
+     * 1. Redistributions of source code must retain the above copyright
+     *    notice, this list of conditions and the following disclaimer.
+     *
+     * 2. Redistributions in binary form must reproduce the above copyright
+     *    notice, this list of conditions and the following disclaimer in
+     *    the documentation and/or other materials provided with the
+     *    distribution.
+     *
+     * 3. All advertising materials mentioning features or use of this
+     *    software must display the following acknowledgment:
+     *    "This product includes software developed by the OpenSSL Project
+     *    for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
+     *
+     * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
+     *    endorse or promote products derived from this software without
+     *    prior written permission. For written permission, please contact
+     *    openssl-core at openssl.org.
+     *
+     * 5. Products derived from this software may not be called "OpenSSL"
+     *    nor may "OpenSSL" appear in their names without prior written
+     *    permission of the OpenSSL Project.
+     *
+     * 6. Redistributions of any form whatsoever must retain the following
+     *    acknowledgment:
+     *    "This product includes software developed by the OpenSSL Project
+     *    for use in the OpenSSL Toolkit (http://www.openssl.org/)"
+     *
+     * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
+     * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+     * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE OpenSSL PROJECT OR
+     * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+     * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+     * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+     * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+     * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+     * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+     * OF THE POSSIBILITY OF SUCH DAMAGE.
+     * ====================================================================
+     *
+     * This product includes cryptographic software written by Eric Young
+     * (eay at cryptsoft.com).  This product includes software written by Tim
+     * Hudson (tjh at cryptsoft.com).
+     *
+     */
+  """
+
+- HTTP Parser, located at deps/http_parser. HTTP Parser's license follows:
+  """
+    http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright
+    Igor Sysoev.
+
+    Additional changes are licensed under the same terms as NGINX and
+    copyright Joyent, Inc. and other Node contributors. All rights reserved.
+
+    Permission is hereby granted, free of charge, to any person obtaining a copy
+    of this software and associated documentation files (the "Software"), to
+    deal in the Software without restriction, including without limitation the
+    rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+    sell copies of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included in
+    all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+    IN THE SOFTWARE.
+  """
+
+- Closure Linter is located at tools/closure_linter. Closure's license
+  follows:
+  """
+    # Copyright (c) 2007, Google Inc.
+    # All rights reserved.
+    #
+    # Redistribution and use in source and binary forms, with or without
+    # modification, are permitted provided that the following conditions are
+    # met:
+    #
+    #     * Redistributions of source code must retain the above copyright
+    # notice, this list of conditions and the following disclaimer.
+    #     * Redistributions in binary form must reproduce the above
+    # copyright notice, this list of conditions and the following disclaimer
+    # in the documentation and/or other materials provided with the
+    # distribution.
+    #     * Neither the name of Google Inc. nor the names of its
+    # contributors may be used to endorse or promote products derived from
+    # this software without specific prior written permission.
+    #
+    # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+    # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+    # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+    # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+    # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+    # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+    # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+    # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+    # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+    # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+  """
+
+- tools/cpplint.py is a C++ linter. Its license follows:
+  """
+    # Copyright (c) 2009 Google Inc. All rights reserved.
+    #
+    # Redistribution and use in source and binary forms, with or without
+    # modification, are permitted provided that the following conditions are
+    # met:
+    #
+    #    * Redistributions of source code must retain the above copyright
+    # notice, this list of conditions and the following disclaimer.
+    #    * Redistributions in binary form must reproduce the above
+    # copyright notice, this list of conditions and the following disclaimer
+    # in the documentation and/or other materials provided with the
+    # distribution.
+    #    * Neither the name of Google Inc. nor the names of its
+    # contributors may be used to endorse or promote products derived from
+    # this software without specific prior written permission.
+    #
+    # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+    # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+    # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+    # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+    # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+    # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+    # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+    # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+    # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+    # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+  """
+
+- lib/punycode.js is copyright 2011 Mathias Bynens <http://mathiasbynens.be/>
+  and released under the MIT license.
+  """
+    * Punycode.js <http://mths.be/punycode>
+    * Copyright 2011 Mathias Bynens <http://mathiasbynens.be/>
+    * Available under MIT license <http://mths.be/mit>
+  """
+
+- tools/gyp. GYP is a meta-build system. GYP's license follows:
+  """
+    Copyright (c) 2009 Google Inc. All rights reserved.
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions are
+    met:
+
+       * Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+       * Redistributions in binary form must reproduce the above
+    copyright notice, this list of conditions and the following disclaimer
+    in the documentation and/or other materials provided with the
+    distribution.
+       * Neither the name of Google Inc. nor the names of its
+    contributors may be used to endorse or promote products derived from
+    this software without specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+    OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+  """
+
+- Zlib at deps/zlib. zlib's license follows:
+  """
+    /* zlib.h -- interface of the 'zlib' general purpose compression library
+      version 1.2.4, March 14th, 2010
+
+      Copyright (C) 1995-2010 Jean-loup Gailly and Mark Adler
+
+      This software is provided 'as-is', without any express or implied
+      warranty.  In no event will the authors be held liable for any damages
+      arising from the use of this software.
+
+      Permission is granted to anyone to use this software for any purpose,
+      including commercial applications, and to alter it and redistribute it
+      freely, subject to the following restrictions:
+
+      1. The origin of this software must not be misrepresented; you must not
+         claim that you wrote the original software. If you use this software
+         in a product, an acknowledgment in the product documentation would be
+         appreciated but is not required.
+      2. Altered source versions must be plainly marked as such, and must not be
+         misrepresented as being the original software.
+      3. This notice may not be removed or altered from any source distribution.
+
+      Jean-loup Gailly
+      Mark Adler
+
+    */
+  """
+
+- npm is a package manager program located at deps/npm.
+  npm's license follows:
+  """
+    Copyright (c) Isaac Z. Schlueter
+    All rights reserved.
+
+    npm is released under the Artistic 2.0 License.
+    The text of the License follows:
+
+
+    --------
+
+
+    The Artistic License 2.0
+
+    Copyright (c) 2000-2006, The Perl Foundation.
+
+    Everyone is permitted to copy and distribute verbatim copies
+    of this license document, but changing it is not allowed.
+
+    Preamble
+
+    This license establishes the terms under which a given free software
+    Package may be copied, modified, distributed, and/or redistributed.
+    The intent is that the Copyright Holder maintains some artistic
+    control over the development of that Package while still keeping the
+    Package available as open source and free software.
+
+    You are always permitted to make arrangements wholly outside of this
+    license directly with the Copyright Holder of a given Package.  If the
+    terms of this license do not permit the full use that you propose to
+    make of the Package, you should contact the Copyright Holder and seek
+    a different licensing arrangement.
+
+    Definitions
+
+        "Copyright Holder" means the individual(s) or organization(s)
+        named in the copyright notice for the entire Package.
+
+        "Contributor" means any party that has contributed code or other
+        material to the Package, in accordance with the Copyright Holder's
+        procedures.
+
+        "You" and "your" means any person who would like to copy,
+        distribute, or modify the Package.
+
+        "Package" means the collection of files distributed by the
+        Copyright Holder, and derivatives of that collection and/or of
+        those files. A given Package may consist of either the Standard
+        Version, or a Modified Version.
+
+        "Distribute" means providing a copy of the Package or making it
+        accessible to anyone else, or in the case of a company or
+        organization, to others outside of your company or organization.
+
+        "Distributor Fee" means any fee that you charge for Distributing
+        this Package or providing support for this Package to another
+        party.  It does not mean licensing fees.
+
+        "Standard Version" refers to the Package if it has not been
+        modified, or has been modified only in ways explicitly requested
+        by the Copyright Holder.
+
+        "Modified Version" means the Package, if it has been changed, and
+        such changes were not explicitly requested by the Copyright
+        Holder.
+
+        "Original License" means this Artistic License as Distributed with
+        the Standard Version of the Package, in its current version or as
+        it may be modified by The Perl Foundation in the future.
+
+        "Source" form means the source code, documentation source, and
+        configuration files for the Package.
+
+        "Compiled" form means the compiled bytecode, object code, binary,
+        or any other form resulting from mechanical transformation or
+        translation of the Source form.
+
+
+    Permission for Use and Modification Without Distribution
+
+    (1)  You are permitted to use the Standard Version and create and use
+    Modified Versions for any purpose without restriction, provided that
+    you do not Distribute the Modified Version.
+
+
+    Permissions for Redistribution of the Standard Version
+
+    (2)  You may Distribute verbatim copies of the Source form of the
+    Standard Version of this Package in any medium without restriction,
+    either gratis or for a Distributor Fee, provided that you duplicate
+    all of the original copyright notices and associated disclaimers.  At
+    your discretion, such verbatim copies may or may not include a
+    Compiled form of the Package.
+
+    (3)  You may apply any bug fixes, portability changes, and other
+    modifications made available from the Copyright Holder.  The resulting
+    Package will still be considered the Standard Version, and as such
+    will be subject to the Original License.
+
+
+    Distribution of Modified Versions of the Package as Source
+
+    (4)  You may Distribute your Modified Version as Source (either gratis
+    or for a Distributor Fee, and with or without a Compiled form of the
+    Modified Version) provided that you clearly document how it differs
+    from the Standard Version, including, but not limited to, documenting
+    any non-standard features, executables, or modules, and provided that
+    you do at least ONE of the following:
+
+        (a)  make the Modified Version available to the Copyright Holder
+        of the Standard Version, under the Original License, so that the
+        Copyright Holder may include your modifications in the Standard
+        Version.
+
+        (b)  ensure that installation of your Modified Version does not
+        prevent the user installing or running the Standard Version. In
+        addition, the Modified Version must bear a name that is different
+        from the name of the Standard Version.
+
+        (c)  allow anyone who receives a copy of the Modified Version to
+        make the Source form of the Modified Version available to others
+        under
+
+            (i)  the Original License or
+
+            (ii)  a license that permits the licensee to freely copy,
+            modify and redistribute the Modified Version using the same
+            licensing terms that apply to the copy that the licensee
+            received, and requires that the Source form of the Modified
+            Version, and of any works derived from it, be made freely
+            available in that license fees are prohibited but Distributor
+            Fees are allowed.
+
+
+    Distribution of Compiled Forms of the Standard Version
+    or Modified Versions without the Source
+
+    (5)  You may Distribute Compiled forms of the Standard Version without
+    the Source, provided that you include complete instructions on how to
+    get the Source of the Standard Version.  Such instructions must be
+    valid at the time of your distribution.  If these instructions, at any
+    time while you are carrying out such distribution, become invalid, you
+    must provide new instructions on demand or cease further distribution.
+    If you provide valid instructions or cease distribution within thirty
+    days after you become aware that the instructions are invalid, then
+    you do not forfeit any of your rights under this license.
+
+    (6)  You may Distribute a Modified Version in Compiled form without
+    the Source, provided that you comply with Section 4 with respect to
+    the Source of the Modified Version.
+
+
+    Aggregating or Linking the Package
+
+    (7)  You may aggregate the Package (either the Standard Version or
+    Modified Version) with other packages and Distribute the resulting
+    aggregation provided that you do not charge a licensing fee for the
+    Package.  Distributor Fees are permitted, and licensing fees for other
+    components in the aggregation are permitted. The terms of this license
+    apply to the use and Distribution of the Standard or Modified Versions
+    as included in the aggregation.
+
+    (8) You are permitted to link Modified and Standard Versions with
+    other works, to embed the Package in a larger work of your own, or to
+    build stand-alone binary or bytecode versions of applications that
+    include the Package, and Distribute the result without restriction,
+    provided the result does not expose a direct interface to the Package.
+
+
+    Items That are Not Considered Part of a Modified Version
+
+    (9) Works (including, but not limited to, modules and scripts) that
+    merely extend or make use of the Package, do not, by themselves, cause
+    the Package to be a Modified Version.  In addition, such works are not
+    considered parts of the Package itself, and are not subject to the
+    terms of this license.
+
+
+    General Provisions
+
+    (10)  Any use, modification, and distribution of the Standard or
+    Modified Versions is governed by this Artistic License. By using,
+    modifying or distributing the Package, you accept this license. Do not
+    use, modify, or distribute the Package, if you do not accept this
+    license.
+
+    (11)  If your Modified Version has been derived from a Modified
+    Version made by someone other than you, you are nevertheless required
+    to ensure that your Modified Version complies with the requirements of
+    this license.
+
+    (12)  This license does not grant you the right to use any trademark,
+    service mark, tradename, or logo of the Copyright Holder.
+
+    (13)  This license includes the non-exclusive, worldwide,
+    free-of-charge patent license to make, have made, use, offer to sell,
+    sell, import and otherwise transfer the Package with respect to any
+    patent claims licensable by the Copyright Holder that are necessarily
+    infringed by the Package. If you institute patent litigation
+    (including a cross-claim or counterclaim) against any party alleging
+    that the Package constitutes direct or contributory patent
+    infringement, then this Artistic License to you shall terminate on the
+    date that such litigation is filed.
+
+    (14)  Disclaimer of Warranty:
+    THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
+    IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED
+    WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
+    NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL
+    LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL
+    BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+    DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF
+    ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+    --------
+
+
+    "Node.js" and "node" trademark Joyent, Inc. npm is not officially
+    part of the Node.js project, and is neither owned by nor
+    officially affiliated with Joyent, Inc.
+
+    Packages published in the npm registry (other than the Software and
+    its included dependencies) are not part of npm itself, are the sole
+    property of their respective maintainers, and are not covered by
+    this license.
+
+    "npm Logo" created by Mathias Pettersson and Brian Hammond,
+    used with permission.
+
+    "Gubblebum Blocky" font
+    Copyright (c) by Tjarda Koster, http://jelloween.deviantart.com
+    included for use in the npm website and documentation,
+    used with permission.
+
+    This program uses several Node modules contained in the node_modules/
+    subdirectory, according to the terms of their respective licenses.
+  """
+
+- tools/doc/node_modules/marked. Marked is a Markdown parser. Marked's
+  license follows:
+  """
+    Copyright (c) 2011-2012, Christopher Jeffrey (https://github.com/chjj/)
+
+    Permission is hereby granted, free of charge, to any person obtaining a copy
+    of this software and associated documentation files (the "Software"), to deal
+    in the Software without restriction, including without limitation the rights
+    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+    copies of the Software, and to permit persons to whom the Software is
+    furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included in
+    all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+    THE SOFTWARE.
+  """
+
+- test/gc/node_modules/weak. Node-weak is a node.js addon that provides garbage
+  collector notifications. Node-weak's license follows:
+  """
+    Copyright (c) 2011, Ben Noordhuis <info at bnoordhuis.nl>
+
+    Permission to use, copy, modify, and/or distribute this software for any
+    purpose with or without fee is hereby granted, provided that the above
+    copyright notice and this permission notice appear in all copies.
+
+    THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+    WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+    MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+    ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+    WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+    ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+    OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+  """
+
+- src/ngx-queue.h. ngx-queue.h is taken from the nginx source tree. nginx's
+  license follows:
+  """
+    Copyright (C) 2002-2012 Igor Sysoev
+    Copyright (C) 2011,2012 Nginx, Inc.
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+    1. Redistributions of source code must retain the above copyright
+       notice, this list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright
+       notice, this list of conditions and the following disclaimer in the
+       documentation and/or other materials provided with the distribution.
+
+    THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+    ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+    FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+    OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+    OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+  """
+
+- wrk is located at tools/wrk. wrk's license follows:
+  """
+
+                                     Apache License
+                               Version 2.0, January 2004
+                            http://www.apache.org/licenses/
+
+       TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+       1. Definitions.
+
+          "License" shall mean the terms and conditions for use, reproduction,
+          and distribution as defined by Sections 1 through 9 of this document.
+
+          "Licensor" shall mean the copyright owner or entity authorized by
+          the copyright owner that is granting the License.
+
+          "Legal Entity" shall mean the union of the acting entity and all
+          other entities that control, are controlled by, or are under common
+          control with that entity. For the purposes of this definition,
+          "control" means (i) the power, direct or indirect, to cause the
+          direction or management of such entity, whether by contract or
+          otherwise, or (ii) ownership of fifty percent (50%) or more of the
+          outstanding shares, or (iii) beneficial ownership of such entity.
+
+          "You" (or "Your") shall mean an individual or Legal Entity
+          exercising permissions granted by this License.
+
+          "Source" form shall mean the preferred form for making modifications,
+          including but not limited to software source code, documentation
+          source, and configuration files.
+
+          "Object" form shall mean any form resulting from mechanical
+          transformation or translation of a Source form, including but
+          not limited to compiled object code, generated documentation,
+          and conversions to other media types.
+
+          "Work" shall mean the work of authorship, whether in Source or
+          Object form, made available under the License, as indicated by a
+          copyright notice that is included in or attached to the work
+          (an example is provided in the Appendix below).
+
+          "Derivative Works" shall mean any work, whether in Source or Object
+          form, that is based on (or derived from) the Work and for which the
+          editorial revisions, annotations, elaborations, or other modifications
+          represent, as a whole, an original work of authorship. For the purposes
+          of this License, Derivative Works shall not include works that remain
+          separable from, or merely link (or bind by name) to the interfaces of,
+          the Work and Derivative Works thereof.
+
+          "Contribution" shall mean any work of authorship, including
+          the original version of the Work and any modifications or additions
+          to that Work or Derivative Works thereof, that is intentionally
+          submitted to Licensor for inclusion in the Work by the copyright owner
+          or by an individual or Legal Entity authorized to submit on behalf of
+          the copyright owner. For the purposes of this definition, "submitted"
+          means any form of electronic, verbal, or written communication sent
+          to the Licensor or its representatives, including but not limited to
+          communication on electronic mailing lists, source code control systems,
+          and issue tracking systems that are managed by, or on behalf of, the
+          Licensor for the purpose of discussing and improving the Work, but
+          excluding communication that is conspicuously marked or otherwise
+          designated in writing by the copyright owner as "Not a Contribution."
+
+          "Contributor" shall mean Licensor and any individual or Legal Entity
+          on behalf of whom a Contribution has been received by Licensor and
+          subsequently incorporated within the Work.
+
+       2. Grant of Copyright License. Subject to the terms and conditions of
+          this License, each Contributor hereby grants to You a perpetual,
+          worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+          copyright license to reproduce, prepare Derivative Works of,
+          publicly display, publicly perform, sublicense, and distribute the
+          Work and such Derivative Works in Source or Object form.
+
+       3. Grant of Patent License. Subject to the terms and conditions of
+          this License, each Contributor hereby grants to You a perpetual,
+          worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+          (except as stated in this section) patent license to make, have made,
+          use, offer to sell, sell, import, and otherwise transfer the Work,
+          where such license applies only to those patent claims licensable
+          by such Contributor that are necessarily infringed by their
+          Contribution(s) alone or by combination of their Contribution(s)
+          with the Work to which such Contribution(s) was submitted. If You
+          institute patent litigation against any entity (including a
+          cross-claim or counterclaim in a lawsuit) alleging that the Work
+          or a Contribution incorporated within the Work constitutes direct
+          or contributory patent infringement, then any patent licenses
+          granted to You under this License for that Work shall terminate
+          as of the date such litigation is filed.
+
+       4. Redistribution. You may reproduce and distribute copies of the
+          Work or Derivative Works thereof in any medium, with or without
+          modifications, and in Source or Object form, provided that You
+          meet the following conditions:
+
+          (a) You must give any other recipients of the Work or
+              Derivative Works a copy of this License; and
+
+          (b) You must cause any modified files to carry prominent notices
+              stating that You changed the files; and
+
+          (c) You must retain, in the Source form of any Derivative Works
+              that You distribute, all copyright, patent, trademark, and
+              attribution notices from the Source form of the Work,
+              excluding those notices that do not pertain to any part of
+              the Derivative Works; and
+
+          (d) If the Work includes a "NOTICE" text file as part of its
+              distribution, then any Derivative Works that You distribute must
+              include a readable copy of the attribution notices contained
+              within such NOTICE file, excluding those notices that do not
+              pertain to any part of the Derivative Works, in at least one
+              of the following places: within a NOTICE text file distributed
+              as part of the Derivative Works; within the Source form or
+              documentation, if provided along with the Derivative Works; or,
+              within a display generated by the Derivative Works, if and
+              wherever such third-party notices normally appear. The contents
+              of the NOTICE file are for informational purposes only and
+              do not modify the License. You may add Your own attribution
+              notices within Derivative Works that You distribute, alongside
+              or as an addendum to the NOTICE text from the Work, provided
+              that such additional attribution notices cannot be construed
+              as modifying the License.
+
+          You may add Your own copyright statement to Your modifications and
+          may provide additional or different license terms and conditions
+          for use, reproduction, or distribution of Your modifications, or
+          for any such Derivative Works as a whole, provided Your use,
+          reproduction, and distribution of the Work otherwise complies with
+          the conditions stated in this License.
+
+       5. Submission of Contributions. Unless You explicitly state otherwise,
+          any Contribution intentionally submitted for inclusion in the Work
+          by You to the Licensor shall be under the terms and conditions of
+          this License, without any additional terms or conditions.
+          Notwithstanding the above, nothing herein shall supersede or modify
+          the terms of any separate license agreement you may have executed
+          with Licensor regarding such Contributions.
+
+       6. Trademarks. This License does not grant permission to use the trade
+          names, trademarks, service marks, or product names of the Licensor,
+          except as required for reasonable and customary use in describing the
+          origin of the Work and reproducing the content of the NOTICE file.
+
+       7. Disclaimer of Warranty. Unless required by applicable law or
+          agreed to in writing, Licensor provides the Work (and each
+          Contributor provides its Contributions) on an "AS IS" BASIS,
+          WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+          implied, including, without limitation, any warranties or conditions
+          of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+          PARTICULAR PURPOSE. You are solely responsible for determining the
+          appropriateness of using or redistributing the Work and assume any
+          risks associated with Your exercise of permissions under this License.
+
+       8. Limitation of Liability. In no event and under no legal theory,
+          whether in tort (including negligence), contract, or otherwise,
+          unless required by applicable law (such as deliberate and grossly
+          negligent acts) or agreed to in writing, shall any Contributor be
+          liable to You for damages, including any direct, indirect, special,
+          incidental, or consequential damages of any character arising as a
+          result of this License or out of the use or inability to use the
+          Work (including but not limited to damages for loss of goodwill,
+          work stoppage, computer failure or malfunction, or any and all
+          other commercial damages or losses), even if such Contributor
+          has been advised of the possibility of such damages.
+
+       9. Accepting Warranty or Additional Liability. While redistributing
+          the Work or Derivative Works thereof, You may choose to offer,
+          and charge a fee for, acceptance of support, warranty, indemnity,
+          or other liability obligations and/or rights consistent with this
+          License. However, in accepting such obligations, You may act only
+          on Your own behalf and on Your sole responsibility, not on behalf
+          of any other Contributor, and only if You agree to indemnify,
+          defend, and hold each Contributor harmless for any liability
+          incurred by, or claims asserted against, such Contributor by reason
+          of your accepting any such warranty or additional liability.
+
+       END OF TERMS AND CONDITIONS
+  """
+```
diff --git a/R/SockJSAdapter.R b/R/SockJSAdapter.R
new file mode 100644
index 0000000..4292baa
--- /dev/null
+++ b/R/SockJSAdapter.R
@@ -0,0 +1,246 @@
+#
+# SockJSAdapter.R
+#
+# Copyright (C) 2009-13 by RStudio, Inc.
+#
+# This program is licensed to you under the terms of version 3 of the
+# GNU Affero General Public License. This program is distributed WITHOUT
+# ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+# AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+#
+
+local({
+  # Read config directives from stdin and put them in the environment.
+  fd = file('stdin')
+  input <- readLines(fd)
+  Sys.setenv(
+  SHINY_APP=input[1],
+  SHINY_PORT=input[2],
+  SHINY_GAID=input[3],
+  SHINY_SHARED_SECRET=input[4],
+  SHINY_SERVER_VERSION=input[5],
+  WORKER_ID=input[6],
+  SHINY_MODE=input[7],
+  RSTUDIO_PANDOC=input[8],
+  LOG_FILE=input[9])
+
+  disableProtocols <- strsplit(input[10], ",")[[1]]
+  if (length(disableProtocols) == 0) {
+    disableProtocols <- ""
+  } else {
+    disableProtocols <- paste('"', disableProtocols, '"', sep = '', collapse = ',')
+  }
+  reconnect <- if (identical("true", tolower(input[11]))) "true" else "false"
+  options(shiny.sanitize.errors = identical("true", tolower(input[12])))
+
+  # Top-level bookmarking directory (for all users)
+  bookmarkStateDir <- input[13]
+  # Name of bookmark directory for this app. Uses the basename of the path and
+  # appends a hash of the full path. So if the path is "/path/to/myApp", the
+  # result is "myApp-6fbdbedc4c99d052b538b2bfc3c96550".
+  bookmarkAppDir <- paste0(
+    basename(input[1]), "-",
+    digest::digest(input[1], algo = "md5", serialize = FALSE)
+  )
+
+  if (!is.null(asNamespace("shiny")$shinyOptions)) {
+    if (nchar(bookmarkStateDir) > 0) {
+      shiny::shinyOptions(
+        save.interface = function(id, callback) {
+          username <- Sys.info()[["effective_user"]]
+          dirname <- file.path(bookmarkStateDir, username, bookmarkAppDir, id)
+          if (dir.exists(dirname)) {
+            stop("Directory ", dirname, " already exists")
+          } else {
+            dir.create(dirname, recursive = TRUE, mode = "0700")
+            callback(dirname)
+          }
+        },
+        load.interface = function(id, callback) {
+          username <- Sys.info()[["effective_user"]]
+          dirname <- file.path(bookmarkStateDir, username, bookmarkAppDir, id)
+          if (!dir.exists(dirname)) {
+            stop("Session ", id, " not found")
+          } else {
+            callback(dirname)
+          }
+        }
+      )
+    } else {
+      shiny::shinyOptions(
+        save.interface = function(id, callback) {
+          stop("This server is not configured for saving sessions to disk.")
+        },
+        load.interface = function(id, callback) {
+          stop("This server is not configured for saving sessions to disk.")
+        }
+      )
+    }
+  } 
+  close(fd)
+
+  if (!identical(Sys.getenv('LOG_FILE'), "")){
+    # Redirect stderr to the given path.
+    message("Redirecting to ", Sys.getenv("LOG_FILE"))
+    errFile <- file(Sys.getenv('LOG_FILE'), "a")
+    sink(errFile, type="message")
+  }
+
+  MIN_R_VERSION <- "2.15.1"
+  MIN_SHINY_VERSION <- "0.7.0"
+  MIN_RMARKDOWN_VERSION <- "0.1.90"
+  MIN_KNITR_VERSION <- "1.5.32"
+
+  # We can have a more stringent requirement for the Shiny version when using
+  # rmarkdown
+  MIN_SHINY_RMARKDOWN_VERSION <- "0.9.1.9005"
+
+  options(shiny.sharedSecret = Sys.getenv('SHINY_SHARED_SECRET'))
+
+  rVer <- as.character(getRversion());
+  shinyVer <- tryCatch({as.character(packageVersion("shiny"))},
+      error=function(e){"0.0.0"});
+  cat(paste("R version: ", rVer, "\n", sep=""))
+  cat(paste("Shiny version: ", shinyVer, "\n", sep=""))
+
+  markdownVer <- tryCatch({as.character(packageVersion("rmarkdown"))},
+      error=function(e){"0.0.0"});
+  cat(paste("rmarkdown version: ", markdownVer, "\n", sep=""))
+
+  knitrVer <- tryCatch({as.character(packageVersion("knitr"))},
+      error=function(e){"0.0.0"});
+  cat(paste("knitr version: ", knitrVer, "\n", sep=""))
+
+  if (compareVersion(MIN_R_VERSION,rVer)>0){
+    # R is out of date
+    stop(paste("R version '", rVer, "' found. Shiny Server requires at least '",
+        MIN_R_VERSION, "'."), sep="")
+  }
+  if (compareVersion(MIN_SHINY_VERSION,shinyVer)>0){
+    if (shinyVer == "0.0.0"){
+      # Shiny not found
+      stop(paste("The Shiny package was not found in the library. Ensure that ",
+        "Shiny is installed and is available in the Library of the ",
+        "user you're running this application as.", sep="\n"))
+    } else{
+      # Shiny is out of date
+      stop(paste("Shiny version '", shinyVer, "' found. Shiny Server requires at least '",
+          MIN_SHINY_VERSION, "'."), sep="")      
+    }  
+  }
+
+  mode <- Sys.getenv('SHINY_MODE')
+  # Trying to use rmd, verify package.
+  if (identical(mode, "rmd")){
+    if (compareVersion(MIN_RMARKDOWN_VERSION,markdownVer)>0){
+      if (markdownVer == "0.0.0"){
+        # rmarkdown not found
+        stop(paste("You are attempting to load an rmarkdown file, but the ",
+          "rmarkdown package was not found in the library. Ensure that ",
+          "rmarkdown is installed and is available in the Library of the ",
+          "user you're running this application as.", sep="\n"))
+      } else{
+        # rmarkdown is out of date
+        stop(paste("rmarkdown version '", markdownVer, "' found. Shiny Server requires at least '",
+            MIN_RMARKDOWN_VERSION, "'."), sep="")
+      } 
+    }
+    if (compareVersion(MIN_SHINY_RMARKDOWN_VERSION,shinyVer)>0){
+      # We know it's installed b/c we got to this code chunk, so it's outdated.
+      stop(paste("Shiny version '", shinyVer, "' found. Shiny Server requires at least '",
+          MIN_SHINY_RMARKDOWN_VERSION, "' to use with the rmarkdown package."), sep="")
+    }
+    if (compareVersion(MIN_KNITR_VERSION,knitrVer)>0){
+      # We know it's installed b/c we got to this code chunk, so it's outdated.
+      stop(paste("knitr version '", knitrVer, "' found. Shiny Server requires at least '",
+          MIN_KNITR_VERSION, "' to use with the rmarkdown package."), sep="")
+    }
+  }
+
+  library(shiny)
+
+  if (exists("setServerInfo", envir=asNamespace("shiny"))) {
+    shiny:::setServerInfo(shinyServer = TRUE, 
+      version = Sys.getenv("SHINY_SERVER_VERSION"), 
+      edition = "OS")
+  }
+
+   gaTrackingCode <- ''
+   if (nzchar(Sys.getenv('SHINY_GAID'))) {
+      gaTrackingCode <- HTML(sprintf("<script type=\"text/javascript\">
+
+  var _gaq = _gaq || [];
+  _gaq.push(['_setAccount', '%s']);
+  _gaq.push(['_trackPageview']);
+
+  (function() {
+    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+  })();
+
+</script>", Sys.getenv('SHINY_GAID')))
+   }
+
+   inject <- paste(
+      tags$script(src='__assets__/sockjs-0.3.4.min.js'),
+      tags$script(src='__assets__/shiny-server-client.min.js'),
+      tags$script(
+        sprintf("preShinyInit({reconnect:%s,disableProtocols:[%s]});",
+          reconnect, disableProtocols
+        )
+      ),
+      tags$link(rel='stylesheet', type='text/css', href='__assets__/shiny-server.css'),
+      gaTrackingCode,
+      HTML("</head>"),
+      sep="\n"
+   )
+
+   filter <- function(...) {
+      # The signature of filter functions changed between Shiny 0.4.0 and
+      # 0.4.1; previously the parameters were (ws, headers, response) and
+      # after they became (request, response). To work with both types, we
+      # just grab the last argument.
+      response <- list(...)[[length(list(...))]]
+
+      if (response$status < 200 || response$status > 300) return(response)
+
+      # Don't break responses that use httpuv's file-based bodies.
+      if ('file' %in% names(response$content))
+         return(response)
+                                                
+      if (!grepl("^text/html\\b", response$content_type, perl=T))
+         return(response)
+
+      # HTML files served from static handler are raw. Convert to char so we
+      # can inject our head content.
+      if (is.raw(response$content))
+         response$content <- rawToChar(response$content)
+
+      response$content <- sub("</head>", inject, response$content, 
+         ignore.case=T)
+      return(response)
+   }
+   options(shiny.http.response.filter=filter)
+})
+
+# Port can be either a TCP port number, in which case we need to cast to
+# integer; or else a Unix domain socket path, in which case we need to
+# leave it as a string
+port <- suppressWarnings(as.integer(Sys.getenv('SHINY_PORT')))
+if (is.na(port)) {
+  port <- Sys.getenv('SHINY_PORT')
+  attr(port, 'mask') <- strtoi('0077', 8)
+}
+cat(paste("\nStarting Shiny with process ID: '",Sys.getpid(),"'\n", sep=""))
+
+if (identical(Sys.getenv('SHINY_MODE'), "shiny")){
+  runApp(Sys.getenv('SHINY_APP'),port=port,launch.browser=FALSE)
+} else if (identical(Sys.getenv('SHINY_MODE'), "rmd")){
+  library(rmarkdown)
+  rmarkdown::run(file=NULL, dir=Sys.getenv('SHINY_APP'),
+    shiny_args=list(port=port,launch.browser=FALSE), auto_reload=FALSE)
+} else{
+  stop(paste("Unclear Shiny mode:", Sys.getenv('SHINY_MODE')))
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9988867
--- /dev/null
+++ b/README.md
@@ -0,0 +1,43 @@
+# Shiny Server
+
+Shiny Server is a server program that makes [Shiny](https://rstudio.com/shiny) applications available over the web.
+
+## Features
+
+* Host multiple Shiny applications, each with its own URL
+* Can be configured to allow any user on the system to create and deploy their own Shiny applications
+* Supports non-websocket-capable browsers, like IE9
+* Free and open source ([AGPLv3](http://www.gnu.org/licenses/agpl-3.0.html) license)
+* Pre-built installers for select Linux distributions.
+
+## Installing
+
+At this time, Shiny Server can be run on Linux servers with explicit support for Ubuntu 12.04 or greater (64 bit) and CentOS/RHEL 5 (64 bit) or greater. If you are using one of these distributions, please download the pre-packaged installers from RStudio:
+
+> [Download Shiny Server Installers](https://www.rstudio.com/products/shiny/shiny-server/). 
+
+These installers will provide a majority of the prerequisite software and will provision all the necessary directories for you.
+
+If you are not using one of the explicitly supported distributions, you can still use Shiny Server by building it from source, see the [instructions for building from source](https://github.com/rstudio/shiny-server/wiki/Building-Shiny-Server-from-Source).
+
+## Configuration
+
+Shiny Server will use the [default configuration](https://github.com/rstudio/shiny-server/blob/master/config/default.config) unless an alternate configuration is provided at `/etc/shiny-server/shiny-server.conf`. Using the default configuration, Shiny Server will look for Shiny apps in `/srv/shiny-server/` and host them on port 3838. If you plan to host your apps in this directory, you can either copy an app you've already developed to that location:
+
+```
+sudo cp -R ~/MY-APP /srv/shiny-server/
+```
+
+Or you can copy some or all of the examples provided with the Shiny package. (The location of the R library varies from system to system. You can use the command `R -e ".libPaths()" --quiet` to print the directory of the R library.) For instance, on Ubuntu, you could execute `cp -R /usr/local/lib/R/site-library/shiny/examples/* /srv/shiny-server/`.
+
+Now start a web browser and point it to `http://<hostname>:3838/APP_NAME/`
+
+**If the browser is not able to connect to the server, configure your server's firewall to allow inbound TCP connections on port 3838.**
+
+To customize any of the above, or to explore the other ways Shiny Server can host Shiny apps, see the [Shiny Server Configuration Reference](https://rstudio.github.io/shiny-server/latest/#configuration-settings) for details on the various ways Shiny Server can be configured.
+
+## Documentation & Contact & Support
+
+See [the Administrator's Guide to Shiny Server](https://rstudio.github.io/shiny-server/latest/) for more complete documentation regarding the setup and management of Shiny Server.
+
+Please direct questions to the [shiny-discuss](https://groups.google.com/group/shiny-discuss) mailing list. If you're interested in Professional Support, please look at our commercial [Shiny Server Pro](https://www.rstudio.com/products/shiny-server-pro/) product.
diff --git a/assets/shiny-server.css b/assets/shiny-server.css
new file mode 100644
index 0000000..0108232
--- /dev/null
+++ b/assets/shiny-server.css
@@ -0,0 +1,69 @@
+body.ss-reconnecting {
+  background: none;
+  opacity: 1;
+}
+
+body.disconnected {
+  background: none;
+  opacity: 1;
+}
+
+#ss-connect-dialog {
+  opacity: 1 !important;
+  padding: 1em;
+  position: fixed;
+  bottom: 50px;
+  left: -30px;
+  padding-left: 45px;
+  padding-right: 18px;
+  width: 300px;
+  height: auto;
+  z-index: 99999;
+  background-color: #404040;
+  color: white;
+  border-radius: 3px;
+  font-size: 0.9em;
+  box-shadow: rgba(0, 0, 0, 0.3) 3px 3px 10px;
+}
+#ss-connect-dialog label {
+  display: block;
+  font-weight: normal;
+  color: white;
+  margin: 0;
+  padding: 0;
+}
+
+#ss-connect-dialog a, #ss-connect-dialog a:visited {
+  display: block;
+  color: #9999FF;
+  font-weight: bold;
+  margin: 0;
+  padding: 0;
+}
+
+.ss-dialog-link {
+}
+
+.ss-dialog-text {
+}
+
+#ss-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  bottom: 0;
+  right: 0;
+  z-index: 99998;
+  overflow: hidden;
+  cursor: progress;
+}
+
+.ss-gray-out {
+  background-color: #999;
+  opacity: 0.7;
+  cursor: not-allowed !important;
+}
+
+#ss-clearfix {
+  clear: both;
+}
diff --git a/bin/deploy-example.in b/bin/deploy-example.in
new file mode 100644
index 0000000..f968a68
--- /dev/null
+++ b/bin/deploy-example.in
@@ -0,0 +1,91 @@
+#!/bin/bash
+
+if [ "$(id -u)" != "0" ]; then
+  echo "You must be root to run this script."
+  exit 1
+fi
+
+if [ $# -gt 0 ]; then
+  CONF=$1
+
+  if [ $CONF == "shiny-server-rules" ]; then
+    echo "shiny-server-rules is not a valid configuration."
+    exit 1
+  fi
+
+else
+  # No arguments passed. Use the default.
+  CONF="default"
+fi
+
+echo "Attempting to deploy configuration named: $CONF"
+FILE="${CMAKE_INSTALL_PREFIX}/shiny-server/config/$CONF.config"
+if [ ! -f $FILE ]; then
+  echo "ERROR: There is no configuration named: $CONF"
+  exit 1
+fi
+
+SSCONF="/etc/shiny-server/shiny-server.conf"
+
+# Restart
+restart_shiny () {
+  echo "Restarting Shiny Server..."
+  INIT_SYSTEM=`cat /proc/1/comm`
+  if test $INIT_SYSTEM = "systemd"
+  then
+    systemctl stop shiny-server.service 2>/dev/null
+    echo "Waiting..."
+    systemctl start shiny-server.service
+  elif test -d /etc/init/
+  then
+    initctl stop shiny-server 2>/dev/null
+    echo "Waiting..."
+    initctl start shiny-server
+  else
+    /sbin/service shiny-server stop 2>/dev/null
+    echo "Waiting..."
+    sleep 5
+    /sbin/service shiny-server start
+  fi
+  echo
+  echo "The $CONF config is all setup now. Enjoy!"
+}
+
+copy_config () {
+  /bin/cp -f $1 $SSCONF 
+  echo "Copied $1 to $SSCONF."
+
+  echo "Installing sample apps..."
+  
+  if [ ! -e /srv/shiny-server/index.html ]; then
+    ln -s ${CMAKE_INSTALL_PREFIX}/shiny-server/samples/welcome.html /srv/shiny-server/index.html
+    echo "Created /srv/shiny-server/index.html"
+  else
+    echo "/srv/shiny-server/index.html already exists. Not overwriting."
+  fi
+
+  if [ ! -e /srv/shiny-server/sample-apps ]; then
+    ln -s ${CMAKE_INSTALL_PREFIX}/shiny-server/samples/sample-apps /srv/shiny-server/sample-apps
+    echo "Created /srv/shiny-server/sample-apps/"
+  else
+    echo "/srv/shiny-server/sample-apps already exists. Not overwriting."
+  fi
+  
+  echo "Done installing samples."
+
+  restart_shiny
+}
+
+if [ -f $SSCONF ]; then
+  read -p "Configuration file already exists at /etc/shiny-server/shiny-server.conf. Are you sure you want to overwrite it? [yN]" -n 1 -r
+  echo 
+  if [[ $REPLY =~ ^[Yy]$ ]]; then
+    copy_config $FILE
+  else 
+    echo "Not overwriting existing file. Exiting."
+    exit 1
+  fi
+else
+  copy_config $FILE
+fi
+
diff --git a/bin/node b/bin/node
new file mode 100755
index 0000000..b2fbc87
--- /dev/null
+++ b/bin/node
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+dir=`dirname "$0"`
+exec "$dir/../ext/node/bin/node" "$@"
+
diff --git a/bin/npm b/bin/npm
new file mode 100755
index 0000000..24bf7d2
--- /dev/null
+++ b/bin/npm
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+dir=`dirname "$0"`
+exec "$dir/../ext/node/bin/node" "$dir/../ext/node/lib/node_modules/npm/bin/npm-cli.js" "$@"
+
diff --git a/binding.gyp b/binding.gyp
new file mode 100644
index 0000000..fee89f5
--- /dev/null
+++ b/binding.gyp
@@ -0,0 +1,8 @@
+{
+  "targets": [
+    {
+      "target_name": "posix",
+      "sources": [ "src/posix.cc" ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/config.html b/config.html
new file mode 100644
index 0000000..931bc2f
--- /dev/null
+++ b/config.html
@@ -0,0 +1,894 @@
+    <p>Listed below are all the directives that are supported in Shiny Server config files.</p>
+    <p><strong>Applies to</strong> indicates the kind of parent scope that this directive normally appears inside.</p>
+    <p><strong>Inheritable</strong> means that you can put this directive at a higher level in the hierarchy and it will be inherited by any children to which it might apply. Inherited directives can be overridden by using the directive again in a child scope.</p>
+    <ul class="directives">
+      
+        <li>
+          <h3 class="code"><a name="run_as"></a>run_as</h3>
+          <p class="desc">The user the app should be run as. This user should have the minimal amount of privileges necessary to successfully run the application (i.e. read-only access to the Shiny application directory). Note that this directive cannot be used with <a class="code" href="#user_apps">user_apps</a>, as <code>user_apps</code> always run as the user who owns the application.</p>
+          
+            <table class="params">
+              <colgroup>
+                <col style="width: 105px"></col>
+                <col style="width: 90px"></col>
+                <col style="width: 90px"></col>
+                <col></col>
+                <col style="width: 90px"></col>
+              </colgroup>
+              <tr>
+                <th>Parameter</th>
+                <th>Data type</th>
+                <th>Type</th>
+                <th>Description</th>
+                <th>Default</th>
+              </tr>
+              
+                <tr>
+                  <td>users</td>
+                  <td>String</td>
+                  <td>multiple</td>
+                  <td>The username that should be used to run the app. If using home_dirs, this can also be the special keyword <code>:HOME_USER:</code> which will instruct <code>home_dirs</code> to run as the user in whose home directory the application exists. If using a special keyword like <code>:HOME_USER:</code>, you can specify additional usernames afterwards which will be used when this directive is applied to hosting models other than <code>home_dirs</code>.</td>
+                  <td></td>
+                </tr>
+              
+            </table>
+          
+
+          <p>
+            <label>Applies to:</label> Top-level, <code><a href="#server">server</a></code>, <code><a href="#location">location</a></code><br/>
+            <label>Inheritable:</label> Yes<br/>
+            
+          </p>
+        </li>
+      
+        <li>
+          <h3 class="code"><a name="access_log"></a>access_log</h3>
+          <p class="desc">The file path of the HTTP access log.</p>
+          
+            <table class="params">
+              <colgroup>
+                <col style="width: 105px"></col>
+                <col style="width: 90px"></col>
+                <col style="width: 90px"></col>
+                <col></col>
+                <col style="width: 90px"></col>
+              </colgroup>
+              <tr>
+                <th>Parameter</th>
+                <th>Data type</th>
+                <th>Type</th>
+                <th>Description</th>
+                <th>Default</th>
+              </tr>
+              
+                <tr>
+                  <td>path</td>
+                  <td>String</td>
+                  <td>required</td>
+                  <td>The file path where the access log should be written</td>
+                  <td></td>
+                </tr>
+              
+                <tr>
+                  <td>format</td>
+                  <td>String</td>
+                  <td>optional</td>
+                  <td>The log file format; see <a href="http://www.senchalabs.org/connect/logger.html">Connect documentation</a> under "Formats"</td>
+                  <td>default</td>
+                </tr>
+              
+            </table>
+          
+
+          <p>
+            <label>Applies to:</label> Top-level<br/>
+            <label>Inheritable:</label> Yes<br/>
+            
+          </p>
+        </li>
+      
+        <li>
+          <h3 class="code"><a name="server"></a>server</h3>
+          <p class="desc">Declares an HTTP server. You need one of these for each port/IP address combination this Shiny Server instance should listen on.</p>
+          
+            <p class="noparams">This directive has no parameters.</p>
+          
+
+          <p>
+            <label>Applies to:</label> Top-level<br/>
+            <label>Inheritable:</label> Yes<br/>
+            
+              <label>Child directives:</label> <code><a href="#listen">listen</a></code>, <code><a href="#server_name">server_name</a></code><br/>
+            
+          </p>
+        </li>
+      
+        <li>
+          <h3 class="code"><a name="listen"></a>listen</h3>
+          <p class="desc">Directs the enclosing server scope to listen on the specified port/IP address combination.</p>
+          
+            <table class="params">
+              <colgroup>
+                <col style="width: 105px"></col>
+                <col style="width: 90px"></col>
+                <col style="width: 90px"></col>
+                <col></col>
+                <col style="width: 90px"></col>
+              </colgroup>
+              <tr>
+                <th>Parameter</th>
+                <th>Data type</th>
+                <th>Type</th>
+                <th>Description</th>
+                <th>Default</th>
+              </tr>
+              
+                <tr>
+                  <td>port</td>
+                  <td>Integer</td>
+                  <td>required</td>
+                  <td>Port to listen on</td>
+                  <td></td>
+                </tr>
+              
+                <tr>
+                  <td>host</td>
+                  <td>String</td>
+                  <td>optional</td>
+                  <td>IPv4 address to listen on (<code>*</code> or <code>0.0.0.0</code> for all); hostnames are not currently allowed, please use raw IPv4 address only</td>
+                  <td>*</td>
+                </tr>
+              
+            </table>
+          
+
+          <p>
+            <label>Applies to:</label> <code><a href="#server">server</a></code><br/>
+            <label>Inheritable:</label> Yes<br/>
+            
+          </p>
+        </li>
+      
+        <li>
+          <h3 class="code"><a name="server_name"></a>server_name</h3>
+          <p class="desc">Directs the enclosing server scope to only honor requests that have the given host headers (i.e. virtual hosts).</p>
+          
+            <table class="params">
+              <colgroup>
+                <col style="width: 105px"></col>
+                <col style="width: 90px"></col>
+                <col style="width: 90px"></col>
+                <col></col>
+                <col style="width: 90px"></col>
+              </colgroup>
+              <tr>
+                <th>Parameter</th>
+                <th>Data type</th>
+                <th>Type</th>
+                <th>Description</th>
+                <th>Default</th>
+              </tr>
+              
+                <tr>
+                  <td>names</td>
+                  <td>String</td>
+                  <td>multiple</td>
+                  <td>The virtual hostname(s) to bind this server to</td>
+                  <td></td>
+                </tr>
+              
+            </table>
+          
+
+          <p>
+            <label>Applies to:</label> <code><a href="#server">server</a></code><br/>
+            <label>Inheritable:</label> Yes<br/>
+            
+          </p>
+        </li>
+      
+        <li>
+          <h3 class="code"><a name="location"></a>location</h3>
+          <p class="desc">Creates a scope that configures the given URL as a website (<a class="code" href="#site_dir">site_dir</a>), specific application (<a class="code" href="#app_dir">app_dir</a>), autouser root (<a class="code" href="#user_apps">user_apps</a>), autouser root with <a class="code" href="#run_as">run_as</a> support (<a class="code" href="#user_dirs">user_dirs</a>), or redirect to a different URL (<a class="code" href="#redirect">redirect</a>).</p>
+          
+            <table class="params">
+              <colgroup>
+                <col style="width: 105px"></col>
+                <col style="width: 90px"></col>
+                <col style="width: 90px"></col>
+                <col></col>
+                <col style="width: 90px"></col>
+              </colgroup>
+              <tr>
+                <th>Parameter</th>
+                <th>Data type</th>
+                <th>Type</th>
+                <th>Description</th>
+                <th>Default</th>
+              </tr>
+              
+                <tr>
+                  <td>path</td>
+                  <td>String</td>
+                  <td>required</td>
+                  <td>The request path that this location should match</td>
+                  <td></td>
+                </tr>
+              
+            </table>
+          
+
+          <p>
+            <label>Applies to:</label> <code><a href="#server">server</a></code>, <code><a href="#location">location</a></code><br/>
+            <label>Inheritable:</label> Yes<br/>
+            
+              <label>Child directives:</label> <code><a href="#run_as">run_as</a></code>, <code><a href="#location">location</a></code>, <code><a href="#site_dir">site_dir</a></code>, <code><a href="#directory_index">directory_index</a></code>, <code><a href="#user_apps">user_apps</a></code>, <code><a href="#user_dirs">user_dirs</a></code>, <code><a href="#app_dir">app_dir</a></code>, <code><a href="#redirect">redirect</a></code>, <code><a href="#log_dir">log_dir</a></code>, <code><a hre [...]
+            
+          </p>
+        </li>
+      
+        <li>
+          <h3 class="code"><a name="site_dir"></a>site_dir</h3>
+          <p class="desc">Configures the enclosing location scope to be a website that can contain both Shiny applications and unrelated static assets in a single directory tree.</p>
+          
+            <table class="params">
+              <colgroup>
+                <col style="width: 105px"></col>
+                <col style="width: 90px"></col>
+                <col style="width: 90px"></col>
+                <col></col>
+                <col style="width: 90px"></col>
+              </colgroup>
+              <tr>
+                <th>Parameter</th>
+                <th>Data type</th>
+                <th>Type</th>
+                <th>Description</th>
+                <th>Default</th>
+              </tr>
+              
+                <tr>
+                  <td>rootPath</td>
+                  <td>String</td>
+                  <td>required</td>
+                  <td>The path to the root directory of the website</td>
+                  <td></td>
+                </tr>
+              
+            </table>
+          
+
+          <p>
+            <label>Applies to:</label> <code><a href="#location">location</a></code><br/>
+            <label>Inheritable:</label> Yes<br/>
+            
+          </p>
+        </li>
+      
+        <li>
+          <h3 class="code"><a name="directory_index"></a>directory_index</h3>
+          <p class="desc">When enabled, if a directory is requested by the client and an <code>index.html</code> file is not present, a list of the directory contents is created automatically and returned to the client. If this directive is not present in a custom config file, the default behavior is to disable directory indexes. However, it is enabled if no config file is present at all (in other words, the default config file has it enabled).</p>
+          
+            <table class="params">
+              <colgroup>
+                <col style="width: 105px"></col>
+                <col style="width: 90px"></col>
+                <col style="width: 90px"></col>
+                <col></col>
+                <col style="width: 90px"></col>
+              </colgroup>
+              <tr>
+                <th>Parameter</th>
+                <th>Data type</th>
+                <th>Type</th>
+                <th>Description</th>
+                <th>Default</th>
+              </tr>
+              
+                <tr>
+                  <td>enabled</td>
+                  <td>Boolean</td>
+                  <td>required</td>
+                  <td>Whether directory contents should automatically displayed</td>
+                  <td></td>
+                </tr>
+              
+            </table>
+          
+
+          <p>
+            <label>Applies to:</label> Top-level, <code><a href="#server">server</a></code>, <code><a href="#location">location</a></code><br/>
+            <label>Inheritable:</label> Yes<br/>
+            
+          </p>
+        </li>
+      
+        <li>
+          <h3 class="code"><a name="user_apps"></a>user_apps</h3>
+          <p class="desc">DEPRECATED! This directive has been deprecated in favor of <a class="code" href="#user_dirs">user_dirs</a>, which offers more flexibility with regards to the <a class="code" href="#run_as">run_as</a> configuration. Configures the enclosing location scope to be an autouser root, meaning that applications will be served up from users' ~/ShinyApps directories and all Shiny processes will run as the user in whose directory the application is found.</p>
+          
+            <table class="params">
+              <colgroup>
+                <col style="width: 105px"></col>
+                <col style="width: 90px"></col>
+                <col style="width: 90px"></col>
+                <col></col>
+                <col style="width: 90px"></col>
+              </colgroup>
+              <tr>
+                <th>Parameter</th>
+                <th>Data type</th>
+                <th>Type</th>
+                <th>Description</th>
+                <th>Default</th>
+              </tr>
+              
+                <tr>
+                  <td>enabled</td>
+                  <td>Boolean</td>
+                  <td>optional</td>
+                  <td>Whether this location should serve up all users' ~/ShinyApps directories</td>
+                  <td>true</td>
+                </tr>
+              
+            </table>
+          
+
+          <p>
+            <label>Applies to:</label> <code><a href="#location">location</a></code><br/>
+            <label>Inheritable:</label> Yes<br/>
+            
+          </p>
+        </li>
+      
+        <li>
+          <h3 class="code"><a name="user_dirs"></a>user_dirs</h3>
+          <p class="desc">Configures the enclosing location scope to be an autouser root, meaning that applications will be served up from users' ~/ShinyApps directories. This directive does respect an affiliated run_as setting, meaning that the applications will be executed as whichever user is configured in the applicable run_as setting. Note that many distributions, by default, will prohibit users from being able to access each other's home directories.</p>
+          
+            <table class="params">
+              <colgroup>
+                <col style="width: 105px"></col>
+                <col style="width: 90px"></col>
+                <col style="width: 90px"></col>
+                <col></col>
+                <col style="width: 90px"></col>
+              </colgroup>
+              <tr>
+                <th>Parameter</th>
+                <th>Data type</th>
+                <th>Type</th>
+                <th>Description</th>
+                <th>Default</th>
+              </tr>
+              
+                <tr>
+                  <td>enabled</td>
+                  <td>Boolean</td>
+                  <td>optional</td>
+                  <td>Whether this location should serve up all users' ~/ShinyApps directories</td>
+                  <td>true</td>
+                </tr>
+              
+            </table>
+          
+
+          <p>
+            <label>Applies to:</label> <code><a href="#location">location</a></code><br/>
+            <label>Inheritable:</label> Yes<br/>
+            
+          </p>
+        </li>
+      
+        <li>
+          <h3 class="code"><a name="app_dir"></a>app_dir</h3>
+          <p class="desc">Configures the enclosing location scope to serve up the specified Shiny application.</p>
+          
+            <table class="params">
+              <colgroup>
+                <col style="width: 105px"></col>
+                <col style="width: 90px"></col>
+                <col style="width: 90px"></col>
+                <col></col>
+                <col style="width: 90px"></col>
+              </colgroup>
+              <tr>
+                <th>Parameter</th>
+                <th>Data type</th>
+                <th>Type</th>
+                <th>Description</th>
+                <th>Default</th>
+              </tr>
+              
+                <tr>
+                  <td>path</td>
+                  <td>String</td>
+                  <td>required</td>
+                  <td>The path to the Shiny application directory</td>
+                  <td></td>
+                </tr>
+              
+            </table>
+          
+
+          <p>
+            <label>Applies to:</label> <code><a href="#location">location</a></code><br/>
+            <label>Inheritable:</label> Yes<br/>
+            
+          </p>
+        </li>
+      
+        <li>
+          <h3 class="code"><a name="redirect"></a>redirect</h3>
+          <p class="desc">Configures the enclosing location to redirect all requests to the specified URL.</p>
+          
+            <table class="params">
+              <colgroup>
+                <col style="width: 105px"></col>
+                <col style="width: 90px"></col>
+                <col style="width: 90px"></col>
+                <col></col>
+                <col style="width: 90px"></col>
+              </colgroup>
+              <tr>
+                <th>Parameter</th>
+                <th>Data type</th>
+                <th>Type</th>
+                <th>Description</th>
+                <th>Default</th>
+              </tr>
+              
+                <tr>
+                  <td>url</td>
+                  <td>String</td>
+                  <td>required</td>
+                  <td>The URL (or base URL) to redirect to</td>
+                  <td></td>
+                </tr>
+              
+                <tr>
+                  <td>statusCode</td>
+                  <td>Integer</td>
+                  <td>optional</td>
+                  <td>The status code to send with the response (usually 301 for permanent redirects or 302 for temporary redirects)</td>
+                  <td>302</td>
+                </tr>
+              
+                <tr>
+                  <td>exact</td>
+                  <td>Boolean</td>
+                  <td>optional</td>
+                  <td>Whether to match on the URL exactly; if false, any subpaths will match as well</td>
+                  <td>true</td>
+                </tr>
+              
+            </table>
+          
+
+          <p>
+            <label>Applies to:</label> <code><a href="#location">location</a></code><br/>
+            <label>Inheritable:</label> Yes<br/>
+            
+          </p>
+        </li>
+      
+        <li>
+          <h3 class="code"><a name="log_dir"></a>log_dir</h3>
+          <p class="desc">Directs the application to write error logs to the specified directory. Only applies to location scopes that are configured with <a class="code" href="#app_dir">app_dir</a> or <a class="code" href="#site_dir">site_dir</a>, as <a class="code" href="#user_apps">user_apps</a> (autouser) always writes error logs to <code>~/ShinyApps/log</code>.</p>
+          
+            <table class="params">
+              <colgroup>
+                <col style="width: 105px"></col>
+                <col style="width: 90px"></col>
+                <col style="width: 90px"></col>
+                <col></col>
+                <col style="width: 90px"></col>
+              </colgroup>
+              <tr>
+                <th>Parameter</th>
+                <th>Data type</th>
+                <th>Type</th>
+                <th>Description</th>
+                <th>Default</th>
+              </tr>
+              
+                <tr>
+                  <td>path</td>
+                  <td>String</td>
+                  <td>required</td>
+                  <td>The path to which application log files should be written</td>
+                  <td></td>
+                </tr>
+              
+            </table>
+          
+
+          <p>
+            <label>Applies to:</label> Top-level, <code><a href="#server">server</a></code>, <code><a href="#location">location</a></code><br/>
+            <label>Inheritable:</label> Yes<br/>
+            
+          </p>
+        </li>
+      
+        <li>
+          <h3 class="code"><a name="members_of"></a>members_of</h3>
+          <p class="desc">Restricts a <a class="code" href="#user_apps">user_apps</a> or <a class="code" href="#user_dirs">user_dirs</a> (autouser) scope to require membership in one or more groups (or, if no arguments are passed, lifts group restrictions from a <a class="code" href="#members_of">members_of</a> directive in a parent scope).</p>
+          
+            <table class="params">
+              <colgroup>
+                <col style="width: 105px"></col>
+                <col style="width: 90px"></col>
+                <col style="width: 90px"></col>
+                <col></col>
+                <col style="width: 90px"></col>
+              </colgroup>
+              <tr>
+                <th>Parameter</th>
+                <th>Data type</th>
+                <th>Type</th>
+                <th>Description</th>
+                <th>Default</th>
+              </tr>
+              
+                <tr>
+                  <td>groups</td>
+                  <td>String</td>
+                  <td>multiple</td>
+                  <td>Users must be a member of at least one of these groups in order to deploy applications; if no groups are provided, then all users are allowed</td>
+                  <td></td>
+                </tr>
+              
+            </table>
+          
+
+          <p>
+            <label>Applies to:</label> Top-level, <code><a href="#server">server</a></code>, <code><a href="#location">location</a></code><br/>
+            <label>Inheritable:</label> Yes<br/>
+            
+          </p>
+        </li>
+      
+        <li>
+          <h3 class="code"><a name="google_analytics_id"></a>google_analytics_id</h3>
+          <p class="desc">Configure Google Analytics tracking code to be inserted in Shiny application pages.</p>
+          
+            <table class="params">
+              <colgroup>
+                <col style="width: 105px"></col>
+                <col style="width: 90px"></col>
+                <col style="width: 90px"></col>
+                <col></col>
+                <col style="width: 90px"></col>
+              </colgroup>
+              <tr>
+                <th>Parameter</th>
+                <th>Data type</th>
+                <th>Type</th>
+                <th>Description</th>
+                <th>Default</th>
+              </tr>
+              
+                <tr>
+                  <td>gaid</td>
+                  <td>String</td>
+                  <td>required</td>
+                  <td>The Google tracking ID, for example, UA-18988-1</td>
+                  <td></td>
+                </tr>
+              
+            </table>
+          
+
+          <p>
+            <label>Applies to:</label> Top-level, <code><a href="#server">server</a></code>, <code><a href="#location">location</a></code><br/>
+            <label>Inheritable:</label> Yes<br/>
+            
+          </p>
+        </li>
+      
+        <li>
+          <h3 class="code"><a name="app_init_timeout"></a>app_init_timeout</h3>
+          <p class="desc">Defines the amount of time Shiny Server will wait for an R process to start before giving up.</p>
+          
+            <table class="params">
+              <colgroup>
+                <col style="width: 105px"></col>
+                <col style="width: 90px"></col>
+                <col style="width: 90px"></col>
+                <col></col>
+                <col style="width: 90px"></col>
+              </colgroup>
+              <tr>
+                <th>Parameter</th>
+                <th>Data type</th>
+                <th>Type</th>
+                <th>Description</th>
+                <th>Default</th>
+              </tr>
+              
+                <tr>
+                  <td>timeout</td>
+                  <td>Integer</td>
+                  <td>required</td>
+                  <td>The number of seconds to wait for the application to start.</td>
+                  <td></td>
+                </tr>
+              
+            </table>
+          
+
+          <p>
+            <label>Applies to:</label> Top-level, <code><a href="#server">server</a></code>, <code><a href="#location">location</a></code>, <code><a href="#application">application</a></code><br/>
+            <label>Inheritable:</label> Yes<br/>
+            
+          </p>
+        </li>
+      
+        <li>
+          <h3 class="code"><a name="app_idle_timeout"></a>app_idle_timeout</h3>
+          <p class="desc">Defines the amount of time an R process will persist with no connections before being terminated.</p>
+          
+            <table class="params">
+              <colgroup>
+                <col style="width: 105px"></col>
+                <col style="width: 90px"></col>
+                <col style="width: 90px"></col>
+                <col></col>
+                <col style="width: 90px"></col>
+              </colgroup>
+              <tr>
+                <th>Parameter</th>
+                <th>Data type</th>
+                <th>Type</th>
+                <th>Description</th>
+                <th>Default</th>
+              </tr>
+              
+                <tr>
+                  <td>timeout</td>
+                  <td>Integer</td>
+                  <td>required</td>
+                  <td>The number of seconds to keep an empty R process alive before killing it.</td>
+                  <td></td>
+                </tr>
+              
+            </table>
+          
+
+          <p>
+            <label>Applies to:</label> Top-level, <code><a href="#server">server</a></code>, <code><a href="#location">location</a></code>, <code><a href="#application">application</a></code><br/>
+            <label>Inheritable:</label> Yes<br/>
+            
+          </p>
+        </li>
+      
+        <li>
+          <h3 class="code"><a name="simple_scheduler"></a>simple_scheduler</h3>
+          <p class="desc">A basic scheduler which will spawn one single-threaded R worker for each application. If no scheduler is specified, this is the default scheduler.</p>
+          
+            <table class="params">
+              <colgroup>
+                <col style="width: 105px"></col>
+                <col style="width: 90px"></col>
+                <col style="width: 90px"></col>
+                <col></col>
+                <col style="width: 90px"></col>
+              </colgroup>
+              <tr>
+                <th>Parameter</th>
+                <th>Data type</th>
+                <th>Type</th>
+                <th>Description</th>
+                <th>Default</th>
+              </tr>
+              
+                <tr>
+                  <td>maxRequests</td>
+                  <td>Integer</td>
+                  <td>optional</td>
+                  <td>The maximum number of requests to assign to this scheduler before it should start returning rejecting incoming traffic using a '503 - Service Unavailable' message. Once this threshold is hit, users attempting to initialize a new session will receive 503 errors.</td>
+                  <td>100</td>
+                </tr>
+              
+            </table>
+          
+
+          <p>
+            <label>Applies to:</label> Top-level, <code><a href="#server">server</a></code>, <code><a href="#location">location</a></code>, <code><a href="#application">application</a></code><br/>
+            <label>Inheritable:</label> Yes<br/>
+            
+          </p>
+        </li>
+      
+        <li>
+          <h3 class="code"><a name="allow_app_override"></a>allow_app_override</h3>
+          <p class="desc">If present, will allow users to override the global defaults for a scheduler by customizing the parameters associated with a scheduler or even the type of scheduler used.</p>
+          
+            <table class="params">
+              <colgroup>
+                <col style="width: 105px"></col>
+                <col style="width: 90px"></col>
+                <col style="width: 90px"></col>
+                <col></col>
+                <col style="width: 90px"></col>
+              </colgroup>
+              <tr>
+                <th>Parameter</th>
+                <th>Data type</th>
+                <th>Type</th>
+                <th>Description</th>
+                <th>Default</th>
+              </tr>
+              
+                <tr>
+                  <td>enabled</td>
+                  <td>Boolean</td>
+                  <td>optional</td>
+                  <td>Whether or not this is enabled. Default is true.</td>
+                  <td>true</td>
+                </tr>
+              
+            </table>
+          
+
+          <p>
+            <label>Applies to:</label> Top-level<br/>
+            <label>Inheritable:</label> Yes<br/>
+            
+          </p>
+        </li>
+      
+        <li>
+          <h3 class="code"><a name="application"></a>application</h3>
+          <p class="desc">DEPRECATED. This setting is deprecated and no longer enforced in Shiny Server. It will be ignored.</p>
+          
+            <table class="params">
+              <colgroup>
+                <col style="width: 105px"></col>
+                <col style="width: 90px"></col>
+                <col style="width: 90px"></col>
+                <col></col>
+                <col style="width: 90px"></col>
+              </colgroup>
+              <tr>
+                <th>Parameter</th>
+                <th>Data type</th>
+                <th>Type</th>
+                <th>Description</th>
+                <th>Default</th>
+              </tr>
+              
+                <tr>
+                  <td>empty</td>
+                  <td>String</td>
+                  <td>required</td>
+                  <td></td>
+                  <td></td>
+                </tr>
+              
+            </table>
+          
+
+          <p>
+            <label>Applies to:</label> <code><a href="#location">location</a></code><br/>
+            <label>Inheritable:</label> Yes<br/>
+            
+              <label>Child directives:</label> <code><a href="#app_init_timeout">app_init_timeout</a></code>, <code><a href="#app_idle_timeout">app_idle_timeout</a></code>, <code><a href="#simple_scheduler">simple_scheduler</a></code><br/>
+            
+          </p>
+        </li>
+      
+        <li>
+          <h3 class="code"><a name="template_dir"></a>template_dir</h3>
+          <p class="desc">A directory containing custom templates to be used when generating pages in Shiny Server.</p>
+          
+            <table class="params">
+              <colgroup>
+                <col style="width: 105px"></col>
+                <col style="width: 90px"></col>
+                <col style="width: 90px"></col>
+                <col></col>
+                <col style="width: 90px"></col>
+              </colgroup>
+              <tr>
+                <th>Parameter</th>
+                <th>Data type</th>
+                <th>Type</th>
+                <th>Description</th>
+                <th>Default</th>
+              </tr>
+              
+                <tr>
+                  <td>dir</td>
+                  <td>String</td>
+                  <td>required</td>
+                  <td>The directory containing HTML templates.</td>
+                  <td></td>
+                </tr>
+              
+            </table>
+          
+
+          <p>
+            <label>Applies to:</label> Top-level, <code><a href="#server">server</a></code>, <code><a href="#location">location</a></code><br/>
+            <label>Inheritable:</label> Yes<br/>
+            
+          </p>
+        </li>
+      
+        <li>
+          <h3 class="code"><a name="disable_websockets"></a>disable_websockets</h3>
+          <p class="desc">Disable WebSockets on connections to the server. Some networks will not reliably support WebSockets, so this setting can be used to force Shiny Server to fall back to another protocol to communicate with the server. This is equivalent to adding 'websocket' to <code>disabled_protocols</code></p>
+          
+            <table class="params">
+              <colgroup>
+                <col style="width: 105px"></col>
+                <col style="width: 90px"></col>
+                <col style="width: 90px"></col>
+                <col></col>
+                <col style="width: 90px"></col>
+              </colgroup>
+              <tr>
+                <th>Parameter</th>
+                <th>Data type</th>
+                <th>Type</th>
+                <th>Description</th>
+                <th>Default</th>
+              </tr>
+              
+                <tr>
+                  <td>val</td>
+                  <td>Boolean</td>
+                  <td>optional</td>
+                  <td>Whether or not WebSockets should be disabled.</td>
+                  <td>true</td>
+                </tr>
+              
+            </table>
+          
+
+          <p>
+            <label>Applies to:</label> Top-level<br/>
+            <label>Inheritable:</label> Yes<br/>
+            
+          </p>
+        </li>
+      
+        <li>
+          <h3 class="code"><a name="disable_protocols"></a>disable_protocols</h3>
+          <p class="desc">Disable some of the SockJS protocols used to establish a connection between your users and your server. Some network configurations cause problems with particular protocols; this option allows you to disable those.</p>
+          
+            <table class="params">
+              <colgroup>
+                <col style="width: 105px"></col>
+                <col style="width: 90px"></col>
+                <col style="width: 90px"></col>
+                <col></col>
+                <col style="width: 90px"></col>
+              </colgroup>
+              <tr>
+                <th>Parameter</th>
+                <th>Data type</th>
+                <th>Type</th>
+                <th>Description</th>
+                <th>Default</th>
+              </tr>
+              
+                <tr>
+                  <td>names</td>
+                  <td>String</td>
+                  <td>multiple</td>
+                  <td>The protocol(s) to disable. Available protocols are: 'websocket', 'xdr-streaming', 'xhr-streaming', 'iframe-eventsource', 'iframe-htmlfile', 'xdr-polling', 'xhr-polling', 'iframe-xhr-polling', 'jsonp-polling'</td>
+                  <td></td>
+                </tr>
+              
+            </table>
+          
+
+          <p>
+            <label>Applies to:</label> Top-level<br/>
+            <label>Inheritable:</label> Yes<br/>
+            
+          </p>
+        </li>
+      
+    </ul>
diff --git a/config/default.config b/config/default.config
new file mode 100644
index 0000000..97671ae
--- /dev/null
+++ b/config/default.config
@@ -0,0 +1,21 @@
+# Instruct Shiny Server to run applications as the user "shiny"
+run_as shiny;
+
+# Define a server that listens on port 3838
+server {
+  listen 3838;
+
+  # Define a location at the base URL
+  location / {
+
+    # Host the directory of Shiny Apps stored in this directory
+    site_dir /srv/shiny-server;
+
+    # Log all Shiny output to files in this directory
+    log_dir /var/log/shiny-server;
+
+    # When a user visits the base URL rather than a particular application,
+    # an index of the applications available in this directory will be shown.
+    directory_index on;
+  }
+}
diff --git a/config/init.d/debian/shiny-server b/config/init.d/debian/shiny-server
new file mode 100644
index 0000000..6426684
--- /dev/null
+++ b/config/init.d/debian/shiny-server
@@ -0,0 +1,106 @@
+#! /bin/sh
+### BEGIN INIT INFO
+# Provides:          shiny-server
+# Required-Start:    $remote_fs
+# Required-Stop:     $remote_fs
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: shiny Server
+# Description:       shiny server deploys R shiny applications
+### END INIT INFO
+
+# Author: RStudio <info at rstudio.org>
+#
+
+# Do NOT "set -e"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="shiny server"
+NAME=shiny-server
+DAEMON=shiny-server
+SCRIPTNAME=/etc/init.d/shiny-server
+
+# Exit if the package is not installed
+[ -x "$DAEMON" ] || exit 0
+
+# Load the VERBOSE setting and other rcS variables
+. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+	# Return
+	#   0 if daemon has been started
+	#   1 if daemon was already running
+	#   2 if daemon could not be started
+	start-stop-daemon --start --quiet --background --exec $DAEMON --test > /dev/null \
+		|| return 1
+	start-stop-daemon --start --quiet --background --exec $DAEMON \
+		|| return 2
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+	# Return
+	#   0 if daemon has been stopped
+	#   1 if daemon was already stopped
+	#   2 if daemon could not be stopped
+	#   other if a failure occurred
+	start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --name $NAME
+	RETVAL="$?"
+	[ "$RETVAL" = 2 ] && return 2
+	return "$RETVAL"
+}
+
+case "$1" in
+  start)
+	[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
+	do_start
+	case "$?" in
+		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+	esac
+	;;
+  stop)
+	[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+	do_stop
+	case "$?" in
+		0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+		2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+	esac
+	;;
+  restart|force-reload)
+	log_daemon_msg "Restarting $DESC" "$NAME"
+	do_stop
+	case "$?" in
+	  0|1)
+		do_start
+		case "$?" in
+			0) log_end_msg 0 ;;
+			1) log_end_msg 1 ;; # Old process is still running
+			*) log_end_msg 1 ;; # Failed to start
+		esac
+		;;
+	  *)
+	  	# Failed to stop
+		log_end_msg 1
+		;;
+	esac
+	;;
+  *)
+	echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
+	exit 3
+	;;
+esac
+
+:
diff --git a/config/init.d/redhat/shiny-server b/config/init.d/redhat/shiny-server
new file mode 100755
index 0000000..d7237a8
--- /dev/null
+++ b/config/init.d/redhat/shiny-server
@@ -0,0 +1,100 @@
+#!/bin/sh
+#
+# chkconfig:   2345 80 20 
+# description: shiny-server deploys R shiny applications
+# processname: shiny-server
+
+# Source function library.
+. /etc/rc.d/init.d/functions
+
+prog=shiny-server
+execute=$(which $prog)
+logfile="/var/log/${prog}.log"
+lockfile="/var/run/${prog}.pid"
+
+# Exit if the package is not installed
+if [ ! -x $execute ] ; then 
+   echo -n $"$prog wasn't found or isn't executable."
+   exit 1
+fi
+
+start() {
+    touch $logfile
+    echo -n $"Starting shiny-server: "
+    if [ -e $lockfile ] && [ -e /proc/`cat /var/run/shiny-server.pid` ]; then
+        echo -n $"already running.";
+        failure $"cannot start shiny-server: already running.";
+        echo
+        return 1
+    fi
+    $prog --daemon "--pidfile=$lockfile" >> $logfile 2>&1 &
+    retval=$?
+    [ $retval -eq 0 ] && success || failure
+    echo
+    return $retval
+}
+
+stop() {
+    echo -n $"Stopping shiny-server: "
+    if [ ! -e $lockfile ] || [ ! -e /proc/`cat /var/run/shiny-server.pid` ]; then
+        echo -n $"not running.";
+        failure $"cannot stop shiny-server: not running.";
+        echo
+        return 1
+    fi
+    kill `cat "$lockfile"`
+    retval=$?
+    [ $retval -eq 0 ] && success || failure
+    echo
+    return $retval
+}
+
+restart() {
+    stop
+    sleep 1
+    start
+}
+
+reload() {
+    echo -n $"Reloading shiny-server: "
+    if [ ! -e $lockfile ] || [ ! -e /proc/`cat /var/run/shiny-server.pid` ]; then
+        echo -n $"not running.";
+        failure $"cannot reload shiny-server: not running.";
+        echo
+        return 1
+    fi
+    kill -1 `cat "$lockfile"`
+    retval=$?
+    [ $retval -eq 0 ] && success || failure
+    echo
+    return $retval 
+}
+
+rh_status() {
+    status $prog
+}
+
+rh_status_q() {
+    rh_status >/dev/null 2>&1
+}
+
+case "$1" in
+    start)
+        start
+        ;;
+    stop)
+        stop
+        ;;
+    restart)
+        $1
+        ;;
+    reload)
+        $1
+        ;;
+    status)
+        rh_status
+        ;;
+    *)
+        echo $"Usage: $0 {start|stop|status|restart}"
+        exit 2
+esac
diff --git a/config/init.d/suse/shiny-server b/config/init.d/suse/shiny-server
new file mode 100644
index 0000000..93ae2e4
--- /dev/null
+++ b/config/init.d/suse/shiny-server
@@ -0,0 +1,98 @@
+#!/bin/sh
+#
+### BEGIN INIT INFO
+# Provides:          shiny-server
+# Required-Start:    $local_fs $remote_fs $syslog $network $named
+# Required-Stop:     $local_fs $remote_fs $syslog $network $named
+# Default-Start:     3 5
+# Default-Stop:      0 1 2 6
+# Short-Description: shiny-server deploys R shiny applications
+# Description:       shiny-server deploys R shiny applications
+### END INIT INFO
+
+prog=shiny-server
+logfile="/var/log/${prog}.log"
+lockfile="/var/run/${prog}.pid"
+
+execute=$(which $prog)
+
+# Exit if the package is not installed
+if [ ! -x "$execute" ] ; then
+   echo -n $"$prog wasn't found or isn't executable."
+   exit 1
+fi
+
+# Source LSB init functions
+. /etc/rc.status
+
+# Reset status of this service
+rc_reset
+
+case "$1" in
+    start)
+	echo -n "Starting shiny-server "
+
+	if [ -e $lockfile ] && [ -e /proc/`cat /var/run/shiny-server.pid` ]; then
+      echo -n "cannot start shiny-server: already running."
+      rc_failed 3
+  else 
+  	$execute --daemon "--pidfile=$lockfile" >> $logfile 2>&1 &
+	  retval=$?
+	  [ $retval -eq 0 ] && echo -n || rc_failed 1
+  fi
+
+	# Remember status and be verbose
+	rc_status -v
+	;;
+    stop)
+	echo -n $"Stopping shiny-server: "
+  if [ ! -e $lockfile ] || [ ! -e /proc/`cat /var/run/shiny-server.pid` ]; then
+      echo -n "cannot stop shiny-server: not running."
+      rc_failed 1
+  else
+  	kill `cat "$lockfile"`
+	  retval=$?
+	  [ $retval -eq 0 ] && echo -n  || rc_failed 1
+  fi
+  
+	# Remember status and be verbose
+	rc_status -v
+	;;
+    restart)
+	## Stop the service and regardless of whether it was
+	## running or not, start it again.
+	$0 stop
+	sleep 1
+	$0 start
+
+	# Remember status and be quiet
+	rc_status
+	;;
+    reload)
+	echo -n $"Reloading shiny-server: "
+  if [ ! -e $lockfile ] || [ ! -e /proc/`cat /var/run/shiny-server.pid` ]; then
+      echo -n $"cannot reload shiny-server: not running.";
+      rc_failed 1
+  else 
+  	kill -1 `cat "$lockfile"`
+		retval=$?
+		[ $retval -eq 0 ] && echo -n || rc_failed 1
+  fi
+  
+	rc_status -v
+	;;
+    status)
+	echo -n "Checking for service shiny-server"
+
+	if [ ! -e $lockfile ] || [ ! -e /proc/`cat /var/run/shiny-server.pid` ]; then
+    rc_failed 2
+  fi
+
+	# Remember status and be verbose
+	rc_status -v
+	;;
+esac
+rc_exit
+
+
+
diff --git a/config/logrotate b/config/logrotate
new file mode 100644
index 0000000..3c47a02
--- /dev/null
+++ b/config/logrotate
@@ -0,0 +1,7 @@
+/var/log/shiny-server.log {
+       rotate 12
+       copytruncate
+       compress
+       missingok
+       size 1M
+}
\ No newline at end of file
diff --git a/config/multi-server.config b/config/multi-server.config
new file mode 100644
index 0000000..d7ac3e3
--- /dev/null
+++ b/config/multi-server.config
@@ -0,0 +1,46 @@
+
+# Instruct Shiny Server to run applications as the user ':HOME_USER:', where
+# possible (this username only takes effect in locations managed by 
+# 'user_dirs'). In all other locations, we should fall back to the 'shiny' user.
+run_as :HOME_USER: shiny;
+
+# Define a server that listens of port 3838.
+server {
+  listen 3838;
+
+  # Define a location at the URL "/users"
+  location /users {
+    
+    # Allow users to host their own apps in ~/ShinyApps
+    user_dirs;
+
+    # Optionally, you can restrict the privilege of hosting Shiny applications
+    # only to members of a particular Linux group.
+    # members_of shinyUsers;
+  }
+
+  # Define the location at the URL "/example1"
+  location /example1 {
+    app_dir /srv/shiny-server/sample-apps/hello;
+    log_dir /var/log/shiny-server;
+  }
+}
+
+# Define a server that listens on port 4949
+server {
+  listen 4949;
+
+  # Define a location at the base URL
+  location / {
+
+    # Host the directory of Shiny Apps stored in this directory
+    site_dir /srv/shiny-server;
+
+    # Log all Shiny output to files in this directory
+    log_dir /var/log/shiny-server;
+
+    # When a user visits the base URL rather than a particular application,
+    # an index of the applications available in this directory will be shown.
+    directory_index on;
+  }
+}
diff --git a/config/shiny-server-rules.config b/config/shiny-server-rules.config
new file mode 100644
index 0000000..a39a518
--- /dev/null
+++ b/config/shiny-server-rules.config
@@ -0,0 +1,218 @@
+run_as {
+  # Description of the directive
+  desc "The user the app should be run as. This user should have the minimal amount of privileges necessary to successfully run the application (i.e. read-only access to the Shiny application directory). Note that this directive cannot be used with `user_apps`, as `user_apps` always run as the user who owns the application.";
+
+  # required parameter
+  param String users... "The username that should be used to run the app. If using home_dirs, this can also be the special keyword `:HOME_USER:` which will instruct `home_dirs` to run as the user in whose home directory the application exists. If using a special keyword like `:HOME_USER:`, you can specify additional usernames afterwards which will be used when this directive is applied to hosting models other than `home_dirs`.";
+
+  # List of parent node names that are valid for this directive ($ means root)
+  at $ server location;
+
+  # Up to how many times can this directive appear within a node's scope (not
+  # including descendant nodes)?
+  maxcount 1;
+}
+
+socket_dir {
+  desc "The path to the (empty) directory within which Shiny worker processes should create domain sockets. The directory should be owned by root with permissions set to `0333`. Defaults to `/var/shiny-server/sockets` if this directive is not present.";
+  param String path "The domain socket directory path (should be absolute).";
+  at $;
+  undocumented;
+}
+
+access_log {
+  desc "The file path of the HTTP access log.";
+  param String path "The file path where the access log should be written";
+  param String [format] "The log file format; see [Connect documentation](http://www.senchalabs.org/connect/logger.html) under \"Formats\"" default;
+  at $;
+}
+
+server {
+  desc "Declares an HTTP server. You need one of these for each port/IP address combination this Shiny Server instance should listen on.";
+  at $;
+}
+
+listen {
+  desc "Directs the enclosing server scope to listen on the specified port/IP address combination.";
+  param Integer port "Port to listen on";
+  # Optional parameter
+  param String [host] "IPv4 address to listen on (`*` or `0.0.0.0` for all); hostnames are not currently allowed, please use raw IPv4 address only" *;
+  at server;
+  maxcount 1;
+}
+
+server_name {
+  desc "Directs the enclosing server scope to only honor requests that have the given host headers (i.e. virtual hosts).";
+  # Variadic parameter
+  param String names... "The virtual hostname(s) to bind this server to";
+  at server;
+  maxcount 1;
+}
+
+location {
+  desc "Creates a scope that configures the given URL as a website (`site_dir`), specific application (`app_dir`), autouser root (`user_apps`), autouser root with `run_as` support (`user_dirs`), or redirect to a different URL (`redirect`).";
+  param String path "The request path that this location should match";
+  at server location;
+}
+
+site_dir {
+  desc "Configures the enclosing location scope to be a website that can contain both Shiny applications and unrelated static assets in a single directory tree.";
+  param String rootPath "The path to the root directory of the website";
+  at location;
+  maxcount 1;
+  precludes user_apps app_dir redirect user_dirs;
+}
+
+directory_index {
+  desc "When enabled, if a directory is requested by the client and an `index.html` file is not present, a list of the directory contents is created automatically and returned to the client. If this directive is not present in a custom config file, the default behavior is to disable directory indexes. However, it is enabled if no config file is present at all (in other words, the default config file has it enabled).";
+  param Boolean enabled "Whether directory contents should automatically displayed";
+  at $ server location;
+  maxcount 1;
+}
+
+user_apps {
+  desc "DEPRECATED! This directive has been deprecated in favor of `user_dirs`, which offers more flexibility with regards to the `run_as` configuration. Configures the enclosing location scope to be an autouser root, meaning that applications will be served up from users' ~/ShinyApps directories and all Shiny processes will run as the user in whose directory the application is found.";
+  param Boolean [enabled] "Whether this location should serve up all users' ~/ShinyApps directories" on;
+  at location;
+  maxcount 1;
+  precludes app_dir redirect log_dir run_as user_dirs site_dir;
+}
+
+user_dirs {
+  desc "Configures the enclosing location scope to be an autouser root, meaning that applications will be served up from users' ~/ShinyApps directories. This directive does respect an affiliated run_as setting, meaning that the applications will be executed as whichever user is configured in the applicable run_as setting. Note that many distributions, by default, will prohibit users from being able to access each other's home directories.";
+  param Boolean [enabled] "Whether this location should serve up all users' ~/ShinyApps directories" on;
+  at location;
+  maxcount 1;
+  precludes app_dir redirect log_dir site_dir user_apps;
+}
+
+app_dir {
+  desc "Configures the enclosing location scope to serve up the specified Shiny application.";
+  param String path "The path to the Shiny application directory";
+  at location;
+  maxcount 1;
+  precludes redirect user_apps site_dir user_dirs;
+}
+
+redirect {
+  desc "Configures the enclosing location to redirect all requests to the specified URL.";
+  param String url "The URL (or base URL) to redirect to";
+  param Integer [statusCode] "The status code to send with the response (usually 301 for permanent redirects or 302 for temporary redirects)" 302;
+  param Boolean [exact] "Whether to match on the URL exactly; if false, any subpaths will match as well" true;
+
+  at location;
+  maxcount 1;
+
+  precludes log_dir google_analytics_id;
+}
+
+log_dir {
+  desc "Directs the application to write error logs to the specified directory. Only applies to location scopes that are configured with `app_dir` or `site_dir`, as `user_apps` (autouser) always writes error logs to `~/ShinyApps/log`.";
+  param String path "The path to which application log files should be written";
+  at $ server location;
+  maxcount 1;
+}
+
+members_of {
+  desc "Restricts a `user_apps` or `user_dirs` (autouser) scope to require membership in one or more groups (or, if no arguments are passed, lifts group restrictions from a `members_of` directive in a parent scope).";
+  param String groups... "Users must be a member of at least one of these groups in order to deploy applications; if no groups are provided, then all users are allowed";
+  at $ server location;
+  maxcount 1;
+}
+
+google_analytics_id {
+  desc "Configure Google Analytics tracking code to be inserted in Shiny application pages.";
+  param String gaid "The Google tracking ID, for example, UA-18988-1";
+  at $ server location;
+  internal true;  # Probably shouldn't document this since it will change soon
+}
+
+app_init_timeout {
+  desc "Defines the amount of time Shiny Server will wait for an R process to start before giving up.";
+  param Integer timeout "The number of seconds to wait for the application to start.";
+  at $ server location application;
+  maxcount 1;
+}
+
+app_idle_timeout {
+  desc "Defines the amount of time an R process will persist with no connections before being terminated.";
+  param Integer timeout "The number of seconds to keep an empty R process alive before killing it.";
+  at $ server location application;
+  maxcount 1;
+}
+
+simple_scheduler {
+  desc "A basic scheduler which will spawn one single-threaded R worker for each application. If no scheduler is specified, this is the default scheduler.";
+  param Integer [maxRequests] "The maximum number of requests to assign to this scheduler before it should start returning rejecting incoming traffic using a '503 - Service Unavailable' message. Once this threshold is hit, users attempting to initialize a new session will receive 503 errors." 100;
+  at $ server location application;
+  maxcount 1;
+}
+
+allow_app_override {
+  desc "If present, will allow users to override the global defaults for a scheduler by customizing the parameters associated with a scheduler or even the type of scheduler used.";
+  at $;
+  param Boolean [enabled] "Whether or not this is enabled. Default is true." true;
+  maxcount 1;
+}
+
+application {
+  desc "DEPRECATED. This setting is deprecated and no longer enforced in Shiny Server. It will be ignored.";
+  param String empty "";
+  at location;
+}
+
+template_dir {
+  desc "A directory containing custom templates to be used when generating pages in Shiny Server.";
+  param String dir "The directory containing HTML templates.";
+  at $ server location;
+  maxcount 1;
+}
+
+bookmark_state_dir {
+  desc "A directory for storing persisted application state. If Shiny Server is running without root privileges, then the `run_as` account must have read/write access to this directory. If no `bookmark_state_dir` directive is provided, `/var/lib/shiny-server/bookmarks` will be used.";
+  param String dir "The directory where bookmark data should be stored.";
+  at $ server location;
+  maxcount 1;
+}
+
+disable_websockets {
+  desc "Disable WebSockets on connections to the server. Some networks will not reliably support WebSockets, so this setting can be used to force Shiny Server to fall back to another protocol to communicate with the server. This is equivalent to adding 'websocket' to `disabled_protocols`";
+  param Boolean [val] "Whether or not WebSockets should be disabled." true;
+  at $ server location;
+  maxcount 1;
+}
+
+disable_protocols {
+  desc "Disable some of the SockJS protocols used to establish a connection between your users and your server. Some network configurations cause problems with particular protocols; this option allows you to disable those.";
+  param String names... "The protocol(s) to disable. Available protocols are: 'websocket', 'xdr-streaming', 'xhr-streaming', 'iframe-eventsource', 'iframe-htmlfile', 'xdr-polling', 'xhr-polling', 'iframe-xhr-polling', 'jsonp-polling'";
+  at $ server location;
+  maxcount 1;
+}
+
+preserve_logs {
+  desc "By default, log files from Shiny processes that exited successfully (exit status 0) will be deleted. This behavior can be overridden by setting this property to `true` in which case Shiny Server will not delete the log files from any Shiny process that it spawns. WARNING: This feature should only be enabled when combined with proper log rotation. Otherwise, thousands of log files could quickly accrue and cause problems for the file system on which they are stored.";
+  param Boolean [enabled] "Whether or not logs should be preserved." false;
+  at $;
+  maxcount 1;
+}
+
+log_as_user {
+  desc "By default, the log files for R processes are created and managed by the user running the server centrally (often root). In the typical scenario in which the logs are stored in a server-wide directory, this is desirable as only root user may have write access to such a directory. In other cases, such as using `user_dirs` on a system in which the users' home directories are on an NFS mount which uses `root_squash`, creating log files as root may be a problem. In those scenarios, t [...]
+  param Boolean [enabled] "Whether or not the log files should be managed by the owner of the process to which they belong." false;
+  at $ server location;
+  maxcount 1;
+}
+
+reconnect {
+  desc "When a user's connection to the server is interrupted, Shiny Server will offer them a dialog that allows them to reconnect to their existing Shiny session for 15 seconds. This implies that the server will keep the Shiny session active on the server for an extra 15 seconds after a user disconnects in case they reconnect. After the 15 seconds, the user's session will be reaped and they will be notified and offered an opportunity to refresh the page. If this setting is true, the ser [...]
+  param Boolean [enabled] "Whether or not to offer to automatically reconnect disconnected users." true;
+  at $ server location;
+  maxcount 1;
+}
+
+sanitize_errors {
+  desc "If this setting is true (the default), only generic error messages will be shown to the client (unless these were wrapped in `safeError()`).";
+  param Boolean [enabled] "Whether or not to sanitize error messages on the client." true;
+  at $ server location;
+  maxcount 1;
+}
diff --git a/config/systemd/shiny-server.debian.service b/config/systemd/shiny-server.debian.service
new file mode 100644
index 0000000..459d8c2
--- /dev/null
+++ b/config/systemd/shiny-server.debian.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=ShinyServer
+
+[Service]
+TimeoutStartSec=10
+ExecStart=/bin/bash -c '/opt/shiny-server/bin/shiny-server --pidfile=/var/run/shiny-server.pid >> /var/log/shiny-server.log 2>&1'
+# Needed to give SS a chance to write out to the PID file.
+ExecStartPost=/bin/sleep 3
+PIDFile=/var/run/shiny-server.pid
+Type=simple
+
+ExecReload=/bin/kill -HUP $MAINPID
+ExecStopPost=/bin/sleep 5
+RestartSec=1
+
+
+[Install]
+WantedBy=multi-user.target
diff --git a/config/systemd/shiny-server.service b/config/systemd/shiny-server.service
new file mode 100644
index 0000000..6528fe2
--- /dev/null
+++ b/config/systemd/shiny-server.service
@@ -0,0 +1,18 @@
+[Unit]
+Description=ShinyServer
+
+[Service]
+TimeoutStartSec=10
+ExecStart=/usr/bin/bash -c '/opt/shiny-server/bin/shiny-server --pidfile=/var/run/shiny-server.pid >> /var/log/shiny-server.log 2>&1'
+# Needed to give SS a chance to write out to the PID file.
+ExecStartPost=/usr/bin/sleep 3
+PIDFile=/var/run/shiny-server.pid
+Type=simple
+
+ExecReload=/bin/kill -HUP $MAINPID
+ExecStopPost=/usr/bin/sleep 5
+RestartSec=1
+
+
+[Install]
+WantedBy=multi-user.target
diff --git a/config/upstart/shiny-server.conf b/config/upstart/shiny-server.conf
new file mode 100644
index 0000000..74f6df3
--- /dev/null
+++ b/config/upstart/shiny-server.conf
@@ -0,0 +1,26 @@
+# shiny-server.conf
+
+description "Shiny application server"
+
+start on runlevel [2345]
+stop on runlevel [016]
+
+limit nofile 1000000 1000000
+
+post-stop exec sleep 3
+
+post-start script
+    i=0
+    while [ $i -lt 5 ]
+    do
+        pgrep "shiny-server" || exit 1
+        sleep 1
+        i=$((i+1))
+    done
+end script 
+  
+exec shiny-server --pidfile=/var/run/shiny-server.pid >> /var/log/shiny-server.log 2>&1
+
+respawn limit 3 30
+
+respawn
diff --git a/config/user-dirs.config b/config/user-dirs.config
new file mode 100644
index 0000000..98001ac
--- /dev/null
+++ b/config/user-dirs.config
@@ -0,0 +1,20 @@
+
+# Tell Shiny Server that we want to run as the user whose 
+# home directory we find the application in.
+run_as :HOME_USER:;
+
+# Define a server that listens of port 3838.
+server {
+  listen 3838;
+
+  # Define a location at the base URL
+  location / {
+    
+    # Allow users to host their own apps in ~/ShinyApps
+    user_dirs;
+
+    # Optionally, you can restrict the privilege of hosting Shiny applications
+    # only to members of a particular Linux group.
+    # members_of shinyUsers;
+  }
+}
diff --git a/docker/README.md b/docker/README.md
new file mode 100644
index 0000000..7bdfbe7
--- /dev/null
+++ b/docker/README.md
@@ -0,0 +1,85 @@
+Docker containers for Shiny Server development
+==============================================
+
+The `ubuntu16.04/` directory contains a Dockerfile for creating an Docker image that can be used for Shiny Server development.
+
+**Note:** Any files created will have UID 1000. This UID is generally the same as the first user added on most Linux hosts, but not for macOS hosts. This may cause problems with creating or modifying any files. Hopefully, Docker will provide a good solution for this in the future.
+
+
+## Building the image
+
+To build the Docker image, start in the shiny-server directory and run:
+
+```sh
+docker build -t ss-devel docker/ubuntu16.04/
+```
+
+## Running the image
+
+```sh
+docker run --rm -ti -p 3838:3838 -v $(pwd):/shiny-server --name ssd ss-devel
+```
+
+This will start a shell as the `docker` user. This user can `sudo` without a password.
+
+Shiny Server has already been installed in the image, so various system files, directories, and the `shiny` user will already exist.
+
+Once started, you will need to build Shiny Server once:
+
+```sh
+cd /shiny-server
+packaging/make-package.sh
+```
+
+Once this is done, you can start `shiny-server` directly from the development directory. You can also modify the source code and restart `shiny-server`.
+
+```sh
+cd /shiny-server
+sudo bin/shiny-server
+```
+
+You can then visit the server at http://localhost:3838/.
+
+After making changes, you may want to rebuild the package and reinstall:
+
+```sh
+packaging/make-package.sh
+
+# Might need to adjust the version
+sudo dpkg -i packaging/build/shiny-server-1.4.4.0-amd64.deb
+```
+
+
+## Notes
+
+If you want to mount the shiny source at `/shiny` and be able to install it, start the container with `-v $(pwd)/../shiny:/shiny` (assuming it's a sibling directory of `shiny-server/`):
+
+```sh
+# Start in shiny-server directory
+docker run --rm -ti -p 3838:3838 \
+    -v $(pwd):/shiny-server \
+    -v $(pwd)/../shiny:/shiny \
+    --name ssd ss-devel
+
+# Then, in another terminal, you can install Shiny:
+docker exec -ti ssd Rscript -e 'devtools::install("/shiny")'
+```
+
+
+If you need to enter a running container and inspect it or install software on it, run:
+
+```
+docker exec -ti ssd /bin/bash
+```
+
+
+If you want to rebuild the docker image without any cached layers, you can delete the `ss-devel` image and rebuild, or you can build with the `--no-cache` flag. The image can be deleted with:
+
+```sh
+# Remove the image and rebuild
+docker rmi ss-devel
+docker build -t ss-devel docker/ubuntu16.04/
+
+# Or build with no cache
+docker build --no-cache -t ss-devel docker/ubuntu16.04/
+```
diff --git a/docker/ubuntu16.04/Dockerfile b/docker/ubuntu16.04/Dockerfile
new file mode 100644
index 0000000..7266cbc
--- /dev/null
+++ b/docker/ubuntu16.04/Dockerfile
@@ -0,0 +1,80 @@
+# To build, cd to the shiny server directory, then:
+#   docker build -t ss-devel docker/ubuntu16.04/
+#
+# To run:
+#   docker run --rm -ti -p 3838:3838 -v $(pwd):/shiny-server --name ss ss-devel
+
+FROM ubuntu:16.04
+
+MAINTAINER Winston Chang "winston at rstudio.com"
+
+# =====================================================================
+# R
+# =====================================================================
+
+# Don't print "debconf: unable to initialize frontend: Dialog" messages
+ARG DEBIAN_FRONTED=noninteractive
+
+# Need this to add R repo
+RUN apt-get update && apt-get install -y software-properties-common
+
+# Add R apt repository
+RUN add-apt-repository "deb http://cran.r-project.org/bin/linux/ubuntu $(lsb_release -cs)/"
+RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E084DAB9
+
+# Install basic stuff and R
+RUN apt-get update && apt-get install -y \
+    sudo \
+    git \
+    vim-tiny \
+    less \
+    wget \
+    r-base \
+    r-base-dev \
+    r-recommended \
+    fonts-texgyre
+
+RUN echo 'options(\n\
+  repos = c(CRAN = "https://cran.r-project.org/"),\n\
+  download.file.method = "libcurl",\n\
+  # Detect number of physical cores\n\
+  Ncpus = parallel::detectCores(logical=FALSE)\n\
+)' >> /etc/R/Rprofile.site
+
+# Create docker user with empty password (will have uid and gid 1000)
+RUN useradd --create-home --shell /bin/bash docker \
+    && passwd docker -d \
+    && adduser docker sudo
+
+# =====================================================================
+# Shiny Server dev stuff + Shiny
+# =====================================================================
+
+RUN apt-get update && apt-get install -y \
+    gdebi-core \
+    pandoc \
+    pandoc-citeproc \
+    libcurl4-gnutls-dev \
+    libcairo2-dev \
+    libxt-dev \
+    libssl-dev \
+    libxml2-dev \
+    cmake
+
+# Download and install shiny server
+RUN wget --no-verbose https://s3.amazonaws.com/rstudio-shiny-server-os-build/ubuntu-12.04/x86_64/VERSION -O "version.txt" && \
+    VERSION=$(cat version.txt)  && \
+    wget --no-verbose "https://s3.amazonaws.com/rstudio-shiny-server-os-build/ubuntu-12.04/x86_64/shiny-server-$VERSION-amd64.deb" -O ss-latest.deb && \
+    gdebi -n ss-latest.deb && \
+    rm -f version.txt ss-latest.deb
+
+EXPOSE 3838
+
+RUN R -e "install.packages(c('devtools', 'rmarkdown'))"
+
+# Install latest shiny from GitHub and copy examples
+RUN R -e "devtools::install_github('rstudio/shiny')" && \
+  cp -R /usr/local/lib/R/site-library/shiny/examples/* /srv/shiny-server/
+
+USER docker
+WORKDIR /home/docker
diff --git a/external/install-dependencies-debian b/external/install-dependencies-debian
new file mode 100644
index 0000000..4c1ea5c
--- /dev/null
+++ b/external/install-dependencies-debian
@@ -0,0 +1,15 @@
+#!/usr/bin/env false
+
+# This is not an executable shell script--please execute each line by hand
+
+# Install build prerequisites
+sudo apt-get install make gcc g++ git python libssl-dev cmake
+
+# Download, build, and install CMake (must be 2.8.10 or later). If a suitable
+# version is available from apt-get, then use it instead.
+wget http://www.cmake.org/files/v2.8/cmake-2.8.11.2.tar.gz
+tar xzf cmake-2.8.11.2.tar.gz
+cd cmake-2.8.11.2
+./configure
+make
+sudo make install
diff --git a/external/install-dependencies-redhat5 b/external/install-dependencies-redhat5
new file mode 100755
index 0000000..fac339b
--- /dev/null
+++ b/external/install-dependencies-redhat5
@@ -0,0 +1,24 @@
+#!/usr/bin/env false
+
+# This is not an executable shell script--please execute each line by hand
+
+# If you've never done so before, install the EPEL repo information
+wget http://dl.fedoraproject.org/pub/epel/5/i386/epel-release-5-4.noarch.rpm
+sudo rpm -Uvh epel-release-5-4.noarch.rpm
+
+# Install build prerequisites
+sudo yum -y install make gcc gcc-c++ git python26 openssl-devel rpm-build
+
+# If you are not able to connect to https URLs during build, it's probably
+# because of the limited number of certificate authorities that are included
+# with the OS by default. Use this command to replace the default bundle with
+# a more extensive list.
+sudo curl http://curl.haxx.se/ca/cacert.pem -o /etc/pki/tls/certs/ca-bundle.crt
+
+# Download, build, and install CMake (must be 2.8.10 or later).
+wget http://www.cmake.org/files/v2.8/cmake-2.8.11.2.tar.gz
+tar xzf cmake-2.8.11.2.tar.gz
+cd cmake-2.8.11.2
+./configure
+make
+sudo make install
diff --git a/external/install-dependencies-redhat6 b/external/install-dependencies-redhat6
new file mode 100755
index 0000000..40132be
--- /dev/null
+++ b/external/install-dependencies-redhat6
@@ -0,0 +1,17 @@
+#!/usr/bin/env false
+
+# This is not an executable shell script--please execute each line by hand
+
+# If you've never done so before, install the EPEL repo information
+wget http://dl.fedoraproject.org/pub/epel/6/i386/epel-release-6-8.noarch.rpm
+sudo rpm -Uvh epel-release-6-8.noarch.rpm
+
+# Install build prerequisites
+sudo yum -y install make gcc gcc-c++ git python openssl-devel rpm-build cmake28
+
+# If you are not able to connect to https URLs during build, it's probably
+# because of the limited number of certificate authorities that are included
+# with the OS by default. Use this command to replace the default bundle with
+# a more extensive list.
+sudo curl http://curl.haxx.se/ca/cacert.pem -o /etc/pki/tls/certs/ca-bundle.crt
+
diff --git a/external/node/CMakeLists.txt b/external/node/CMakeLists.txt
new file mode 100644
index 0000000..76e5e7b
--- /dev/null
+++ b/external/node/CMakeLists.txt
@@ -0,0 +1,50 @@
+project(node)
+
+set(NODEJS_VERSION 6.9.1)
+# See e.g. http://nodejs.org/dist/v0.10.21/SHASUMS.txt to find the appropriate
+# hash value for the node-v0.10.21.tar.gz file.
+set(NODEJS_SHA256 a98997ca3a4d10751f0ebe97839b2308a31ae884b4203cda0c99cf36bc7fe3bf)
+
+include(ExternalProject)
+
+# Allow overriding of Python command used to build node.js (pass
+# -DPYTHON=<path> to cmake).
+if(NOT PYTHON)
+  if(EXISTS /usr/bin/python26)
+    # RedHat/CentOS 5 requires python26, because python means Python 2.4
+    set(PYTHON python26)
+  else()
+    set(PYTHON python)
+  endif()
+endif()
+
+get_filename_component(NODE_PREFIX 
+                       "${CMAKE_CURRENT_SOURCE_DIR}/../../ext/node"
+                       REALPATH)
+
+execute_process(COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/../../ext/node/bin/node" "--version"
+  RESULT_VARIABLE NODEVER_EXIT
+  OUTPUT_VARIABLE NODEVER
+  OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+if (NOT NODEVER STREQUAL "v${NODEJS_VERSION}")
+  # Download and build Node.js.
+  # We copy bin/node to bin/shiny-server in order to make the process table
+  # show "shiny-server" for our process.
+  ExternalProject_Add(
+    node
+    URL http://nodejs.org/dist/v${NODEJS_VERSION}/node-v${NODEJS_VERSION}.tar.gz
+    URL_HASH SHA256=${NODEJS_SHA256}
+    DOWNLOAD_DIR download
+    SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/node-v${NODEJS_VERSION}
+    BUILD_IN_SOURCE 1
+    CONFIGURE_COMMAND ${PYTHON} ${CMAKE_CURRENT_SOURCE_DIR}/node-v${NODEJS_VERSION}/configure
+                      "--prefix=${NODE_PREFIX}"
+    BUILD_COMMAND make -j4 PYTHON=${PYTHON}
+    INSTALL_COMMAND make install &&
+                    cp "${NODE_PREFIX}/bin/node" "${NODE_PREFIX}/bin/shiny-server" &&
+                    rm "${NODE_PREFIX}/bin/npm" &&
+                    cd "${NODE_PREFIX}/lib/node_modules/npm" &&
+                    ./scripts/relocate.sh
+  )
+endif()
\ No newline at end of file
diff --git a/external/pandoc/CMakeLists.txt b/external/pandoc/CMakeLists.txt
new file mode 100644
index 0000000..26e2825
--- /dev/null
+++ b/external/pandoc/CMakeLists.txt
@@ -0,0 +1,64 @@
+project(pandoc)
+
+set(PANDOC_VERSION 1.12.3)
+
+get_filename_component(PANDOC_PREFIX 
+                       "${CMAKE_CURRENT_SOURCE_DIR}/../../ext/pandoc"
+                       REALPATH)
+
+set(PANDOC_SHA1 11f65f5c35d7525dc4c40acf6019fb19a4b48c7e)
+
+set(MY_DOWNLOAD_PATH "${CMAKE_CURRENT_BINARY_DIR}/pandoc-${PANDOC_VERSION}.zip")
+
+if (NOT EXISTS "${MY_DOWNLOAD_PATH}")
+    file(DOWNLOAD "http://s3.amazonaws.com/rstudio-buildtools/pandoc-${PANDOC_VERSION}.zip" "${MY_DOWNLOAD_PATH}" 
+    EXPECTED_MD5 1541304bf51d86e361b7d930f899ac1c)
+endif()
+
+execute_process(
+    COMMAND mkdir -p ${PANDOC_PREFIX}
+    DEPENDS "${MY_DOWNLOAD_PATH}")
+
+# Detect distribution
+if(EXISTS "/etc/debian_version")
+  set(DISTRIBUTION debian)
+    # Then we'll want the dynamic versions
+    execute_process(
+        COMMAND unzip -o -u -j ${MY_DOWNLOAD_PATH} pandoc-${PANDOC_VERSION}/linux/${DISTRIBUTION}/x86_64/pandoc -d ${PANDOC_PREFIX}
+        DEPENDS "${MY_DOWNLOAD_PATH}")
+    
+    execute_process(
+        COMMAND unzip -o -u -j ${MY_DOWNLOAD_PATH} pandoc-${PANDOC_VERSION}/linux/${DISTRIBUTION}/x86_64/pandoc-citeproc -d ${PANDOC_PREFIX}
+        DEPENDS "${MY_DOWNLOAD_PATH}")
+endif()
+
+if(EXISTS "/etc/redhat-release")
+  set(DISTRIBUTION rpm)
+
+  execute_process(
+    COMMAND sed -nr "s/.*release\\ ([0-9]).*/\\1/p" /etc/redhat-release
+    OUTPUT_VARIABLE REDHAT_VER)
+
+  if (${REDHAT_VER} EQUAL "5")
+    # Need the dynamic versions of pandoc.
+    execute_process(
+        COMMAND unzip -o -u -j ${MY_DOWNLOAD_PATH} pandoc-${PANDOC_VERSION}/linux/${DISTRIBUTION}/x86_64/pandoc -d ${PANDOC_PREFIX}
+        DEPENDS "${MY_DOWNLOAD_PATH}")
+    
+    execute_process(
+        COMMAND unzip -o -u -j ${MY_DOWNLOAD_PATH} pandoc-${PANDOC_VERSION}/linux/${DISTRIBUTION}/x86_64/pandoc-citeproc -d ${PANDOC_PREFIX}
+        DEPENDS "${MY_DOWNLOAD_PATH}")
+  endif()
+endif()
+
+execute_process(
+  COMMAND mkdir -p ${PANDOC_PREFIX}/static
+  DEPENDS "${MY_DOWNLOAD_PATH}")
+
+execute_process(
+  COMMAND unzip -o -u -j ${MY_DOWNLOAD_PATH} pandoc-${PANDOC_VERSION}/linux/debian/x86_64/pandoc -d ${PANDOC_PREFIX}/static
+  DEPENDS "${MY_DOWNLOAD_PATH}")
+
+execute_process(
+    COMMAND unzip -o -u -j ${MY_DOWNLOAD_PATH} pandoc-${PANDOC_VERSION}/linux/debian/x86_64/pandoc-citeproc -d ${PANDOC_PREFIX}/static
+    DEPENDS "${MY_DOWNLOAD_PATH}")
diff --git a/lib/config/app-config.js b/lib/config/app-config.js
new file mode 100644
index 0000000..7def321
--- /dev/null
+++ b/lib/config/app-config.js
@@ -0,0 +1,118 @@
+/*
+ * app-config.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+var config = require('../config/config');
+var schema = require('../config/schema');
+var fsutil = require('../core/fsutil');
+var map = require('../core/map');
+var paths = require('../core/paths');
+var path = require('path');
+var _ = require('underscore');
+var Q = require('q');
+var events = require('events');
+
+exports.AppConfig = AppConfig;
+function AppConfig(eventBus) {
+  this.$cache = map.create();
+  var self = this;
+
+  eventBus.on('vacantSched', function(appKey){
+    delete self.$cache[appKey];
+  });
+}
+
+(function() {
+  this.readConfig_p = function(appSpec) {
+    var key = appSpec.getKey();
+    if (_.has(this.$cache, key)){
+      // We already have this appSpec cached, just return it.
+      return this.$cache[key];
+    }
+
+    var appDir = appSpec.appDir;
+
+    var self = this;
+
+    return findConfig_p(appDir)
+    .then(function(confPath){
+      var schemaPath = paths.projectFile('config/shiny-server-rules.config');
+
+      var conf = config.read_p(
+        confPath, 
+        schemaPath);
+
+      // Cache the response for later use.
+      self.$cache[key] = Q.fcall(function(){ return conf;});
+      return conf;
+    }, function(err){
+      // No app configuration file found. Return null.
+      self.$cache[key] = Q.fcall(function(){ return null;});
+      return null;
+    });
+  }
+  
+  /**
+   * Supplement the base config with the app-specific config.
+   * 
+   */ 
+  this.addLocalConfig = function(baseConfig, appConfig){
+    if (!appConfig || _.size(appConfig) == 0){
+      return baseConfig;
+    }
+
+    // Filter appConfig to only have whitelisted properties. (Don't allow a local
+    // app to overwrite runAs, for instance).
+    appConfig = _.pick(appConfig, 'appDefaults', 'scheduler');
+
+    baseConfig.settings = merge(appConfig, baseConfig.settings);
+
+    return baseConfig;
+  }
+
+}).call(AppConfig.prototype);
+
+
+/**
+ * Check to see if there's a configuration file in the given app directory,
+ * if so, parse it.
+ * @param appDir The base directory in which the application is hosted.
+ * @return A promise resolving to the path of the application-specific
+ *   configuration file
+ */
+function findConfig_p(appDir){
+  var filePath = path.join(appDir, ".shiny_app.conf");
+  return fsutil.safeStat_p(filePath)
+  .then(function(stat){
+    if (stat && stat.isFile()){
+      return (filePath);
+    }
+    throw new Error('Invalid app configuration file.');
+  });
+}
+
+exports.merge = merge;
+function merge(src, target){
+  for (var el in src){
+    // if it's an object (and not an array), recurse.
+    if (typeof src[el] === 'object' && 
+        Object.prototype.toString.call(src[el]) !== '[object Array]') {
+      if (!_.has(target, el)){
+        // This object doesn't yet exist in target. Create an empty placeholder
+        target[el] = map.create();
+      }
+      target[el] = merge(src[el], target[el]);
+    } else{
+      target[el] = src[el];
+    }
+  }
+  return target;
+}
diff --git a/lib/config/config.js b/lib/config/config.js
new file mode 100644
index 0000000..2326701
--- /dev/null
+++ b/lib/config/config.js
@@ -0,0 +1,224 @@
+/*
+ * config.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+var assert = require('assert');
+var fs = require('fs');
+var util = require('util');
+var Q = require('q');
+var _ = require('underscore');
+var parser = require('./parser');
+var schema = require('./schema');
+
+exports.read_p = read_p;
+/**
+ * Reads the given config file. The returned promise resolves to the ConfigNode
+ * object at the root of the config file.
+ *
+ * @param {String} path File path for the config file.
+ * @returns {Promise} Promise that resolves to ConfigNode.
+ */
+function read_p(path, schemaPath) {
+  return Q.nfcall(fs.readFile, path, 'utf8')
+  .then(function(cdata) {
+    return Q.nfcall(fs.readFile, schemaPath, 'utf8')
+    .then(function(sdata) {
+      return schema.applySchema(parse(cdata, path), parse(sdata, schemaPath));
+    });
+  });
+}
+
+/**
+ * Reads the given config file.
+ *
+ * @param {String} path File path for the config file.
+ * @returns {ConfigNode} The root ConfigNode.
+ */
+exports.readSync = readSync;
+function readSync(path, schemaPath) {
+  var configData = parse(fs.readFileSync(path, 'utf8'), path);
+  var schemaData = parse(fs.readFileSync(schemaPath, 'utf8'), schemaPath);
+  return schema.applySchema(configData, schemaData);
+}
+
+exports.parse = parse;
+function parse(data, pathHint) {
+  var root = new parser.ConfigParser(data, pathHint).parse();
+  return directiveToConfig(root, null);
+}
+
+function directiveToConfig(directive, parent) {
+  var depth = parent ? parent.depth + 1 : 0;
+  var node = new ConfigNode(parent, directive.getName(), directive.getArgs(),
+      directive.getPosition(), depth);
+  node.children = _.map(directive.children, function(child) {
+    return directiveToConfig(child, node);
+  });
+  return node;
+};
+
+exports.ConfigNode = ConfigNode;
+/**
+ * A ConfigNode object represents a node in the config tree. Each non-root node
+ * has a parent node, a name, and zero or more argument strings. Each node has
+ * zero or more children.
+ *
+ * Examples of simple nodes:
+ * user jcheng;
+ * autouser on;
+ *
+ * Examples of nodes with children:
+ * location /users {
+ *   autouser on;
+ * }
+ *
+ * Nested nodes:
+ * server {
+ *   port 8080;
+ *   location / {
+ *     appdir /var/www-shiny/;
+ *   }
+ * }
+ *
+ * @constructor
+ */
+function ConfigNode(parent, name, args, position, depth) {
+  this.parent = parent;
+  this.name = name;
+  this.args = args;
+  this.values = null;
+  this.position = position;
+  this.depth = depth;
+  this.children = [];
+}
+(function() {
+  /**
+   * Return a single node with the given criteria, if it exists. This node's
+   * children are searched first. If no match is found and inherit is true,
+   * then recurse to the parent, if any.
+   *
+   * @param {*} criteria The node criteria. See search().
+   * @param {Boolean} [inherit] Whether to search ancestor nodes too.
+   * @returns {ConfigNode} A matching ConfigNode object, or null.
+   */
+  this.getOne = function(criteria, inherit) {
+    if (arguments.length < 2 || typeof inherit === 'undefined')
+      inherit = true;
+
+    var predicate = this.$makePredicate(criteria);
+
+    var result = _.find(this.children, predicate);
+    if (result)
+      return result;
+    if (inherit && this.parent)
+      return this.parent.getOne(predicate, true);
+    else
+      return null;
+  };
+
+  /**
+   * Convenience function that behaves the same as
+   * getOne(criteria, [inherit]).values
+   * except in the case that getOne finds nothing (or its values field is
+   * falsy), in which case an empty object is returned.
+   */
+  this.getValues = function(criteria, inherit) {
+    if (arguments.length < 2 || typeof inherit === 'undefined')
+      inherit = true;
+
+    var node = this.getOne(criteria, inherit);
+    return (node && node.values) ? node.values : {};
+  };
+
+  /**
+   * Return all the nodes that match the given criteria AND are direct children
+   * of this config node.
+   *
+   * @param {*} criteria The node criteria. See search().
+   * @returns {Array} Array of matching ConfigNode objects.
+   */
+  this.getAll = function(criteria) {
+    return _.filter(this.children, this.$makePredicate(criteria));
+  };
+
+  /**
+   * Convenience function that finds a node using the same behavior as getOne()
+   * and, if it exists and has one or more arguments, returns the first arg.
+   * If no node is found or it has no arguments then the defaultValue is
+   * returned.
+   *
+   * @param {*} criteria The node criteria. See search().
+   * @param {String} [defaultValue] Value to return if no suitable match is found.
+   * @param {boolean} [inherit] True if ancestor nodes should be included when
+   *   finding a matching node.
+   * @returns {String} The string value that was requested, or defaultValue.
+   */
+  this.getValue = function(criteria, defaultValue, inherit) {
+    if (arguments.length < 3 || typeof inherit === 'undefined')
+      inherit = true;
+    var match = this.getOne(criteria, inherit);
+    if (!match)
+      return defaultValue;
+    if (match.args.length < 1)
+      return defaultValue;
+    return match.args[0];
+  };
+
+  /**
+   * Finds ALL nodes from here and all descendant nodes that match the
+   * criteria. If includeSelf, then this node is itself eligible for inclusion
+   * in the results (if it matches the criteria, that is).
+   *
+   * @param {*} criteria The criteria for searching. This can be one of several
+   *   types:
+   *   * true - Match all nodes
+   *   * false - Match no nodes
+   *   * String - Match all nodes whose name is === to this string
+   *   * Function - Matches all nodes that this function maps to a truthy value
+   *   * Regexp - Matches all nodes whose names test true for this regex
+   * @param {boolean} includeSelf If false, this node will not be included in
+   *   the results even if it matches the criteria.
+   * @returns {Array} All the matching nodes (depth-first, preorder).
+   */
+  this.search = function(criteria, includeSelf, postOrder) {
+    var predicate = this.$makePredicate(criteria);
+
+    var results = [];
+    if (includeSelf && !postOrder && predicate(this))
+      results.push(this);
+    results = _.flatten(results.concat(
+      _.map(this.children, function(child) {
+        return child.search(predicate, true, postOrder);
+      })
+    ));
+    if (includeSelf && postOrder && predicate(this))
+      results.push(this);
+    return results;
+  };
+
+  this.$makePredicate = function(criteria) {
+    if (criteria === true)
+      return function() { return true; };
+    if (criteria === false)
+      return function() { return false; };
+
+    switch (typeof criteria) {
+      case 'function':
+        return criteria;
+      case 'string':
+        return function(node) { return node.name === criteria; };
+      case 'object':
+        return function(node) { return criteria.test(node.name); };
+      default:
+        throw new Error('Unexpected criteria type ' + (typeof criteria));
+    }
+  };
+}).call(ConfigNode.prototype);
diff --git a/lib/config/lexer.js b/lib/config/lexer.js
new file mode 100644
index 0000000..043d4d9
--- /dev/null
+++ b/lib/config/lexer.js
@@ -0,0 +1,315 @@
+/*
+ * lexer.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+var assert = require('assert');
+var util = require('util');
+var _ = require('underscore');
+
+/**
+ * Token type constants
+ */
+var TT = exports.TT = {
+  WORD: 'TT_WORD',
+  OPENBRACE: 'TT_OPENBRACE',
+  CLOSEBRACE: 'TT_CLOSEBRACE',
+  TERM: 'TT_TERM',
+  WS: 'TT_WS',
+  COMMENT: 'TT_COMMENT',
+  EOD: 'TT_EOD'
+};
+
+/**
+ * Character classification constants
+ */
+var c_ = 1;
+var C_ALPHA = c_++;
+var C_DIGIT = c_++;
+var C_OPENBRACE = c_++;    // {
+var C_CLOSEBRACE = c_++;   // }
+var C_SEMICOLON = c_++;    // ;
+var C_HASH = c_++;         // #
+var C_SQUOTE = c_++;       // '
+var C_DQUOTE = c_++;       // "
+var C_BACKSLASH = c_++;
+var C_EOL = c_++;
+var C_WS = c_++;           // whitespace
+var C_CONTROL = c_++;
+var C_OTHER = c_ + 100;
+var C_EOD = c_ + 101;
+delete c_;
+
+function Position(line, col, offset, file) {
+  this.line = line;
+  this.col = col;
+  this.offset = offset;
+  this.file = file;
+}
+(function() {
+  this.toString = function() {
+    if (this.file)
+      return util.format("%s:%d:%d", this.file, this.line, this.col);
+    else
+      return util.format("at line %d, column %d", this.line, this.col);
+  }
+}).call(Position.prototype);
+
+function Token(type, content, position) {
+  this.type = type;
+  this.content = content;
+  this.position = position;
+}
+
+(function() {
+}).call(Token.prototype);
+
+exports.Lexer = Lexer;
+/**
+ * @param {String} data The config data.
+ * @param {String} [pathHint] The path to the config data's file (for error
+ *   logging/debugging purposes).
+ */
+function Lexer(data, pathHint) {
+  this.$data = data.replace(/\r/, ''); // Strip carriage return
+  this.$pathHint = pathHint;
+
+  // Pointer into this.$data that indicates where the next character is.
+  // This value must never be changed except by $nextChar(), which knows
+  // how to keep $line and $col in sync.
+  this.$pos = 0;
+  // 1-based row and column
+  this.$line = 1;
+  this.$col = 1;
+}
+
+(function() {
+  /**
+   * Return the next token, or null if done.
+   *
+   * Will throw if a control character or unterminated quoted string is
+   * encountered. The exception will have a `position` property that is an
+   * object with line and col properties (1-based).
+   */
+  this.nextToken = function() {
+    // This is the position of the next character, and thus the starting
+    // position of the new token
+    var tokenStart = new Position(
+        this.$line, this.$col, this.$pos, this.$pathHint);
+
+    // String value indicating the logical token value--may differ from the
+    // source characters (for example quoting/escaping are performed)
+    var content;
+    // One of the TT.XXX constants
+    var type = 0;
+
+    // General approach: We can tell by peeking at the first character what
+    // kind of token we're going to match. Then call $matchXXX methods that
+    // know how to consume those tokens.
+    try {
+      var c = this.$peekChar();
+      switch (this.$classify(c)) {
+        case C_ALPHA:
+        case C_DIGIT:
+        case C_OTHER:
+        case C_BACKSLASH:
+          type = TT.WORD;
+          content = this.$matchWord();
+          break;
+        case C_SEMICOLON:
+          type = TT.TERM;
+          content = this.$nextChar();
+          break;
+        case C_OPENBRACE:
+          type = TT.OPENBRACE;
+          content = this.$nextChar();
+          break;
+        case C_CLOSEBRACE:
+          type = TT.CLOSEBRACE;
+          content = this.$nextChar();
+          break;
+        case C_EOL:
+          type = TT.WS;
+          content = this.$nextChar();
+          break;
+        case C_WS:
+          type = TT.WS;
+          content = this.$matchWhitespace();
+          break;
+        case C_HASH:
+          type = TT.COMMENT;
+          content = this.$matchComment();
+          break;
+        case C_SQUOTE:
+          type = TT.WORD;
+          content = this.$matchQuoted();
+          break;
+        case C_DQUOTE:
+          type = TT.WORD;
+          content = this.$matchQuoted();
+          break;
+        case C_CONTROL:
+          throw new Error('Invalid character detected');
+        case C_EOD:
+          type = TT.EOD;
+          content = '';
+          break;
+      }
+
+      assert(type);
+
+      return new Token(type, content, tokenStart);
+
+    } catch (ex) {
+      ex.position = tokenStart;
+      throw ex;
+    }
+  };
+
+  this.$matchWord = function() {
+    var result = "";
+    while (true) {
+      switch (this.$classify(this.$peekChar())) {
+        case C_ALPHA:
+        case C_DIGIT:
+        case C_OTHER:
+        case C_BACKSLASH:
+          result += this.$nextChar();
+          break;
+        default:  // Note: Includes C_EOD
+          return result;
+      }
+    }
+  };
+
+  this.$matchWhitespace = function() {
+    // Eat one or more whitespace characters
+    return this.$consumeRegex(/[ \t\r]+/g);
+  };
+
+  this.$matchComment = function() {
+    // Discard the leading #
+    var hash = this.$nextChar();
+    assert.equal(hash, '#');
+    // Eat # to end of line
+    return this.$consumeRegex(/[^\n]*/g);
+  };
+
+  this.$matchQuoted = function() {
+    var value = '';
+    var nextChar;
+    var quot = this.$nextChar();
+    assert(quot === '"' || quot === "'", '$matchQuoted called incorrectly');
+    while (true) {
+      // Eat all characters that aren't special with respect to quoted strings;
+      // that includes everything but single/double quotes and backslash
+      value += this.$consumeRegex(/[^'"\\]*/g);
+      switch (this.$peekChar()) {
+        case '"':
+        case "'":
+          if (this.$peekChar() !== quot) {
+            // False alarm, this is not the same kind of quote character that
+            // was used to start this quoted string
+            value += this.$nextChar();
+          } else {
+            // All done--discard the close quote and return!
+            this.$nextChar();
+            return value;
+          }
+          break;
+        case '\\':
+          this.$nextChar(); // consume backslash
+          if (this.$eod())
+            throw new Error('Closing ' + quot + ' character was not found');
+          else
+            value += this.$nextChar();
+          break;
+        case null:
+          throw new Error('Closing ' + quot + ' character was not found');
+      }
+    }
+  };
+
+  this.$consumeRegex = function(re) {
+    re.lastIndex = this.$pos;
+    var m = re.exec(this.$data);
+    assert(m);
+    assert.equal(m.index, this.$pos);
+    this.$advanceBy(m[0].length);
+    return m[0];
+  }
+
+  /**
+   * Get the next char and advance the cursor.
+   */
+  this.$nextChar = function() {
+    if (this.$eod())
+      return null;
+    var c = this.$data.charAt(this.$pos++);
+    if (c === '\n') {
+      this.$line++;
+      this.$col = 1;
+    } else {
+      this.$col++;
+    }
+    return c;
+  };
+
+  /**
+   * Skip over n characters.
+   *
+   * This uses a slow implementation of calling $nextChar() n times rather than
+   * just `this.$pos += n` because $line and $col need to be kept in sync with
+   * $pos. There are faster ways than this of course, but would take more code
+   * than seems worth it.
+   */
+  this.$advanceBy = function(n) {
+    assert(n >= 0);
+
+    var c;
+    for (var i = 0; i < n; i++) {
+      c = this.$nextChar();
+      assert.notEqual(c, null);
+    }
+  };
+
+  /**
+   * Get the next character but don't advance the cursor.
+   */
+  this.$peekChar = function() {
+    if (this.$eod())
+      return null;
+    return this.$data.charAt(this.$pos);
+  };
+
+  // Return true if we're done
+  this.$eod = function() {
+    return this.$pos < 0 || this.$pos >= this.$data.length;
+  };
+
+  var re = /^(?:([a-zA-Z])|([0-9])|(\{)|(\})|(;)|(#)|(')|(")|(\\)|(\n)|([ \t\r])|([\x00-\x08\x0B-\x0C\x0E-\x1F]))$/
+  var classMap = [C_ALPHA, C_DIGIT, C_OPENBRACE, C_CLOSEBRACE, C_SEMICOLON, C_HASH, C_SQUOTE, C_DQUOTE, C_BACKSLASH, C_EOL, C_WS, C_CONTROL];
+  /**
+   * Classify the character. Let's try not to call it on every character
+   * in the stream.
+   */
+  this.$classify = function(char) {
+    if (char === null)
+      return C_EOD;
+    var m = re.exec(char);
+    if (!m)
+      return C_OTHER;
+    var capture = _.indexOf(_.map(_.rest(m), function(x) {return !!x}), true);
+    assert(capture >= 0 && capture < classMap.length, "Unexpected capture value " + capture);
+    return classMap[capture];
+  };
+
+}).call(Lexer.prototype);
+
diff --git a/lib/config/parser.js b/lib/config/parser.js
new file mode 100644
index 0000000..0eecf49
--- /dev/null
+++ b/lib/config/parser.js
@@ -0,0 +1,152 @@
+/*
+ * parser.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+var assert = require('assert');
+var fs = require('fs');
+var util = require('util');
+var _ = require('underscore');
+var lexer = require('./lexer');
+
+var TT = lexer.TT;
+
+function Directive(nameToken, args, parent, children) {
+  this.nameToken = nameToken;
+  this.args = args;
+  this.children = children;
+}
+
+(function() {
+  this.getName = function() {
+    return this.nameToken ? this.nameToken.content : null;
+  };
+  this.getArgs = function() {
+    return _.pluck(this.args, 'content');
+  };
+  this.getPosition = function() {
+    return this.nameToken ? this.nameToken.position : null;
+  };
+}).call(Directive.prototype);
+
+exports.ConfigParser = ConfigParser;
+function ConfigParser(data, pathHint) {
+  this.$tokens = new lexer.Lexer(data, pathHint);
+}
+
+(function() {
+
+  this.parse = function() {
+    try {
+      var rootScope = new Directive(null, [], null, []);
+      this.$parseChildDirectives(rootScope, true);
+      return rootScope;
+    } catch(err) {
+      if (err.position) {
+        err.message += ' (' + err.position.toString() + ')';
+      }
+      throw err;
+    }
+  };
+
+  this.$parseChildDirectives = function(parent, atRoot) {
+    while (true) {
+      var nextDirective = this.$parseOne(parent);
+      if (nextDirective === null) {
+        // The data ended.
+        if (atRoot)
+          return;
+        else {
+          this.$throw(new Error('The scope was never closed'),
+            parent.getPosition());
+        }
+      } else if (nextDirective === false) {
+        // The scope closed.
+        if (atRoot) {
+          this.$throw(new Error('Unexpected } character encountered'));
+        }
+        return;
+      } else {
+        parent.children.push(nextDirective);
+      }
+    }
+  };
+
+  this.$parseOne = function(parent) {
+    var nameToken;
+    var token;
+
+    while (true) {
+      nameToken = this.$nextToken();
+      // No more data.
+      if (nameToken.type === TT.EOD)
+        return null;
+      // Scope closed.
+      if (nameToken.type === TT.CLOSEBRACE)
+        return false;
+      // Statement terminated (it was empty); get the next one.
+      if (nameToken.type === TT.TERM)
+        continue;
+      if (nameToken.type !== TT.WORD) {
+        this.$throw(new Error('Unexpected token encountered: ' +
+            nameToken.content));
+      }
+
+      // The nameToken is TT.WORD, we're good.
+      var directive = new Directive(nameToken, [], parent, []);
+
+      // Now let's look for args (words), '{', or ';'
+      while (true) {
+        token = this.$nextToken();
+        switch (token.type) {
+          case TT.WORD:
+            directive.args.push(token);
+            continue;
+          case TT.OPENBRACE:
+            this.$parseChildDirectives(directive, false);
+            return directive;
+          case TT.CLOSEBRACE:
+            this.$throw(
+                new Error('Unexpected } character (did you leave a semicolon off the previous directive?)'));
+          case TT.TERM:
+            return directive;
+          case TT.EOD:
+            this.$throw(
+                new Error('Unterminated directive; did you leave off a semicolon?'),
+                    nameToken.position);
+          default:
+            assert(false, 'Programmer error: Forgot to handle ' + token.type);
+        }
+      }
+    }
+  };
+
+  this.$nextToken = function() {
+    while (true) {
+      var t = this.$tokens.nextToken();
+      switch (t.type) {
+        // Filter out whitespace and comments
+        case TT.WS:
+        case TT.COMMENT:
+          continue;
+        default:
+          this.$lastKnownPos = t.position;
+          return t;
+      }
+    }
+  };
+
+  this.$throw = function(err, position) {
+    position = position || this.$lastKnownPos;
+    if (position)
+      err.position = position;
+    throw err;
+  };
+}).call(ConfigParser.prototype);
\ No newline at end of file
diff --git a/lib/config/schema.js b/lib/config/schema.js
new file mode 100644
index 0000000..d7a7ad0
--- /dev/null
+++ b/lib/config/schema.js
@@ -0,0 +1,230 @@
+/*
+ * schema.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+var assert = require('assert');
+var fs = require('fs');
+var util = require('util');
+var _ = require('underscore');
+var map = require('../core/map.js');
+
+exports.applySchema = applySchema;
+function applySchema(configRoot, schemaRoot) {
+  var rules = map.create();
+  _.each(schemaRoot.children, function(child) {
+    rules[child.name] = new ConfigSchemaRule(child);
+  });
+
+  _.each(configRoot.search(true, false), function(node) {
+    var rule = rules[node.name];
+    if (!rule)
+      throwForNode(node, new Error('Unknown directive "' + node.name + '"'));
+    rule.applyTo(node);
+  });
+
+  return configRoot;
+}
+
+function ConfigSchemaRule(configNode) {
+  this.$name = configNode.name;
+  var at = configNode.getOne('at', false);
+  if (!at) {
+    throwForNode(configNode, new Error('Bad schema: Missing "at" directive'));
+  }
+  this.$at = _.map(at.args, function(arg) {
+    return (arg === '$') ? null : arg;
+  });
+  this.$precludes = configNode.getOne('precludes', false);
+  if (this.$precludes)
+    this.$precludes = this.$precludes.args;
+  this.$maxcount = configNode.getValue('maxcount', 1/0);
+  this.$params = _.map(configNode.getAll('param'), function(n) {
+    return new ConfigSchemaParam(n);
+  });
+  this.$minParams = _.reduce(this.$params, function(memo, p) {
+    return memo + ((p.optional || p.vararg) ? 0 : 1);
+  }, 0);
+  this.$maxParams = _.reduce(this.$params, function(memo, p) {
+    return memo + (p.vararg ? 1/0 : 1);
+  }, 0);
+  if (_.uniq(_.pluck(this.$params, 'name')).length != this.$params.length) {
+    throwForNode(configNode, new Error('Not all param names were unique'));
+  }
+
+  // Make sure we have required, optional, vararg, in that order
+  _.reduce(this.$params, function(memo, p) {
+    if (p.required) {
+      if (memo > 0)
+        throwForNode(configNode,
+            new Error('Required parameter defined after non-required'));
+      return 0;
+    }
+    if (p.optional) {
+      if (memo > 1)
+        throwForNode(configNode,
+            new Error('Optional parameter defined after vararg'));
+      return 1;
+    }
+    if (p.vararg) {
+      if (memo == 2)
+        throwForNode(configNode,
+            new Error('Only one vararg can be defined per directive'));
+      return 2;
+    }
+  }, 0);
+
+}
+(function() {
+  this.applyTo = function(node) {
+    this.$validateLocation(node);
+    this.$validateAndTransformArgs(node);
+    this.$validateMaxCount(node);
+    this.$validatePrecludes(node);
+  };
+  this.$validateLocation = function(node) {
+    if (!_.contains(this.$at, node.parent.name)) {
+      throwForNode(node, new Error(
+          "The " + node.name + " directive can't be used here"));
+    }
+  };
+  this.$validateAndTransformArgs = function(node) {
+    var argc = node.args.length;
+    if (argc < this.$minParams || argc > this.$maxParams) {
+      var expected = (this.$minParams === this.$maxParams) ? 
+          this.$minParams :
+          util.format("%d to %d", this.$minParams, this.$maxParams);
+      var fewOrMany = argc < this.$minParams ? "few" : "many";
+      var message = util.format('"%s" directive had too %s arguments; ', 
+          node.name, fewOrMany);
+      message += util.format('expected %s, found %d', expected, argc);
+      throwForNode(node, new Error(message));
+    }
+
+    var paramsLeft = _.clone(this.$params);
+    var typedArgs = map.create();
+    _.each(node.args, function(arg) {
+      var param = paramsLeft[0];
+      if (!param.vararg) {
+        paramsLeft.shift();
+        typedArgs[param.name] = param.convert(arg);
+      } else {
+        if (!typedArgs[param.name])
+          typedArgs[param.name] = [];
+        typedArgs[param.name].push(param.convert(arg));
+      }
+    });
+    _.each(paramsLeft, function(param) {
+      // Apply default values, if any
+      if (param.optional && param.hasDefaultValue) {
+        typedArgs[param.name] = param.defaultValue;
+      }
+    });
+    node.values = typedArgs;
+  };
+  this.$validateMaxCount = function(node) {
+    var siblings = node.parent.children;
+
+    if (this.$maxcount > siblings.length)
+      return;
+
+    var count = 0;
+    for (var i = 0; i < siblings.length; i++) {
+      if (siblings[i].name === node.name)
+        count++;
+      if (siblings[i] === node)
+        break;
+    }
+    if (count > this.$maxcount) {
+      throwForNode(node, new Error(
+          util.format('%s directive appears too many times', node.name)));
+    }
+  };
+  this.$validatePrecludes = function(node) {
+    _.each(this.$precludes, function(precluded) {
+      if (node.parent.getOne(precluded, false)) {
+        throwForNode(node.parent, new Error(
+            util.format('%s and %s directives are mutually exclusive',
+                node.name, precluded)));
+      }
+    });
+  };
+}).call(ConfigSchemaRule.prototype);
+
+exports.ConfigSchemaParam = ConfigSchemaParam;
+function ConfigSchemaParam(paramNode) {
+  assert.equal(paramNode.name, 'param');
+  if (paramNode.args.length < 3)
+    throwForNode(paramNode, new Error("Invalid schema specification"));
+  this.type = paramNode.args[0];
+  this.name = paramNode.args[1].replace(/^\[?([^[\].]+)\]?(...)?$/, '$1');
+  this.desc = paramNode.args[2];
+  this.optional = /^\[/.test(paramNode.args[1]);
+  this.vararg = /\.{3}$/.test(paramNode.args[1]);
+  this.required = !this.optional && !this.vararg;
+
+  this.convert = ConfigTypes[this.type];
+  if (!this.convert)
+    throwForNode(paramNode,
+        new Error('Unknown type "' + this.type + '". ' + 
+            'Available types are ' + _.keys(ConfigTypes).join(', ')));
+
+  // Default value
+  if (paramNode.args.length > 3) {
+    if (!this.optional)
+      throwForNode(paramNode,
+          new Error('Only optional parameters can have default values'));
+
+    try {
+      this.defaultValue = this.convert(paramNode.args[3]);
+      this.hasDefaultValue = true;
+    } catch (err) {
+      throwForNode(paramNode, err);
+    }
+  }
+}
+
+var ConfigTypes = exports.ConfigTypes = {
+  Boolean: ToBoolean,
+  Integer: ToInteger,
+  Float: ToFloat,
+  String: _.identity
+};
+
+function ToBoolean(s) {
+  if (/^(true|yes|on)$/im.test(s))
+    return true;
+  else if (/^(false|no|off)$/im.test(s))
+    return false;
+  throw new Error('"' + s + '" is not a valid Boolean value');
+}
+
+function ToInteger(s) {
+  if (/^(0[x])[0-9a-f]+$/im.test(s))
+    return parseInt(s);
+  if (/^-?\d+$/m.test(s))
+    return parseInt(s);
+  throw new Error('"' + s + '" is not a valid Integer value');
+}
+
+function ToFloat(s) {
+  // .0, 0., 0.0, but not .
+  if (/^(\d*\.\d+)|(\d+\.?\d*)$/.test(s))
+    return parseFloat(s);
+  throw new Error('"' + s + '" is not a valid Float value');
+}
+
+exports.throwForNode = throwForNode;
+function throwForNode(node, err) {
+  assert(typeof err.message !== 'undefined');
+  if (node.position)
+    err.message += ' (' + node.position.toString() + ')';
+  throw err;
+}
diff --git a/lib/core/connect-util.js b/lib/core/connect-util.js
new file mode 100644
index 0000000..d240834
--- /dev/null
+++ b/lib/core/connect-util.js
@@ -0,0 +1,25 @@
+/*
+ * connect-util.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+var url = require('url');
+
+exports.filterByRegex = filterByRegex;
+function filterByRegex(pathRegex, app) {
+  return function(req, res, next) {
+    var parsedUrl = url.parse(req.url);
+    if (pathRegex.test(parsedUrl.path)) {
+      app(req, res, next);
+    } else {
+      next();
+    }
+  };
+}
\ No newline at end of file
diff --git a/lib/core/errors.js b/lib/core/errors.js
new file mode 100644
index 0000000..3e71b27
--- /dev/null
+++ b/lib/core/errors.js
@@ -0,0 +1,22 @@
+var util = require('util')
+
+/**
+ * Concept derived from Dustin Senos.
+ * http://dustinsenos.com/articles/customErrorsInNode
+**/
+var AbstractError = function (msg, constr) {
+  Error.captureStackTrace(this, constr || this);
+  this.message = msg || 'Error';
+};
+util.inherits(AbstractError, Error);
+AbstractError.prototype.name = 'Abstract Error';
+
+var OutOfCapacityError = function (msg) {
+  OutOfCapacityError.super_.call(this, msg, this.constructor);
+}
+util.inherits(OutOfCapacityError, AbstractError);
+OutOfCapacityError.prototype.name = 'Out Of Capacity Error';
+
+module.exports = {
+  OutOfCapacity: OutOfCapacityError
+}
\ No newline at end of file
diff --git a/lib/core/fsutil.js b/lib/core/fsutil.js
new file mode 100644
index 0000000..b8499ce
--- /dev/null
+++ b/lib/core/fsutil.js
@@ -0,0 +1,133 @@
+/*
+ * fsutil.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+var fs = require('graceful-fs');
+var Q = require('q');
+var util = require('util');
+var posix = require('../../build/Release/posix');
+
+exports.directoryExistsSync = directoryExistsSync;
+/**
+ * Returns true if the path exists and is a directory, false otherwise.
+ */
+function directoryExistsSync(path) {
+  try {
+    var stats = fs.statSync(path);
+    return stats.isDirectory();
+  } catch (e) {
+    if (e.code === 'ENOENT')
+      return false;
+    throw e;
+  }
+}
+
+exports.safeTail_p = safeTail_p;
+/**
+ * Safely read the tail end of the given file, if it exists, and return as a
+ * string ("" is returned if the file cannot be read for any reason).
+ *
+ * If the file length is less than or equal to maxlen, the whole file is
+ * returned. Otherwise, maxlen bytes are taken from the end and decoded
+ * according to the specified encoding, then everything up to and including
+ * the first line feed character is removed (if there is no line feed character
+ * then nothing is removed).
+ *
+ * @param {String} path - The path of the file to read (or falsy to return "").
+ * @param {Number} maxlen - The number of bytes to read, at maximum.
+ * @param {String} [encoding] - The encoding to use to decode the bytes;
+ *   defaults to "utf8".
+ */
+function safeTail_p(path, maxlen, encoding) {
+  if (!path)
+    return Q.resolve('');
+
+  var defer = Q.defer();
+
+  encoding = encoding || 'utf8';  
+  
+  Q.nfcall(fs.open, path, 'r')
+  .then(function(fd) {
+    return Q.nfcall(fs.fstat, fd)
+    .then(function(stat) {
+      var len = stat.size;
+      if (len == 0)
+        return '';
+
+      var index = Math.max(0, len - maxlen);
+      var bytesToRead = len - index;
+      var buffer = new Buffer(bytesToRead);
+
+      return Q.nfcall(fs.read, fd, buffer, 0, bytesToRead, index)
+      .then(function(result) {
+        var pos = 0;
+        // Skip UTF-8 continuation bytes
+        while (pos < result[0] && buffer.readUInt8(pos) >= 0x80) {
+          pos++;
+        }
+        var str = buffer.toString(encoding, pos, result[0]);
+        if (index != 0) {
+          str = str.substring(str.indexOf('\n') + 1, str.length);
+        }
+        return str;
+      });
+    })
+    .fin(function() {
+      fs.close(fd, function(err) {
+        if (err)
+          logger.error("Couldn't close safeTail_p fd: " + err.message);
+      });
+    });
+  })
+  .then(
+    function(val) {
+      defer.resolve(val);
+    },
+    function(err) {
+      logger.error(err);
+      defer.resolve('');
+    }
+  )
+  .done();
+
+  return defer.promise;
+}
+
+exports.safeStat_p = safeStat_p;
+function safeStat_p(path) {
+  var deferred = Q.defer();
+  fs.stat(path, function(err, stat) {
+    if (err)
+      deferred.resolve(null);
+    else
+      deferred.resolve(stat);
+  })
+  return deferred.promise;
+}
+
+var F_WRLCK = 1;
+var SEEK_SET = 0;
+
+exports.createPidFile = createPidFile;
+function createPidFile(path) {
+  var fd = fs.openSync(path, 'a+', 0600);
+  if (!posix.acquireRecordLock(fd, F_WRLCK, SEEK_SET, 0, 0)) {
+    return false;
+  }
+
+  var buf = new Buffer(process.pid + '', 'ascii');
+  fs.truncateSync(fd, 0);
+  var pos = 0;
+  while (pos < buf.length)
+    pos += fs.writeSync(fd, buf, pos, buf.length - pos, pos);
+
+  return true;
+}
diff --git a/lib/core/log.js b/lib/core/log.js
new file mode 100644
index 0000000..f137f3c
--- /dev/null
+++ b/lib/core/log.js
@@ -0,0 +1,16 @@
+/*
+ * log.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+var log4js = require('log4js');
+
+global.logger = log4js.getLogger('shiny-server');
+global.logger.setLevel(process.env.SHINY_LOG_LEVEL || 'INFO');
diff --git a/lib/core/map.js b/lib/core/map.js
new file mode 100644
index 0000000..830a0a8
--- /dev/null
+++ b/lib/core/map.js
@@ -0,0 +1,33 @@
+var _ = require('underscore');
+
+/*
+ * map.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+exports.create = create;
+/**
+ * Create a map (similar to {} but without the problems described in
+ * http://www.devthought.com/2012/01/18/an-object-is-not-a-hash/).
+ */
+function create() {
+  return Object.create(null);
+}
+
+exports.compact = compact;
+/**
+ * Return a copy of object x with null or undefined "own" properties removed.
+ */
+function compact(x) {
+  function shouldDrop(key) {
+    return typeof(x[key]) === 'undefined' || x[key] === null;
+  }
+  return _.omit(x, _.filter(_.keys(x), shouldDrop))
+}
\ No newline at end of file
diff --git a/lib/core/paths.js b/lib/core/paths.js
new file mode 100644
index 0000000..61a22f0
--- /dev/null
+++ b/lib/core/paths.js
@@ -0,0 +1,21 @@
+/*
+ * paths.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+var path = require('path');
+
+var projectRoot = path.normalize(path.join(((__dirname)), '../../'));
+exports.projectRoot = projectRoot;
+
+exports.projectFile = projectFile;
+function projectFile(filename) {
+  return path.normalize(path.join(projectRoot, filename));
+}
diff --git a/lib/core/permissions.js b/lib/core/permissions.js
new file mode 100644
index 0000000..e4bcecd
--- /dev/null
+++ b/lib/core/permissions.js
@@ -0,0 +1,39 @@
+/*
+ * permissions.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+var posix = require('../../build/Release/posix');
+
+exports.isSuperuser = isSuperuser;
+/**
+ * Return true if the user is root
+ */
+function isSuperuser() {
+  return process.getuid() == 0;
+}
+
+var processUser = {};
+exports.getProcessUser = getProcessUser;
+function getProcessUser() {
+  var uid = process.getuid();
+  if (!processUser.name || processUser.uid !== uid) {
+    processUser = {
+      uid: uid,
+      name: (posix.getpwuid(uid) || {}).name
+    }
+  }
+  return processUser.name;
+}
+
+exports.canRunAs = canRunAs;
+function canRunAs(user) {
+  return isSuperuser() || user === getProcessUser();
+}
\ No newline at end of file
diff --git a/lib/core/qutil.js b/lib/core/qutil.js
new file mode 100644
index 0000000..c20a93c
--- /dev/null
+++ b/lib/core/qutil.js
@@ -0,0 +1,177 @@
+/*
+ * qutil.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+var assert = require('assert');
+var Q = require('q');
+var _ = require('underscore');
+
+/**
+ * Adds an eat() method to promises that swallows exceptions.
+ */
+Q.makePromise.prototype.eat = function() {
+  this.fail(function(err) {});
+};
+
+exports.serialized = serialized;
+/**
+ * Takes a function that returns a promise, and returns a wrapped version that
+ * ensures that multiple invocations are serialized (only one at a time).
+ */
+function serialized(func) {
+  var currentPromise = null;
+  var wrapped = function() {
+    var self = this;
+    var args = arguments;
+    if (currentPromise) {
+      return currentPromise.fin(function() {
+        return wrapped.apply(self, args);
+      });
+    }
+
+    currentPromise = func.apply(self, args);
+    
+    currentPromise
+    .fin(function() {
+      currentPromise = null;
+    })
+    .eat();
+    
+    return currentPromise;
+  };
+  return wrapped;
+}
+
+exports.withTimeout_p = withTimeout_p;
+/**
+ * If timeoutMs elapses before promise resolves, the returned promise will
+ * fail with an Error whose code is 'ETIMEOUT'. Otherwise the returned promise
+ * passes through the resolution/failure of the original promise.
+ */
+function withTimeout_p(timeoutMs, promise, label) {
+  label = label || 'Operation';
+  var defer = Q.defer();
+  promise.then(
+    function(value) {
+      defer.resolve(value);
+    },
+    function(err) {
+      defer.reject(err);
+    }
+  );
+  setTimeout(function() {
+    var err = new Error(label + ' timed out');
+    err.code = 'ETIMEOUT';
+    defer.reject(err);
+  }, timeoutMs);
+  return defer.promise;
+}
+
+exports.forEachPromise_p = forEachPromise_p;
+/**
+ * Starting at the beginning of the array, pass an element to the iterator,
+ * which should return a promise. If the promise resolves, test the value
+ * using the accept function. If accepted, that value is the result. If not
+ * accepted, move on to the next array element.
+ *
+ * If any of the iterator-produced promises fails, then reject the overall
+ * promise with that error.
+ *
+ * If the end of the array is reached without any values being accepted,
+ * defaultValue is the result.
+ */
+function forEachPromise_p(array, iterator, accept, defaultValue) {
+  var deferred = Q.defer();
+  var i = 0;
+  function tryNext() {
+    if (i >= array.length) {
+      // We've reached the end of the list--give up
+      deferred.resolve(defaultValue);
+    }
+    else {
+      try {
+        // Try the next item in the list
+        iterator(array[i++])
+        .then(
+          function(result) {
+            // If the promise returns a result, see if it is acceptable; if
+            // so, we're done, otherwise move on to the next item in the list
+            if (accept(result)) {
+              deferred.resolve(result);
+            } else {
+              tryNext();
+            }
+          },
+          function(err) {
+            deferred.reject(err);
+          }
+        );
+      } catch(ex) {
+        deferred.reject(ex);
+      }
+    }
+  }
+  tryNext();
+  return deferred.promise;
+}
+
+exports.fapply = fapply;
+/**
+ * Apply a synchronous function but wrap the result or exception in a promise.
+ * Why doesn't Q.apply work this way??
+ */
+function fapply(func, object, args) {
+  try {
+    return Q.resolve(func.apply(object, args));
+  } catch(err) {
+    return Q.reject(err);
+  }
+}
+
+/**
+ * Pass in a synchronous function, returns a promise-returning version
+ */
+exports.wrap = wrap;
+function wrap(func) {
+  return function() {
+    try {
+      return Q.resolve(func.apply(this, arguments));
+    } catch(err) {
+      return Q.reject(err);
+    }
+  }
+}
+
+exports.map_p = map_p;
+/**
+ * Pass in a collection and a promise-returning function, and map_p will
+ * do a sequential map operation and resolve to the results.
+ */
+function map_p(collection, func_p) {
+  if (collection.length === 0)
+    return Q.resolve([]);
+
+  var results = [];
+
+  var lastFunc = Q.resolve(true);
+
+  _.each(collection, function(el, index) {
+    lastFunc = lastFunc.then(function() {
+      return func_p(collection[index])
+      .then(function(result) {
+	results[index] = result;
+	return results;
+      });
+    });
+  });
+
+  return lastFunc;
+}
diff --git a/lib/core/re-quote.js b/lib/core/re-quote.js
new file mode 100644
index 0000000..1a2820d
--- /dev/null
+++ b/lib/core/re-quote.js
@@ -0,0 +1,17 @@
+/*
+ * re-quote.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+ module.exports = function(str){
+  // List derived from  http://stackoverflow.com/questions/399078/what-special-characters-must-be-escaped-in-regular-expressions
+  return str.replace(/[.\^$*+?()[{\\|\-\]]/g, '\\$&');
+ }
\ No newline at end of file
diff --git a/lib/core/render.js b/lib/core/render.js
new file mode 100644
index 0000000..a4ff337
--- /dev/null
+++ b/lib/core/render.js
@@ -0,0 +1,146 @@
+/*
+ * render.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+var fs = require('fs');
+var Handlebars = require('handlebars');
+var _ = require('underscore');
+var map = require('../core/map');
+var paths = require('../core/paths');
+var path = require('path');
+
+var templateCache = map.create()
+
+exports.sendPage = sendPage;
+function sendPage(response, status, title, options) {
+  var config = _.extend({
+    contentType: 'text/html; charset=utf-8',
+    title: title,
+    vars: {},
+    headers: {},
+    template: 'error'
+  }, options);
+
+  var headers = _.extend({
+      'Content-Type': config.contentType
+  }, config.headers);
+
+  var tmplt;
+  if (templateCache[options.templateDir + config.template]){
+    tmplt = templateCache[options.templateDir + config.template];
+  } else{
+    // Follow the hierarchy down to the base page to see if any exist.
+    var cascade = config.template.split('-');
+    
+    // Stores a reference to the best (most precise) candidate we've found in 
+    // the custom template directory (if any).
+    var bestCustom;
+
+    // Keep arrays of the templates for which we're going to check in order of
+    // preference. CustomTemplates will take precence.
+    var customTemplates = [];
+    var providedTemplates = [];
+    
+    while (cascade.length > 0){
+      var tmpName = cascade.join('-') + '.html';
+
+      if (options.templateDir){
+        var customTemplate = path.join(options.templateDir || '', tmpName);
+        customTemplates.unshift(customTemplate);
+      }
+      
+      var providedTemplate = paths.projectFile('templates/' + tmpName);
+      providedTemplates.unshift(providedTemplate);
+
+      cascade.pop();
+    }
+
+    // Create a single array in order of least to most desirable
+    var candidates = providedTemplates.concat(customTemplates);
+
+    // Now check to see which of the files exist.
+    while(candidates.length > 0 && !tmplt){
+      var cand = candidates.pop();
+      if (fs.existsSync(cand)){
+        tmplt = fs.readFileSync(cand, 'utf-8');
+      }
+    }
+
+    if (!tmplt){
+      throw new Error("No template available for type: " + config.template);
+    }
+
+    // Cache the retrieved template.
+    templateCache[options.templateDir + config.template] = tmplt;
+  }
+
+  var template = Handlebars.compile(tmplt);
+
+  response.writeHead(status, headers);
+  response.end(template(_.extend({title: title}, config.vars)));
+}
+
+exports.sendClientConsoleMessage = sendClientConsoleMessage;
+/**
+ * Sends the given string to the client, where it will be printed to the
+ * JavaScript error console (if available).
+ */
+function sendClientConsoleMessage(ws, message) {
+  var msg = JSON.stringify({
+    custom: {
+      console: message
+    }
+  });
+  ws.write(msg);
+}
+
+exports.sendClientAlertMessage = sendClientAlertMessage;
+/**
+ * Sends the given string to the client, where it will be displayed as a
+ * JavaScript alert message.
+ */
+function sendClientAlertMessage(ws, alert) {
+  var msg = JSON.stringify({
+    custom: {
+      alert: alert
+    }
+  });
+  ws.write(msg);
+}
+
+exports.error404 = error404;
+// Send a 404 error response
+function error404(req, res, templateDir) {
+  sendPage(res, 404, 'Page not found', {
+    template: 'error-404',
+    templateDir: templateDir,
+    vars: {
+      message: "Sorry, but the page you requested doesn't exist."
+    }
+  });
+}
+
+exports.errorAppOverloaded = errorAppOverloaded;
+// Send a 503 error response
+function errorAppOverloaded(req, res, templateDir) {
+  sendPage(res, 503, 'Too Many Users', {
+    template: 'error-503-users',
+    templateDir: templateDir,    
+    vars: {
+      message: "Sorry, but this application has exceeded its quota of concurrent users. Please try again later."
+    }
+  });
+}
+
+exports.flushCache = flushCache;
+function flushCache(){
+  templateCache = map.create();
+}
\ No newline at end of file
diff --git a/lib/core/shutdown.js b/lib/core/shutdown.js
new file mode 100644
index 0000000..0430883
--- /dev/null
+++ b/lib/core/shutdown.js
@@ -0,0 +1,13 @@
+/*
+ * shutdown.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+exports.shuttingDown = false;
diff --git a/lib/events/simple-event-bus.js b/lib/events/simple-event-bus.js
new file mode 100644
index 0000000..4fea0bf
--- /dev/null
+++ b/lib/events/simple-event-bus.js
@@ -0,0 +1,25 @@
+/*
+ * simple-event-bus.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+var events = require('events');
+var map = require('../core/map');
+
+var SimpleEventBus = function(){
+  events.EventEmitter.call(this);
+}
+SimpleEventBus.prototype.__proto__ = events.EventEmitter.prototype;
+module.exports = SimpleEventBus;
+
+(function(){
+
+}).call(SimpleEventBus.prototype);
\ No newline at end of file
diff --git a/lib/main.js b/lib/main.js
new file mode 100755
index 0000000..c7b56ea
--- /dev/null
+++ b/lib/main.js
@@ -0,0 +1,349 @@
+#!/usr/bin/env node
+/*
+ * main.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+require('./core/log');
+var crypto = require('crypto');
+var fs = require('fs');
+var path = require('path');
+var url = require('url');
+var util = require('util');
+var client_sessions = require('client-sessions');
+var express = require('express');
+var morgan = require('morgan');
+var optimist = require('optimist');
+var Q = require('q');
+var _ = require('underscore');
+var Handlebars = require('handlebars');
+var connect_util = require('./core/connect-util');
+var fsutil = require('./core/fsutil');
+var paths = require('./core/paths');
+var qutil = require('./core/qutil');
+var render = require('./core/render');
+var shutdown = require('./core/shutdown');
+var proxy_http = require('./proxy/http');
+var proxy_sockjs = require('./proxy/sockjs');
+var router = require('./router/router')
+var config_router = require('./router/config-router');
+var Server = require('./server/server');
+var SchedulerRegistry = require('./scheduler/scheduler-registry');
+var SimpleScheduler = require('./scheduler/simple-scheduler');
+var TcpTransport = require('./transport/tcp').Transport;
+var UnixSocketTransport = require('./transport/unix-socket').Transport;
+var SimpleEventBus = require('./events/simple-event-bus');
+var LocalConfigRouter = require('./router/local-config-router');
+var SquashRunAsRouter = require('./router/squash-run-as-router.js');
+
+// Version strings
+(function() {
+  if (fs.existsSync(paths.projectFile('VERSION'))) {
+    SHINY_SERVER_VERSION = fs.readFileSync(
+      paths.projectFile('VERSION'),
+      { encoding: 'ascii' }
+    ).trim();
+  } else {
+    var packageInfo =
+      JSON.parse(fs.readFileSync(paths.projectFile('package.json')));
+    SHINY_SERVER_VERSION = packageInfo['version'].trim() + '.0';
+  }
+})();
+
+var shinyVersionString = 'Shiny Server v' + SHINY_SERVER_VERSION;
+var nodeVersionString = 'Node.js ' + process.version;
+var versionString = shinyVersionString + ' (' + nodeVersionString + ')';
+
+// --version
+if (optimist.argv.version) {
+  console.log(shinyVersionString);
+  console.log(nodeVersionString);
+  process.exit(0);
+}
+
+logger.info(versionString);
+
+var unlinkPidFile = function() {};
+if (optimist.argv.pidfile) {
+  var pidfile = optimist.argv.pidfile;
+  if (typeof(pidfile) !== 'string') {
+    console.error('ERROR: Argument is required for pidfile');
+    process.exit(1);
+  }
+  pidfile = path.resolve(pidfile);
+  logger.info('Using pidfile ' + pidfile);
+  if (!fsutil.createPidFile(pidfile)) {
+    console.error('ERROR: Could not lock pidfile. Is another instance of ' +
+                  'Shiny Server running?');
+    process.exit(1);
+  }
+  unlinkPidFile = function() {
+    fs.unlink(pidfile, function(err) {
+      logger.warn('Error deleting pidfile: ' + err);
+    });
+  };
+  process.on('exit', function() {
+    unlinkPidFile();
+  });
+} else {
+  logger.debug('No pidfile requested');
+}
+
+var configFilePath = '/etc/shiny-server/shiny-server.conf';
+if (optimist.argv._.length >= 1) {
+  configFilePath = path.resolve(optimist.argv._[0]);
+}
+
+logger.info('Using config file "' + configFilePath + '"');
+
+// A simple router function that does nothing but respond "OK". Can be used for
+// load balancer health checks, for example.
+function ping(req, res) {
+  if (url.parse(req.url).pathname == '/ping') {
+    res.writeHead(200, {'Content-Type': 'text/plain'});
+    res.end('OK');
+    return true;
+  }
+  return false;
+}
+
+// We'll need an eventBus...
+var eventBus = new SimpleEventBus();
+
+// ...routers...
+var indirectRouter = new router.IndirectRouter(new router.NullRouter());
+var localConfigRouter = new LocalConfigRouter(
+    new router.RestartRouter(
+      router.join(indirectRouter, ping)
+    ), eventBus
+  );
+var metarouter = new SquashRunAsRouter(localConfigRouter);
+
+// ...a scheduler registry...
+var schedulerRegistry = new SchedulerRegistry(eventBus);
+
+// ...a transport (connects this process with worker procs)...
+var transport = new TcpTransport();
+
+// ...an HTTP proxy...
+var shinyProxy = new proxy_http.ShinyProxy(
+  metarouter,
+  schedulerRegistry
+);
+
+var clientSessionMiddleware = client_sessions({
+  secret: crypto.randomBytes(16).toString('hex')
+});
+
+// Setup a placeholder middleware function until we can create one after 
+// parsing the config.
+var sockjsServer = false;
+var sockjsHandler = function(req, res){
+  return false;
+}
+
+var app = express();
+app.use(clientSessionMiddleware);
+app.use(function(req, res, next) {
+  if (!sockjsHandler(req, res))
+    next();
+});
+// Set up 
+var assets = express.static(paths.projectFile('assets'),
+                            {maxAge:86400000}); // one day
+var ssjAssets = express.static(paths.projectFile('node_modules/shiny-server-client/dist/'),
+                               {maxAge:86400000}); // one day
+
+app.use(connect_util.filterByRegex(
+  /\b__assets__\/.+/,
+  function (req, res, next) {
+    function next404() { render.error404(req, res); }
+    // Need to trim off the directory, as if we got here via a connect route
+    req.originalUrl = req.originalUrl || req.url;
+    req.url = req.url.replace(/^.*\b__assets__\//, '');
+    if (req.url === 'shiny-server-client.js' || req.url === 'shiny-server-client.min.js') {
+      ssjAssets(req, res, next404);
+    } else {
+      assets(req, res, next404);
+    }
+  }
+));
+app.use(shinyProxy.httpListener);
+
+// Now create a server and hook everything up.
+var server = new Server();
+server.on('connection', function(socket) {
+  // Close HTTP connections that haven't seen traffic in 45 seconds.
+  //
+  // SockJS sends a heartbeat every 25s so as long as we wait significantly
+  // longer than that to timeout, we shouldn't need to worry about closing
+  // active connections.
+  socket.setTimeout(45 * 1000);
+});
+server.on('request', _.bind(app.handle, app));
+server.on('error', function(err) {
+  logger.error('HTTP server error (' + err.listenKey + '): ' + err.message);
+});
+server.on('clientError', function(err) {
+  // ETIMEDOUT, EPIPE, ECONNRESET, "This socket is closed." are all very
+  // very common occurrences.
+  logger.debug('HTTP client error (' + err.listenKey + '): ' + err.message);
+});
+
+server.on('upgrade', function(request, socket, head) {
+  if (!sockjsServer){
+    logger.warn("Can't route sockJS traffic until configuration file is parsed.");
+    res.end();
+    return;
+  }
+  clientSessionMiddleware(request, null, function() {
+    sockjsHandler.upgrade(request, socket, head);
+  });
+});
+
+
+var requestLogger = null;
+server.on('request', function(req, res) {
+  if (requestLogger)
+    requestLogger(req, res);
+});
+
+var loadConfig_p = qutil.serialized(function() {
+  return config_router.createRouter_p(configFilePath, schedulerRegistry)
+  .then(function(configRouter) {
+    indirectRouter.setRouter(configRouter);
+    localConfigRouter.setAppOverride(configRouter.getAppOverride());
+    server.setAddresses(configRouter.getAddresses());
+    schedulerRegistry.setTransport(transport);
+    transport.setSocketDir(configRouter.socketDir);
+
+    // Create SockJS server
+    sockjsServer = proxy_sockjs.createServer(metarouter, schedulerRegistry);
+    sockjsHandler = sockjsServer.middleware();
+
+    return createLogger_p(configRouter.accessLogSpec)
+    .then(function(logfunc) {
+      requestLogger = logfunc;
+      logger.trace('Config loaded');
+    });
+  })
+  .fail(function(err) {
+    if (err.code === 'ENOENT') {
+      logger.error('Error loading config: File "' + configFilePath + '" does not exist');
+    } else {
+      logger.error('Error loading config: ' + err.message);
+    }
+  });
+});
+
+loadConfig_p().done();
+
+function createLogger_p(logSpec) {
+  if (!logSpec || !logSpec.path) {
+    logger.debug('No access log configured');
+    return Q.resolve(null);
+  }
+
+  logger.debug('Access log path: ' + logSpec.path);
+
+  try {
+    var stream = fs.createWriteStream(logSpec.path, {flags: 'a'});
+    var next = function(){};
+    var format = logSpec.format;
+    if (format === "default") {
+      format = "combined";  // "default" is deprecated in morgan
+    }
+    var log = morgan(format, {stream: stream});
+    return Q.resolve(function(req, res) {
+      log(req, res, next);
+    });
+    
+  } catch (err) {
+    return Q.reject(err);
+  }
+  return Q.nfcall(fs.open, logSpec.path, 'a', 0660)
+  .then(function(fd) {
+  })
+  .fail(function(err) {
+    logger.error('Error creating access log: ' + err.message);
+    return null;
+  });
+}
+
+// On SIGHUP (i.e., initctl reload), reload configuration
+process.on('SIGHUP', function() {
+  logger.info('SIGHUP received, reloading configuration');
+  render.flushCache();
+  loadConfig_p().done();
+});
+
+// On SIGUSR1, write worker registry contents to log
+process.on('SIGUSR1', function() {
+  schedulerRegistry.dump();
+});
+
+// Clean up worker processes on shutdown
+
+var needsCleanup = true;
+function gracefulShutdown() {
+  // Sometimes the signal gets sent twice. No idea why.
+  if (!needsCleanup)
+    return;
+
+  // On SIGINT/SIGTERM (i.e. normal termination) we wait a second before
+  // exiting so the clients can all be notified
+  shutdown.shuttingDown = true;
+  try {
+    server.destroy();
+  } catch (err) {
+    logger.error('Error while attempting to stop server: ' + err.message);
+  }
+  logger.info('Shutting down worker processes (with notification)');
+  schedulerRegistry.shutdown();
+  needsCleanup = false;
+  setTimeout(process.exit, 500);
+}
+
+function lastDitchShutdown() {
+  if (!needsCleanup)
+    return;
+  // More-violent shutdown (e.g. uncaught exception), no chance to notify
+  // workers as timers won't be scheduled
+  shutdown.shuttingDown = true;
+  logger.info('Shutting down worker processes');
+  schedulerRegistry.shutdown();
+}
+
+process.on('SIGINT', gracefulShutdown);
+process.on('SIGTERM', gracefulShutdown);
+process.on('SIGABRT', gracefulShutdown);
+process.on('uncaughtException2', gracefulShutdown);
+process.on('uncaughtException', function(err) {
+  logger.error('Uncaught exception: ' + err);
+  logger.error(err.stack);
+  process.emit('uncaughtException2', err);
+  throw err;
+});
+process.on('exit', lastDitchShutdown);
+
+if (optimist.argv.memlog) {
+  var memstatsPath = 'mem-' + process.pid + '.csv';
+  logger.info('Writing memory log to ' + memstatsPath);
+  var memstatsStream = fs.createWriteStream(memstatsPath, {
+    encoding: 'utf-8',
+    mode: 0664
+  });
+  memstatsStream.write('rss,heapTotal,heapUsed\n');
+  setInterval(function() {
+    var snapshot = process.memoryUsage();
+    memstatsStream.write(snapshot.rss + ',' + snapshot.heapTotal + ',' + snapshot.heapUsed + '\n');
+  }, 2000);
+}
diff --git a/lib/proxy/errorcode.js b/lib/proxy/errorcode.js
new file mode 100644
index 0000000..026bc46
--- /dev/null
+++ b/lib/proxy/errorcode.js
@@ -0,0 +1,77 @@
+/*
+ * errorcode.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+/**
+
+We use WebSocket/SockJS close codes to convey to shiny-server-client
+and post-0.13 versions of Shiny whether they should attempt to
+reconnect to existing sessions or transparently start new sessions,
+respectively.
+
+|-----------------------------------------------------|
+|              | Restart           | No restart       |
+|-----------------------------------------------------|
+| Reconnect    | 46xx or !wasClean | (never)          |
+| No reconnect | 47xx              | 45xx or wasClean |
+|-----------------------------------------------------|
+
+46xx - Disruption; attempt to reconnect and/or restart
+Examples of 46xx:
+- Any kind of transient networking problem (!wasClean)
+
+47xx - Bad session; don't attempt to reconnect, but can restart
+Examples of 47xx:
+- Server restart
+- Protocol violation, or any other unexpected state
+
+45xx - Normal, clean closure; don't attempt to reconnect or restart
+Examples of 45xx:
+- Idle connection timeout
+- Process exit with error (don't automatically restart)
+
+**/
+
+module.exports = {
+  // It is more important than the baseNum values be *unique*
+  // and *stable between releases* than for them to be in a
+  // logical grouping/order. (i.e. please don't insert a new
+  // code in the middle of the list and renumber all the
+  // subsequent codes).
+  ACCESS_DENIED    : closureCode(0, false, false),
+  OUT_OF_CAPACITY  : closureCode(1, false, false),
+  SHUTTING_DOWN    : closureCode(2, false, true),
+  APP_EXIT         : closureCode(3, false, false),
+  BAD_PROTOCOL     : closureCode(4, false, true),
+  BAD_IDENTIFIER   : closureCode(5, false, true),
+  RETIRED          : closureCode(6, false, false)
+};
+
+function closureCode(baseNum, allowReconnect, allowRestart) {
+  if (baseNum < 0 || baseNum > 99) {
+    logger.warn("Invalid closure code base number: " + baseNum);
+    baseNum = Math.abs(baseNum) % 100;
+  }
+
+  if (!allowReconnect && !allowRestart) {
+    return baseNum + 4500;
+  } else if (allowReconnect && allowRestart) {
+    return baseNum + 4600;
+  } else if (!allowReconnect && allowRestart) {
+    return baseNum + 4700;
+  } else {
+    logger.warn("Invalid closure code flag combination (" +
+      allowReconnect + ", " + allowRestart + ")"
+    );
+    return 4000 + baseNum;
+  }
+}
diff --git a/lib/proxy/http.js b/lib/proxy/http.js
new file mode 100644
index 0000000..ce0e111
--- /dev/null
+++ b/lib/proxy/http.js
@@ -0,0 +1,330 @@
+/*
+ * http.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+var http = require('http');
+var util = require('util');
+var http_proxy = require('http-proxy');
+var qs = require('qs');
+var Q = require('q');
+var _ = require('underscore');
+var OutOfCapacityError = require('../core/errors').OutOfCapacity;
+var AppSpec = require('../worker/app-spec');
+var fsutil = require('../core/fsutil');
+var render = require('../core/render');
+var shutdown = require('../core/shutdown');
+
+var error404 = render.error404;
+var errorAppOverloaded = render.errorAppOverloaded;
+
+// Send a 500 error response
+function error500(req, res, errorText, detail, templateDir, consoleLogFile, appSpec) {
+  fsutil.safeTail_p(consoleLogFile, 8192)
+  .fail(function(consoleLog) {
+    return;
+  })
+  .then(function(consoleLog) {
+    render.sendPage(res, 500, 'An error has occurred', {
+      template: 'error-500',
+      templateDir: templateDir,
+      vars: {
+        message: errorText,
+        detail: detail,
+        console: (appSpec && appSpec.settings.appDefaults.sanitizeErrors) ? null : consoleLog
+      }
+    });
+  })
+  .fail(function(err) {
+    logger.error('Failed to render error 500 page: ' + err.message);
+  })
+  .fin(function() {
+    try {
+      res.end();
+    } catch(err) {
+      logger.error('Error while cleaning up response: ' + err);
+    }
+  })
+  .done();
+}
+
+// All the http_proxy events we know about. They have a frustrating habit of
+// changing these around.
+var knownEvents = exports.knownEvents = ['open', 'close', 'proxyReq', 'proxyRes', 'proxySocket',
+  'start', 'end', 'error',
+  // Known, but undocumented
+  'econnreset',
+  // Undocumented and unknown
+  'proxyReqWs'
+];
+
+exports.ShinyProxy = ShinyProxy;
+/**
+ * ShinyProxy combines HTTP and websocket proxying duties, and manages these
+ * for not one but many back-end HTTP/websocket servers (one for each running
+ * application worker process).
+ *
+ * @constructor
+ * @param {SchedulerRegistry} schedulerRegistry - Implementation of a scheduler
+ *   registry, which launches and manages scheduler processes in response to
+ *   AppSpec requests.
+ * @param {Router} router - Implementation of a router, which is capable
+ *   of mapping a URL to an AppSpec.
+ */
+function ShinyProxy(router, schedulerRegistry) {
+  // The HTTP server for our proxy. For each request that comes in:
+  // - Is it a SockJS request? If so, SockJS handlers deal with it before we
+  //   ever get to our server function.
+  // - Ask the router to figure out what application the request should be
+  //   directed to. (If no application matches, respond with error 404.)
+  // - Ask the worker registry for an AppWorkerHandle for the given AppSpec.
+  //   An AppWorkerHandle represents a handle to a specific R process. (If
+  //   error, respond with error 500.)
+  // - If the AppWorkerHandle has no proxy field, create it.
+  // - Proxy the request.
+  this.httpListener = function(req, res) {
+
+    // Pause the request so we don't miss any events before we
+    // asynchronously hook things up.
+    req.pause();
+    
+    // Figure out what application this URL belongs to, who it should run as,
+    // where the log file goes, etc.
+    router.getAppSpec_p(req, res)
+    .then(function(appSpec) {
+
+      if (!req.socket.writable) {
+        logger.debug("Socket closed, httpListener aborting");
+        return;
+      }
+      if (appSpec === true) {
+        // Router fully handled the request
+        return;
+      }
+      if (!appSpec) {
+        error404(req, res, req.templateDir);
+        return;
+      }
+
+      var worker = qs.parse(req._parsedUrl.query).w;
+
+      if (worker){
+        logger.trace("Using worker #" + JSON.stringify(worker));
+      }
+
+      if (req.url.indexOf(appSpec.prefix) != 0) {
+        logger.error('Bad router returned invalid prefix: "' + appSpec.prefix + '" for URL "' + req.url + '"');
+        error404(req, res, appSpec.settings.templateDir);
+        return;
+      }
+
+      req.url = req.url.substring(appSpec.prefix.length);
+      if (req.url.indexOf('/') != 0)
+        req.url = '/' + req.url;
+      // TODO: Also strip path members
+
+      //TODO: clean this up. This should be a part of the promise chain, but
+      // when returning an error, it would just crash as an unhandled excptn
+      // and never make it into the .fail().
+      var wrk;
+      try{
+        // Extract the non-param portion of the URL
+        wrk = schedulerRegistry.getWorker_p(appSpec, req.url.match(/([^\?]+)(\?.*)?/)[1], 
+        worker)
+      }
+      catch(err){
+        if (err instanceof OutOfCapacityError){
+          logger.trace("Out of capacity, serving 503 for request '" + req.url + "'");
+          errorAppOverloaded(req, res, appSpec.settings.templateDir);
+          return;
+        }
+        throw err;
+      }
+
+      var acquired = false;
+      // Launch a new, or reuse an existing, R process for this app.
+      wrk
+      .then(function(appWorkerHandle) {
+
+        if (!req.socket.writable) {
+          logger.debug("Socket closed, ignoring appWorkerHandle and returning");
+          return;
+        }
+
+        // Ensures that the worker process will not be reaped while we use it.
+        appWorkerHandle.acquire('http');
+        acquired = true;
+
+        // Save a reference to the appWorkerHandle on the response so that we
+        // can call appWorkerHandle.release() when the request ends.
+        res.appWorkerHandle = appWorkerHandle;
+
+        if (!appWorkerHandle.proxy) {
+          // Cache an HTTP proxy right on the appWorkerHandle.
+          appWorkerHandle.proxy = createHttpProxy(appWorkerHandle);
+          appWorkerHandle.exitPromise
+          .fin(function(status) {
+            appWorkerHandle.proxy.close();
+            logger.trace(appWorkerHandle.endpoint.ToString() + ' proxy closed');
+          })
+          .eat();
+        }
+        
+        // We add this listener before trying to proxy, so it will be handled 
+        // first, meaning we won't yet have nulled out the req object.
+        req.on('error', function(err) {
+          logger.error('Error during proxy: ' + err.message);
+          error500(req, res, 'An error occurred while transferring data from the application.',
+              err.message, appSpec.settings.templateDir, appWorkerHandle.logFilePath, appSpec);
+        });
+
+        logger.trace('Proxying request: ' + req.url);
+        req.headers['shiny-shared-secret'] = appWorkerHandle.endpoint.getSharedSecret();
+        // Remember that we will null out req and res in the proxy, so be careful
+        // trying to subscribe to events on them after we've started this proxy.
+        appWorkerHandle.proxy.web(req, res);
+        req.resume();
+      })
+      .fail(function(err) {
+        if (acquired){
+          cleanupResponse(res);
+        }
+        logger.info('Error getting worker: ' + err);
+        if (err.code === 'ENOTFOUND')
+          error404(req, res, appSpec.settings.templateDir);
+        else {
+          error500(req, res, 'The application failed to start.', err.message,
+              appSpec.settings.templateDir, err.consoleLogFile, appSpec);
+        }
+      })
+      .done();
+    })
+    .fail(function(err){
+      error500(req, res, 'Invalid application configuration.', err.message, 
+        req.templateDir);
+    })
+    .done();
+  };
+
+  /**
+   * Very important that this is called on every response that we've even
+   * begun attempting to proxy; it ensures that the R process can be
+   * released when idle.
+   */
+  function cleanupResponse(response) {
+    if (response.appWorkerHandle){
+      response.appWorkerHandle.release('http');
+    }
+    delete response.appWorkerHandle;
+  }
+
+  // Create an HTTP proxy object for the given socket; we need one of these per
+  // active socket. node-http-proxy has a RoutingProxy that provides a more
+  // suitable interface (you can specify a different target for each call to
+  // proxyRequest) but the implementation caches every proxy it creates
+  // forever, so that's much worse than just doing it ourselves.
+  function createHttpProxy(appWorkerHandle) {
+
+    var proxy = new http_proxy.createProxyServer({
+      target: appWorkerHandle.endpoint.getHttpProxyTarget()
+    });
+
+    // Intercept http_proxy events, for debugging/warning purposes.
+    var origEmit = proxy.emit;
+    proxy.emit = function(type) {
+      logger.trace("http_proxy event: " + type);
+      if (!knownEvents.includes(type)) {
+        logger.warn("Unexpected http_proxy event: " + type);
+      }
+      origEmit.apply(this, arguments);
+    };
+
+    // Once we switched the connection between node and the R workers from TCP
+    // to Unix domain sockets, keepalive management from the node side seemed
+    // to stop working. The behavior we observed (using our manual loadtest.js
+    // script) was that node would request to keep the connection open, but not
+    // actually reuse the connection. This would lead the node process to hit
+    // the limit of open files (defaults to 1024 on Linux and only 256 on Mac).
+    //
+    // This fixes the problem by rewriting the Connection header in both
+    // directions while proxying; always close the connection to the target,
+    // while respecting the original connection header from the client.
+    proxy.on('proxyReq', function(proxyReq, req, res, options) {
+      req.keepalive = isKeepalive(req);
+      stripConnectionHeaders(proxyReq);
+      proxyReq.setHeader("connection", "close");
+    });
+    proxy.on('proxyRes', function(proxyRes, req, res) {
+      res.setHeader("connection", req.keepalive ? 'keep-alive' : 'close');
+    });
+
+    proxy.on('error', function(err, req, res) {
+      // This happens when an error occurs during request proxying, for example
+      // if the upstream server drops the connection.
+      error500(req, res, 'The application exited unexpectedly.', err.message,
+          req.templateDir, appWorkerHandle.logFilePath, appWorkerHandle.appSpec);
+      cleanupResponse(res);
+    });
+    // This happens if the proxy-to-target request is disconnected. One easy way
+    // to trigger this, as of this writing, is to have an app.R that has a
+    // Sys.sleep(120) in ui.R.
+    proxy.on('econnreset', function(err, req, res) {
+      cleanupResponse(res);
+    });
+    proxy.on('end', function(req, res) {
+      cleanupResponse(res);
+    });
+    return proxy;
+  }
+};
+
+(function() {
+
+}).call(ShinyProxy.prototype);
+
+
+// Determine if keepalive is desired by the client that generated this request
+function isKeepalive(req) {
+  var conn = req.headers.connection;
+  if (typeof(conn) === 'undefined' || conn === null)
+    conn = '';
+  conn = conn.trim().toLowerCase();
+
+  if (/\bclose\b/i.test(conn))
+    return false;
+  if (/\bkeep-alive\b/i.test(conn))
+    return true;
+
+  // No Connection header. Default to keepalive for 1.1, non for 1.0.
+  if (req.httpVersionMajor < 1 || req.httpVersionMinor < 1) {
+    return false;
+  } else {
+    return true;
+  }
+}
+
+// Per RFC 2616 section 14.10:
+//
+// HTTP/1.1 proxies MUST parse the Connection header field before a
+// message is forwarded and, for each connection-token in this field,
+// remove any header field(s) from the message with the same name as the
+// connection-token.
+function stripConnectionHeaders(req) {
+  var conn = req.getHeader("connection");
+  if (typeof(conn) === 'undefined' || conn === null)
+    return;
+
+  conn.split(/\s+/g).forEach(function(header) {
+    if (header.length === 0)
+      return;
+    req.removeHeader(header);
+  });
+  req.removeHeader("connection");
+}
diff --git a/lib/proxy/multiplex.js b/lib/proxy/multiplex.js
new file mode 100644
index 0000000..cc0d02b
--- /dev/null
+++ b/lib/proxy/multiplex.js
@@ -0,0 +1,194 @@
+/*
+ * multiplex.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+var assert = require('assert');
+var events = require('events');
+var util = require('util');
+var _ = require('underscore');
+var path = require('path');
+
+// MultiplexSocket sits on top of a SockJS server connection and raises
+// "connection" events with logical SockJS server connections (channels)
+// that look and behave like normal SockJS server connections, but share
+// a single SockJS connection. Channels cannot outlive (or predate, for
+// that matter) their underlying connections.
+//
+// Each message that comes across the physical connection will be tagged
+// with the "id" of its logical connection. These ids need only be unique
+// within the scope of each MultiplexSocket.
+//
+// Messages are strings formatted like this:
+//
+// channelId|method|data
+//
+// A message can be one of three types (methods):
+//
+// - "o": Open a new channel @ channelId (data is request URL)
+// - "c": Close the channel (data has {code, reason})
+// - "m": Channel message (data is the message)
+//
+// See shiny-server.js for the corresponding client code.
+module.exports = MultiplexSocket;
+function MultiplexSocket(conn) {
+  events.EventEmitter.call(this);
+  this.$conn = conn; // The single true SockJS connection that was opened
+  this.$channels = {}; // The list of currently active channels
+
+  var self = this;
+  conn.on('close', function() {
+    // The physical connection has gone away, we need to notify anyone
+    // who is using any active channel.
+    _.each(_.values(self.$channels), function(channel) {
+      channel._destroy();
+    });
+    self.$channels = {};
+  });
+  conn.on('data', function(message) {
+    // Message received from physical connection; parse and dispatch it
+    message = parseMultiplexData(message);
+    if (!message) {
+      logger.warn('Invalid multiplex packet received');
+      conn.close();
+      return;
+    }
+
+    var id = message.id;
+    var method = message.method;
+    var payload = message.payload;
+
+    var channel = self.$channels[id];
+    if (!channel) {
+      if (method === 'o') {
+        // Handle the open URL as relative to the connection URL. We do this
+        // to support a Proxy that might manipulate URLs for all HTTP requests,
+        // but would be unable to massage the URL we specify in our WS JSON,
+        // so we'll use relative references..
+        if (!payload){
+          // The parent request will just pass an empty string.
+          payload = conn.url;
+        } else{
+					// It must be a relative path, compute the absolute path for SockJS
+          var parUrl = conn.url;
+          parUrl = parUrl.replace(/\/__sockjs__\/.*$/, "/");
+          payload = path.join(parUrl, payload);
+        }
+
+        // It's a request to open a new channel with the given id.
+        // We want the channel to look a lot like a real SockJS connection,
+        // so let's copy the properties from the actual connection. We'll
+        // overwrite the `url` though, to use the channel's URL.
+        var properties = _.extend(_.pick(conn, connectionProps), {url: payload});
+        channel = new MultiplexChannel(id, conn, properties);
+        self.$channels[id] = channel;
+        self.emit('connection', channel);
+      } else {
+        // TODO: ...what? Close conn?
+      }
+    }
+    else {
+      // The channel exists
+
+      if (method === 'c') {  // The client closed the channel
+        channel._destroy(payload.code, payload.reason);
+      } else if (method === 'm') {  // The client sent a message
+        channel.emit('data', payload);
+      }
+    }
+  });
+}
+util.inherits(MultiplexSocket, events.EventEmitter);
+
+(function() {
+}).call(MultiplexSocket.prototype);
+
+// Fake SockJS connection that can be multiplexed over a real SockJS
+// connection.
+function MultiplexChannel(id, conn, properties) {
+  events.EventEmitter.call(this);
+  this.$id = id;  // The channel id
+  this.$conn = conn;  // The underlying SockJS connection
+  _.extend(this, properties);  // Apply extra properties to this object
+  this.readyState = this.$conn.readyState;
+  assert(this.readyState === 1); // copied from the (definitely active) conn
+}
+util.inherits(MultiplexChannel, events.EventEmitter);
+
+(function() {
+  this.write = function(message) {
+    if (this.readyState === 1)
+      this.$conn.write(formatMessage(this.$id, message));
+  };
+  this.close = function(code, reason) {
+    if (this.readyState === 1 && this.$conn.readyState === 1)
+      this.$conn.write(formatCloseEvent(this.$id, code, reason));
+    this._destroy(code, reason);
+  };
+  this.end = function() {
+    this.close();
+  };
+  // Internal function to set the readyState and raise "close" event.
+  // This can be used in place of close() when we don't want to send
+  // a message to the client.
+  this._destroy = function(code, reason) {
+    this.readyState = 3;
+    this.emit('close', {code: code, reason: reason});
+  };
+}).call(MultiplexChannel.prototype);
+
+function formatMessage(id, message) {
+  return id + '|m|' + message;
+}
+function formatOpenEvent(id, url) {
+  return id + '|o|' + url;
+}
+function formatCloseEvent(id, code, reason) {
+  return id + '|c|' + JSON.stringify({code: code, reason: reason});
+}
+function parseMultiplexData(msg) {
+  try {
+    var m = /^(\d+)\|(m|o|c)\|([\s\S]*)$/m.exec(msg);
+    if (!m)
+      return null;
+    msg = {
+      id: m[1],
+      method: m[2],
+      payload: m[3]
+    };
+
+    switch (msg.method) {
+      case 'm':
+        break;
+      case 'o':
+        break;
+      case 'c':
+        try {
+          msg.payload = JSON.parse(msg.payload);
+        } catch(e) {
+          return null;
+        }
+        break;
+      default:
+        return null;
+    }
+
+    return msg;
+
+  } catch(e) {
+    logger.debug('Error parsing multiplex data: ' + e);
+    return null;
+  }
+}
+
+// Properties to copy from sockjs connection to MultiplexChannel
+var connectionProps = ['readable', 'writable', 'remoteAddress',
+  'remotePort', 'address', 'headers', /* 'url', */ 'pathname',
+  'prefix', 'protocol', 'readyState'];
diff --git a/lib/proxy/robust-sockjs.js b/lib/proxy/robust-sockjs.js
new file mode 100644
index 0000000..a1662be
--- /dev/null
+++ b/lib/proxy/robust-sockjs.js
@@ -0,0 +1,307 @@
+/*
+ * robust-sockjs.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+var util = require('util');
+var events = require('events');
+var crypto = require('crypto');
+var _ = require('underscore');
+
+var MessageBuffer = require("shiny-server-client/common/message-buffer");
+var MessageReceiver = require("shiny-server-client/common/message-receiver");
+var message_utils = require("shiny-server-client/common/message-utils");
+var pathParams = require("shiny-server-client/common/path-params");
+
+var errorcode = require("./errorcode");
+
+// If not a robust URL (missing 'n' or 'o' path param), returns falsy.
+function getInfo(conn){
+  var params = pathParams.extractParams(conn.url);
+  // Look for 'n' (new) or 'o' (open) path param.
+  if (params.n) {
+    return {id: params.n, existing: false};
+  } else if (params.o) {
+    return {id: params.o, existing: true};
+  } else {
+    return false;
+  }
+}
+
+module.exports = RobustSockJSRegistry;
+// @param timeout The number of seconds to retain an abruptly closed connection
+//   before passing along its closed and/or end events.
+function RobustSockJSRegistry(timeout){
+  if (!timeout){
+    timeout = 15;
+  }
+  this._timeout = timeout * 1000;
+
+  this._connections = {};
+
+  var self = this;
+
+  // Handle the incoming connection, either mapping it to an existing session
+  // or creating a new Robust connection encapsulation for it.
+  //
+  // If not a robust connection (getInfo returns falsy), just return the
+  // original connection unchanged.
+	this.robustify = function(conn){
+    var info = getInfo(conn);
+    if (!info) {
+      // Not robust.
+      return conn;
+    }
+    var id = info.id;
+    var existing = info.existing;
+
+    if (id === 'none'){
+      return conn;
+    }
+
+    // The robust connection object to be returned.
+    var robustConn;
+
+    if (self._connections[id]){
+      // ID found in table
+
+      if (existing){
+        // Reconnecting to an existing session
+        logger.debug("Reconnecting to robust connection: " + id);
+
+        self._connections[id].set(conn);
+        // Return falsey as no further processing/wiring is needed on this connection.
+        return;
+      } else {
+        // Trying to create a new session with a colliding ID
+        logger.warn("RobustSockJS collision: " + id);
+        conn.close(errorcode.BAD_IDENTIFIER, "Unable to open connection");
+        return;
+      }
+    } else {
+      // ID not found in table
+
+      if (existing) {
+        // Trying to resume a session which we don't have a record of.
+        logger.debug("Disconnecting client because ID wasn't found.");
+        conn.close(errorcode.BAD_IDENTIFIER, 'Your session could not be resumed on the server.');
+        return;
+      } else {
+        // Creating a new connection.
+        logger.debug("Creating a new robust connection: " + id);
+        self._connections[id] = new self.RobustConn(conn, id);
+      }
+    }
+
+    return self._connections[id];
+  };
+
+  // We can't create a wholly new object, or we lose the references we've established
+  // on this obj elsewhere.
+  this.RobustConn = function(conn, id) {
+    // Extend eventEmitter
+    events.EventEmitter.call(this);
+
+    var robustConn = this;
+
+    this._messageBuffer = new MessageBuffer();
+    this._messageReceiver = new MessageReceiver();
+    this._messageReceiver.onacktimeout = function(e) {
+      if (robustConn._conn && robustConn._conn.readyState === 1) {
+        // logger.debug("Sending " + robustConn._messageReceiver.ACK());
+        robustConn._conn.write(robustConn._messageReceiver.ACK());
+      }
+    };
+
+    // During reconnection, we don't want to send messages until we have
+    // received a CONTINUE message from the client.
+    this._expectContinue = false;
+
+    // Make this connection identifiable as a robust connection. We use
+    // this to drop connections that ask for robustness but whose apps
+    // have reconnect disabled.
+    this.robust = true;
+    this.close = function(code, reason){
+      robustConn._conn.close(code, reason);
+    };
+    this.write = function(){
+      // Records the message in our buffer, and tags it with a message id.
+      arguments[0] = this._messageBuffer.write(arguments[0]);
+
+      // Write if this connection is ready.
+      if (this._expectContinue) {
+        // Do nothing. We've already written to this._messageBuffer; when we
+        // receive CONTINUE then we'll send at that point.
+      } else if (robustConn._conn.readyState === 1){
+        robustConn._conn.write.apply(robustConn._conn, arguments);
+      }
+    };
+    this._readyState = conn.readyState;
+    Object.defineProperty(this, "readyState", {
+      get: function() {
+        // We won't want to actually expose the ready state of the connection,
+        // as it may come and go. We want to be in a good ready state until
+        // we're shutting down.
+        return robustConn._readyState;
+      }
+    });
+    this._withheld = {timer: null, events: []};
+
+    // Swap out the SockJS connection behind this RobustConn
+    this.set = function(conn){
+      if (robustConn._conn) {
+        // Retire old connection
+
+        // Restore the original emit method so we don't see the bubbled
+        // close/end events on our RobustConn.
+        robustConn._conn.emit = robustConn._conn._oldEmit;
+        // Close the underlying stale SockJS Connection
+        // It would be awfully surprising if a client ever saw this (as it
+        // means two connections were simultaneously made for the same
+        // robust session).
+        robustConn._conn.close(errorcode.RETIRED, "Connection was retired by new connection");
+
+        // Clear out any pending close/end messages, as we no longer plan to close.
+        if (robustConn._withheld){
+          robustConn._withheld.events = [];
+          clearTimeout(robustConn._withheld.timer);
+          robustConn._withheld.timer = 0;
+        }
+
+        // Tell the BufferedResendConnection (on the client) what message ID we
+        // had been expecting before we got disconnected. If we had missed any
+        // messages in the meantime, they can send them now.
+        conn.write(robustConn._messageReceiver.CONTINUE());
+        // The next message we receive better be the other side sending CONTINUE
+        // to us too. This is not in response to our CONTINUE, but symmetrical
+        // to it; both sides should send CONTINUE to each other immediately upon
+        // reconnection.
+        this._expectContinue = true;
+      }
+
+      // Set the new connection
+
+      // Update all of our properties
+      this._conn = conn;
+      this.url = conn.url;
+      this.address = conn.address;
+      this.headers = conn.headers;
+
+      // Override the underlying connection's emit so we can echo from our
+      // RobustConn.
+      conn._oldEmit = conn.emit;
+      conn.emit = function(type) {
+        function doEmit(){
+          // We need to emit on the connections[id] object.
+          robustConn.emit.apply(robustConn, arguments);
+          conn._oldEmit.apply(conn, arguments);
+        }
+        if (type === 'data') {
+
+          // In this try block, we'll deal with the extra protocol stuff that
+          // the client adds in BufferedResendConnection. Their side can send
+          // us messages, and we can send them messages. Each side tags its
+          // messages with monotonically increasing hexadecimal message IDs
+          // (each side has its own sequence). And each side records the most
+          // recent ID it's seen.
+          //
+          // When receiving a message, there are three extra things we may need
+          // to deal with.
+          //
+          // 1) Upon disconnection and reconnection, the first message received
+          //    after reconnection MUST be "CONTINUE <id>". This tells us the
+          //    last message id from us the other side heard before we got
+          //    disconnected (actually it is <last message id> + 1). If we get
+          //    this message we must replay any missed messages. If we don't
+          //    get the CONTINUE message but a different one instead, that is
+          //    a bad connection and we should abort immediately.
+          //
+          //    When we're waiting for CONTINUE to arrive, it's not safe for
+          //    us to send any new messages; we must save them in _messageBuffer
+          //    until we know whether they've missed any messages, lest we end
+          //    up sending the messages out of order.
+          //
+          // 2) At any time (before or after CONTINUE) we can receive "ACK <id>"
+          //    from client letting us know they've received our messages up to
+          //    that id, and it's safe for us to discard them from our buffer.
+          //
+          // 3) Actual data messages are tagged with an id. We need to strip off
+          //    the id, record it, possibly send an ACK with it, and then pass
+          //    the data on.
+          try {
+            // An ACK can be received at any time, including when we are expecting
+            // CONTINUE.
+            var discardCount = robustConn._messageBuffer.handleACK(arguments[1]);
+            if (discardCount >= 0) {
+              // It was an ACK message and it was handled. Don't process the
+              // message further.
+
+              // logger.debug("Discarded " + discardCount);
+              return;
+            }
+
+            if (robustConn._expectContinue) {
+              var continueId = message_utils.parseCONTINUE(arguments[1]);
+              if (continueId === null) {
+                throw new Error("Robust protocol error: Expected CONTINUE message");
+              }
+              robustConn._expectContinue = false;
+
+              robustConn._messageBuffer.discard(continueId);
+
+              var msgs = robustConn._messageBuffer.getMessagesFrom(continueId);
+              while (msgs.length) {
+                conn.write(msgs.shift());
+              }
+
+              // It was a CONTINUE message and it was handled. Don't process
+              // the message further.
+              return;
+            }
+
+            // Regular message, we expect it to be tagged with an ID. The
+            // receive() message will throw if not; if it is, then it'll take
+            // note of the ID and return the untagged data.
+            arguments[1] = robustConn._messageReceiver.receive(arguments[1]);
+
+          } catch (e) {
+            logger.warn("Error handling message: " + e);
+            robustConn.close(errorcode.BAD_PROTOCOL, "Protocol error handling message: " + e);
+            return;
+          }
+
+          doEmit.apply(null, arguments);
+        } else if (type === 'end' || type === 'close'){
+          logger.trace("Withholding event '" + type + "' from robust connection " + id);
+          robustConn._withheld.events.push({arg: arguments});
+          if (!robustConn._withheld.timer){
+            robustConn._withheld.timer = setTimeout(function(){
+              // If time has passed, actually kill the connection by emitting the
+              // withheld events of close and/or end.
+              var evt;
+              while ((evt = robustConn._withheld.events.pop())){
+                doEmit.apply(null, evt.arg);
+              }
+              logger.debug("Closing robust connection " + id);
+              delete self._connections[id];
+              robustConn._readyState = 3; // Mark as closed.
+            }, self._timeout);
+          }
+        } else {
+          doEmit.apply(null, arguments);
+        }
+      };
+    };
+
+    this.set(conn);
+  };
+  util.inherits(this.RobustConn, events.EventEmitter);
+}
diff --git a/lib/proxy/sockjs.js b/lib/proxy/sockjs.js
new file mode 100644
index 0000000..5a0b477
--- /dev/null
+++ b/lib/proxy/sockjs.js
@@ -0,0 +1,239 @@
+/*
+ * sockjs.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */ 
+var pause = require('pause');
+var sockjs = require('sockjs');
+var OutOfCapacityError = require('../core/errors').OutOfCapacity;
+var _ = require('underscore');
+var fsutil = require('../core/fsutil');
+var shutdown = require('../core/shutdown');
+var render = require('../core/render');
+var MultiplexSocket = require('./multiplex');
+var RobustSockJS = require('./robust-sockjs');
+var errorcode = require("./errorcode");
+
+exports.createServer = createServer;
+function createServer(router, schedulerRegistry) {
+  // Create a single SockJS server that will serve all applications. We'll use
+  // the connection.url to dispatch among the different worker processes'
+  // websocket ports. Once a connection is established, we simply pipe IO
+  // between the SockJS server connection and the worker websocket connection.
+  var sockjsServer = sockjs.createServer({
+    // TODO: make URL configurable
+    sockjs_url: '//d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.min.js',
+    prefix: '.*/__sockjs__(/[no]=\\w+)?',
+		log: function() {}
+  });
+
+  var robust = new RobustSockJS();
+  sockjsServer.on('connection', function(conn) {
+    var robustConn = robust.robustify(conn);
+    if (!robustConn){
+      return;
+    }
+    onMultiplexConnect(robustConn);
+  });
+
+  function onMultiplexConnect(conn){
+    var msSocket = new MultiplexSocket(conn);
+    msSocket.on('connection', function(mconn) {
+      handleMultiplexChannel(mconn, conn.robust);
+    });
+  }
+
+  function handleMultiplexChannel(conn, robust) {
+    if (!conn) {
+      // We saw conn===null in the wild one time
+      logger.debug('Falsy SockJS connection detected');
+      return;
+    }
+    logger.trace('Accepted SockJS connection for ' + conn.url);
+
+    // Since getAppSpec_p is asynchronous, we need to pause the connection's
+    // data and close events in order to not let data get lost while we wait
+    var paused = pause(conn);
+    // What app is the client trying to reach?
+    router.getAppSpec_p(conn)
+    .then(function(appSpec) {
+      if (!appSpec) {
+        logger.error('Websocket 404: ' + conn.url);
+        return;
+      }
+      if (appSpec === true) {
+        return;  // Request was fully handled
+      }
+      if (!appSpec.settings.appDefaults.reconnect && robust) {
+        logger.info("Shutting down robust connection for non-reconnect app");
+        conn.close(errorcode.ACCESS_DENIED, "Access denied");
+        return;
+      }
+      connectToApp(conn, appSpec);
+    })
+    .fin(function() {
+      // Now that event handlers are hooked up to the connection, it's safe
+      // to unpause
+      paused.resume();
+    })
+    .done();
+  };
+
+  function connectToApp(conn, appSpec) {
+
+    // The connection to the worker process.
+    var wsClient = null;
+
+    // Represents the worker process.
+    var appWorkerHandle = null;
+
+    // Buffer queue for any events that arrive on the SockJS connection before
+    // the worker websocket connection has been established.
+    var connEventQueue = [];
+
+    // Giving this event listener a name so we can remove it later
+    var connDataHandler = function(message) {
+      connEventQueue.push({event: 'data', data: [message]});
+    };
+
+    conn.on('data', connDataHandler);
+    conn.on('close', function() {
+      // Must, must, must match up acquire() and release() calls.
+      if (appWorkerHandle) {
+        appWorkerHandle.release('sock');
+        appWorkerHandle = null;
+      }
+      if (wsClient) {
+        wsClient.close();
+      }
+    });
+
+
+    //TODO: clean this up. This should be a part of the promise chain, but
+    // when returning an error, it would just crash as an unhandled excptn
+    // and never make it into the .fail().
+    var wrk;
+    try{
+      // Can't specify the URL that we're requesting, so provide 'ws' to
+      // represent that this request is for a web socket.
+      wrk = schedulerRegistry.getWorker_p(appSpec, 'ws');
+    }
+    catch(err){
+      if (err instanceof OutOfCapacityError){
+        conn.close(errorcode.OUT_OF_CAPACITY, "Out of capacity");
+        return;
+      }
+      throw err;
+    }
+    wrk
+    .then(function(awh) {
+      if (conn.readyState >= 2) // closing or closed
+        return;
+
+      appWorkerHandle = awh;
+      appWorkerHandle.acquire('sock');
+
+      var wsUrl = 'ws://127.0.0.1/';
+      var pathInfo = conn.url.substring(appSpec.prefix.length);
+
+      // Prepend a slash.
+      if (!pathInfo.match(/^\//)){
+        pathInfo = '/' + pathInfo;
+      }
+
+      pathInfo = pathInfo.replace(/\/__sockjs__\/.*/, "/websocket/");
+      wsClient = appWorkerHandle.endpoint.createWebSocketClient(pathInfo);
+
+      wsClient.onopen = function(event) {
+        conn.removeListener('data', connDataHandler);
+        conn.on('data', _.bind(wsClient.send, wsClient));
+
+        // If any conn events queued up while we were waiting for the
+        // websocket client to connect, emit them now.
+        var queuedEvent;
+        while ((queuedEvent = connEventQueue.shift())) {
+          conn.emit.apply(conn, [queuedEvent.event].concat(queuedEvent.data));
+        }
+      };
+
+      wsClient.onerror = function(event) {
+        // TODO: Send error message and stderr to client via websocket; this
+        // means we couldn't connect to the websocket
+        conn.close();
+      };
+
+      wsClient.onmessage = function(event) {
+        conn.write(event.data);
+      };
+
+      wsClient.onclose = function(event) {
+        // Did the client side already close? If so, then this is a normal
+        // close, no need to log anything.
+        if (conn.readyState > 1) // closing or closed
+          return;
+
+        // Send error message and stderr to client via websocket; this
+        // is an unexpected close (i.e. the R process terminated).
+        fsutil.safeTail_p(appWorkerHandle.logFilePath, 8192)
+        .fail(function(consoleLog) {
+          return '';
+        })
+        .then(function(consoleLog) {
+          if (appSpec.settings.appDefaults.sanitizeErrors) {
+            var moreInfo = consoleLog
+              ? "\r\n\r\nDiagnostic information is private. Please ask your system admin for " +
+              "permission if you need to check the R logs."
+              : "";
+          } else {
+            render.sendClientConsoleMessage(conn, consoleLog);
+            var moreInfo = consoleLog
+              ? "\r\n\r\nDiagnostic information has been dumped to the JavaScript error console."
+              : "";
+          }
+          var exitMessage = shutdown.shuttingDown ?
+              'The server is restarting.\r\n\r\nPlease wait a few moments, then refresh the page.' :
+              'The application unexpectedly exited.' + moreInfo;
+          render.sendClientAlertMessage(conn, exitMessage);
+        })
+        .fail(function(err) {
+          logger.error('Failed to send client close message: ' + err.message);
+        })
+        .fin(function() {
+          try {
+            if (shutdown.shuttingDown) {
+              // Tell client not to try to reconnect to old session; but if
+              // Shiny wants to automatically start a new session, that's OK.
+              conn.close(errorcode.SHUTTING_DOWN, "The server is shutting down");
+            } else {
+              conn.close(errorcode.APP_EXIT, "The application unexpectedly exited");
+            }
+          } catch (err) {
+            logger.error('Failed to close SockJS conn: ' + err.message);
+          }
+        })
+        .done();
+      };
+
+    })
+    .fail(function(err) {
+      // TODO: Write error to websocket, or something
+      logger.error('Error getting worker: ' + err);
+      try {
+        conn.close();
+      } catch (err) {
+        logger.error('Failed to close SockJS conn: ' + err.message);
+      }
+    })
+    .done();
+
+  }
+
+  return sockjsServer;
+}
diff --git a/lib/router/config-router-util.js b/lib/router/config-router-util.js
new file mode 100644
index 0000000..cc82c97
--- /dev/null
+++ b/lib/router/config-router-util.js
@@ -0,0 +1,115 @@
+/*
+ * config-router-util.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+var _ = require('underscore');
+var map = require('../core/map');
+var throwForNode = require('../config/schema').throwForNode;
+
+var ALL_PROTOCOLS = {
+  "websocket": true,
+  "xdr-streaming": true,
+  "xhr-streaming": true,
+  "iframe-eventsource": true,
+  "iframe-htmlfile": true,
+  "xdr-polling": true,
+  "xhr-polling": true,
+  "iframe-xhr-polling": true,
+  "jsonp-polling": true
+};
+
+/** A set of utility helper functions for config routers.
+ */
+
+/**
+ * Parse the application section of a config file, whether in
+ * a global or local config file.
+ * @param settings The settings object into which we should parse
+ *   the locNode. This modified object is ultimately returned.
+ * @param locNode The config node to parse as an application.
+ * @param provideDefaults if true, will provide reasonable defaults
+ *   for things like timeouts. Otherwise, will not add any fields
+ *   to what's found in the config.
+ */
+exports.parseApplication = parseApplication;
+function parseApplication(settings, locNode, provideDefaults){
+  var appSettings = map.create();
+
+  if (provideDefaults){
+    // Specify the defaults now -- will overwrite if they're provided for us.
+    appSettings.initTimeout = 60;
+    appSettings.idleTimeout = 5;
+    appSettings.preserveLogs = false;
+    appSettings.reconnect = true;
+    appSettings.sanitizeErrors = true;
+    appSettings.disableProtocols = [];
+    appSettings.bookmarkStateDir = '/var/lib/shiny-server/bookmarks';
+  }
+
+  if (locNode.getValues('app_idle_timeout') &&
+      (locNode.getValues('app_idle_timeout').timeout ||
+      locNode.getValues('app_idle_timeout').timeout === 0)){
+    appSettings.idleTimeout = locNode.getValues('app_idle_timeout').timeout;
+  }
+  if (locNode.getValues('app_init_timeout') &&
+      locNode.getValues('app_init_timeout').timeout){
+    appSettings.initTimeout = locNode.getValues('app_init_timeout').timeout;
+  }
+
+  if (locNode.getValues('preserve_logs') &&
+      locNode.getValues('preserve_logs').enabled){
+    appSettings.preserveLogs = true;
+  }
+
+  if (locNode.getOne('reconnect') &&
+      locNode.getValues('reconnect').enabled === false) {
+    appSettings.reconnect = false;
+  }
+
+  if (locNode.getOne('sanitize_errors') &&
+      locNode.getValues('sanitize_errors').enabled === false) {
+    appSettings.sanitizeErrors = false;
+  }
+
+  var disableProtocolsNode = locNode.getOne('disable_protocols', true);
+  if (disableProtocolsNode && disableProtocolsNode.values.names) {
+    appSettings.disableProtocols = disableProtocolsNode.values.names;
+    appSettings.disableProtocols.forEach(function(name) {
+      if (!ALL_PROTOCOLS[name]) {
+        throwForNode(disableProtocolsNode,
+          new Error("Unrecognized protocol " + name));
+      }
+    });
+  }
+  if (locNode.getValues('disable_websockets').val){
+    appSettings.disableProtocols.push('websocket');
+  }
+
+  if (locNode.getOne('bookmark_state_dir') &&
+      typeof(locNode.getValues('bookmark_state_dir').dir) === "string") {
+    // We don't validate this directory at this time, save that for before we
+    // actually launch the app.
+    appSettings.bookmarkStateDir = locNode.getValues('bookmark_state_dir').dir;
+  }
+
+  settings.appDefaults = appSettings;
+
+  //try to load in the Simple Scheduler's settings.
+  var scheduler = locNode.getOne('simple_scheduler');
+  if (scheduler){
+    var params = locNode.getValues('simple_scheduler');
+    settings.scheduler = {simple: params};
+  } else{
+    settings.scheduler = {simple: {maxRequests: 100}};
+  }
+
+  return settings;
+}
diff --git a/lib/router/config-router.js b/lib/router/config-router.js
new file mode 100644
index 0000000..9aac94e
--- /dev/null
+++ b/lib/router/config-router.js
@@ -0,0 +1,501 @@
+/*
+ * config-router.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+// TODO: Drop root whenever possible
+var assert = require('assert');
+var path = require('path');
+var util = require('util');
+var stable = require('stable');
+var _ = require('underscore');
+var map = require('../core/map');
+var paths = require('../core/paths');
+var permissions = require('../core/permissions');
+var qutil = require('../core/qutil');
+var router = require('./router');
+var UserDirsRouter = require('./user-dirs-router');
+var config = require('../config/config');
+var posix = require('../../build/Release/posix');
+var DirectoryRouter = require('./directory-router');
+var throwForNode = require('../config/schema').throwForNode;
+var configRouterUtil = require('./config-router-util');
+
+exports.createRouter_p = createRouter_p;
+function createRouter_p(configPath, schedulerRegistry) {
+  return config.read_p(
+    configPath, 
+    paths.projectFile('config/shiny-server-rules.config'))
+  .then(function(conf) {
+    checkPermissions(conf);
+    return new ConfigRouter(conf, schedulerRegistry);
+  });
+}
+
+function checkPermissions(conf) {
+  // run_as nodes that can't be obeyed due to permissions
+  var users = _.filter(conf.search('run_as', false), function(node) {
+    return _.some(node.values.users, function(user){
+      return !permissions.canRunAs(user);
+    });
+  });
+
+  // user_apps nodes (the mere presence of these means root is needed)
+  var userapps = conf.search('user_apps', false);
+  var userdirs = conf.search('user_dirs', false);
+
+  // Listen nodes with ports under 1024
+  var listens = _.filter(conf.search('listen', false), function(node) {
+    return node.values.port < 1024;
+  });
+
+  // Array of strings representing unique users to run_as
+  var uniqueUsers = _.chain(conf.search('run_as', false))
+      .map(function(node) {
+        return node.values.user;
+      })
+      .uniq()
+      .value();
+
+  if (permissions.isSuperuser()) {
+    // Check if superuser is actually necessary
+    if (uniqueUsers.length == 1 && (userapps.length + userdirs.length == 0)&& !listens.length) {
+      logger.warn('Running as root unnecessarily is a security risk! You could be running more securely as non-root.');
+    }
+
+    return;
+  }
+
+  // If we got here, we're not running as root
+
+  if (users.length) {
+    var userLine = users[0].values.users;
+    throwForNode(users[0],
+        new Error("The user '" + permissions.getProcessUser() + "' does not have permissions to run applications as one of the users in '" + 
+          userLine.join(',') + 
+          "'. Please restart shiny-server as one of the users in  '" + 
+          userLine.join(',') + "'."));
+  }
+
+  if (userapps.length) {
+    throwForNode(userapps[0], 
+        new Error('shiny-server must be run as root to use the user_apps directive'));
+  }
+  if (userdirs.length) {
+    throwForNode(userdirs[0], 
+        new Error('shiny-server must be run as root to use the user_dirs directive'));
+  }
+
+  if (listens.length) {
+    var port = listens[0].values.port;
+    throwForNode(listens[0],
+        new Error("The user '" + permissions.getProcessUser() + "' does not have permission to listen on port " + port + ". Please choose a port number of 1024 or above, or restart shiny-server as root."));
+  }
+}
+
+function ConfigRouter(conf, schedulerRegistry) {
+  this.servers = createServers(conf);
+  this.accessLogSpec = createAccessLogSpec(conf.getOne('access_log'));
+  this.socketDir = conf.getValues('socket_dir').path;
+
+  this.$allowAppOverride = conf.getValues('allow_app_override').enabled;
+  this.$templateDir = conf.getValues('template_dir').dir;
+
+  var apps = conf.search("application", true);
+  if (apps && apps.length > 0){
+    logger.error("The `application` configuration has been deprecated. Please "+ 
+      "see http://rstudio.github.io/shiny-server/latest/#location for more " +
+      "information on how to adjust your configuration file.");
+    process.exit(1);
+  }
+}
+Object.defineProperty(ConfigRouter.prototype, "reconnect", {
+  get: function() { throw new Error("reconnect property not supported"); }
+});
+(function() {
+  this.getAppSpec_p = function(req, res) {
+    var self = this;
+    var scored = _.map(this.servers, function(server) {
+      return {
+        server: server,
+        score: server.getScore(req, res)
+      };
+    });
+
+    // Ignore servers that don't match
+    scored = _.filter(scored, function(serverWithScore) {
+      return serverWithScore.score > 0;
+    });
+
+    // Stable sort the scored servers; higher scores at the beginning
+    scored = stable(scored, function(a, b) {
+      return a.score < b.score
+    });
+
+    // Return a promise that resolves to serially calling each server's
+    // getAppSpec_p and returning the first non-falsy result
+    return router.getFirstAppSpec_p(_.pluck(scored, 'server'), req, res)
+    .then(function(appSpec){
+      if (!appSpec || !appSpec.appDir){
+        //occurs when no router is found to handle the request
+        return appSpec;
+      }
+      return appSpec;
+    })
+    
+  };
+
+  this.getAddresses = function() {
+    return _.map(this.servers, function(server) {
+      return { address: server.host, port: server.port };
+    });
+  };
+
+  this.getAppOverride = function(){
+    return this.$allowAppOverride;
+  }
+}).call(ConfigRouter.prototype);
+
+/**
+ * Crawl the parsed and validated config file data to create an array of
+ * ServerRouters.
+ */
+function createServers(conf) {
+  var seenKeys = map.create();
+
+  var servers = _.map(conf.search('server'), function(serverNode) {
+    var listenNode = serverNode.getOne('listen');
+    if (!listenNode)
+      throwForNode(serverNode,
+          new Error('Required "listen" directive is missing'));
+
+    var port = serverNode.getValues('listen').port;
+    if (typeof port === 'undefined')
+      port = 80;
+    if (port <= 0)
+      throwForNode(listenNode, new Error('Invalid port number'));
+
+    var host = serverNode.getValues('listen').host || '0.0.0.0';
+    if (host === '*')
+      host = '0.0.0.0';
+
+    if (!/^\d+\.\d+\.\d+\.\d+$/.test(host)) {
+      throwForNode(listenNode,
+          new Error('Invalid IPv4 address "' + host + '"'));
+    }
+
+    var serverNames = serverNode.getValues('server_name').names;
+
+    // Read all locations. Use post-order traversal so that nested locs
+    // get priority over their parents.
+    var locations = _.chain(serverNode.search('location', false, true))
+    .map(createLocation)
+    .compact()
+    .value();
+
+    // We get the templateDir at the server level so that a global or server-
+    // wide templateDir can be attached to the request directly. We may need
+    // this if a page gets returns without a matching AppSpec (like a 404).
+    var templateDir = serverNode.getValues('template_dir').dir;
+
+    _.each(serverNames || [''], function(serverName) {
+      var key = host + ':' + port;
+      if (serverName !== '')
+        key += '(' + serverName + ')';
+      if (seenKeys[key])
+        throwForNode(listenNode,
+            new Error(key + ' combination conflicts with earlier server definition'));
+      seenKeys[key] = true;
+    });
+
+    return new ServerRouter(port, host, serverNames, locations, templateDir);
+  });
+
+  return servers;
+}
+
+function createAccessLogSpec(accessLogNode) {
+  if (!accessLogNode)
+    return null;
+  return {
+    path: accessLogNode.values.path,
+    format: accessLogNode.values.format
+  };
+}
+
+/**
+ * Delegates requests to sub-routers (locations). Can indicate how well its
+ * configuration matches up to a request (`getScore`).
+ */
+function ServerRouter(port, host, vhosts, locations, templateDir) {
+  assert(port);
+  assert(host);
+  this.port = port;
+  this.host = host;
+  this.vhosts = _.invoke(vhosts || [], 'toLowerCase');
+  this.$locations = locations;
+  this.$templateDir = templateDir;
+}
+(function() {
+  /**
+   * This score helps determine the order in which servers will be tried for
+   * a given request. Higher scores are tried first, and if two servers have
+   * the same score then they are tried in the order they are found in the
+   * configuration file.
+   *
+   * A score of 0 or lower means failure--the server is not able to handle
+   * the request.
+   *
+   * - Matching local port number: 0 points
+   * - Non-matching local port number: fail
+   *
+   * - Server has wildcard (* or 0.0.0.0) for host: 1 point
+   * - Server has same local IP address as request: 2 points
+   * - Non-wildcard host server and differing request host: fail
+   *
+   * - Server has no virtual host: 0 points
+   * - Server has same virtual host: 3 points
+   * - Server has differing virtual host: fail
+   */
+  this.getScore = function(req, res) {
+    var score = 0;
+
+    if (!req.address && !req.connection) {
+      logger.warn('Request with no address and no connection: ' + util.inspect(req));
+      return 0;
+    }
+
+    var address = req.address || req.connection.address();
+    var port = address.port;
+    var host = address.address;
+    var vhost = (req.headers.host || '').split(/:/)[0].toLowerCase();
+    if (!req.headers.host) {
+      logger.warn('No host header sent by user-agent ' + req.headers['user-agent']);
+    }
+    if (port != this.port)
+      return 0;
+    
+    if (this.host == '0.0.0.0' || this.host == '*')
+      score += 1;
+    else if (this.host == host)
+      score += 2;
+    else
+      return 0;
+
+    if (this.vhosts.length > 0) {
+      // TODO: Support non-literal server_name values
+      // http://nginx.org/en/docs/http/ngx_http_core_module.html#server_name
+      var found = _.find(this.vhosts, function(vh) {
+        return vh === vhost;
+      });
+      if (found) {
+        score += 3;
+      } else {
+        return 0;
+      }
+    }
+
+    assert(score > 0);
+    return score;
+  };
+
+  this.getAppSpec_p = function(req, res) {
+    assert(this.getScore(req, res) > 0);
+
+    // Attach the templateDir directly to the request so that we can properly
+    // generate pages without needing an AppSpec.
+    req.templateDir = this.$templateDir;
+
+    // Return a promise that resolves to serially calling each location's
+    // getAppSpec_p and returning the first non-falsy result
+    return router.getFirstAppSpec_p(this.$locations, req, res);
+  };
+}).call(ServerRouter.prototype);
+
+function joinUrlParts(a, b) {
+  return a.replace(/\/$/, '') + '/' + b.replace(/^\//, '');
+}
+
+function deriveLocationPath(locNode) {
+  var path = locNode.values.path;
+  while (locNode.parent && locNode.parent.name === 'location') {
+    locNode = locNode.parent;
+    path = joinUrlParts(locNode.values.path, path);
+  }
+  if (!/^\//.test(path))
+    path = '/' + path;
+  return path;
+}
+
+function createSiteDir(locNode, node, settings) {
+  var locPath = deriveLocationPath(locNode);
+  var rootPath = deriveLocationPath(node.parent);
+
+  var sitedirPath = locNode.getValues('site_dir').rootPath;
+
+  var runas = locNode.getValues('run_as').users;
+  if (!runas || runas.length == 0)
+    throwForNode(locNode, new Error(
+        'Required "run_as" directive not present or has no users.'));
+
+  var logdir = locNode.getValues('log_dir').path;
+  if (!logdir)
+    throwForNode(locNode, new Error(
+        'Required "log_dir" directive was not found'));
+
+  var dirIndex = !!locNode.getValues('directory_index').enabled;
+  // TODO: Filter on locPath
+  var realRouter = new DirectoryRouter(sitedirPath, runas, dirIndex, rootPath,
+      logdir, settings);
+  if (locPath !== rootPath) {
+    return new router.PrefixFilterRouter(realRouter, locPath);
+  } else {
+    return realRouter;
+  }
+}
+
+function createAppDir(locNode, node, settings) {
+  if (locNode.depth !== (node.depth - 1)) {
+    throwForNode(locNode, new Error('A location node may not inherit the app_dir directive.'));
+  }
+
+  var locPath = deriveLocationPath(locNode);
+  var appdirPath = locNode.getValues('app_dir').path;
+
+  var runas = locNode.getValues('run_as').users;
+  if (!runas || runas.length == 0)
+    throwForNode(locNode, new Error(
+        'Required "run_as" directive not present or has no users.'));
+
+  var logdir = locNode.getValues('log_dir').path;
+  if (!logdir)
+    throwForNode(locNode, new Error(
+        'Required "log_dir" directive was not found'));
+
+  assert(appdirPath);
+  return new router.SingleAppRouter(
+      appdirPath, runas, locPath, logdir, settings);
+}
+
+/**
+ * @param userDirsMode A Boolean representing whether we should be operating as
+ *   'user_apps' (which ignores the run_as directive), or 'user_dirs' (which 
+ *   respects the run_as directive).
+ **/
+function createUserApps(locNode, node, settings, userDirsMode) {
+  var locPath = deriveLocationPath(locNode);
+  var rootPath = deriveLocationPath(node.parent);
+
+  var userappsNode = locNode.getOne('user_apps');
+  var userappsEnabled = node.values.length == 0 || node.values.enabled;
+  if (!userappsEnabled) {
+    throwForNode(node, new Error('"' + node.args[0] + '" can no longer be used with the user_apps directive. Please omit the user_apps directive instead.'));
+  }
+
+  // We initially didn't require that user_apps would have a run_as setting
+  // associated with it. So to maintain backwards compatibility, we need to 
+  // continue to maintain these different code paths, at least for the time 
+  // being. For now, we'll pass the (optional) run_as parameter into the 
+  // construction of the UserDirsRouter.
+  var runas = null;
+  if (userDirsMode)
+    runas = locNode.getValues('run_as').users;
+
+  var groupsNode = locNode.getOne('members_of');
+  var groups = locNode.getValues('members_of').groups || [];
+  // groups is a list of group names; map it to a list of numeric group IDs
+  groups = _.map(groups, function(group) {
+    try {
+      var groupInfo = posix.getgrnam(group);
+      if (!groupInfo) {
+        throwForNode(groupsNode,
+            new Error('Group "' + group + '" does not exist'));
+      }
+    } catch (ex) {
+      throwForNode(groupsNode, ex);
+    }
+    return groupInfo.gid;
+  });
+
+  var dirIndex = !!locNode.getValues('directory_index').enabled;
+
+  var realRouter = new UserDirsRouter(rootPath, groups, settings, runas, 
+    dirIndex);
+  if (locPath !== rootPath) {
+    return new router.PrefixFilterRouter(realRouter, locPath);
+  } else {
+    return realRouter;
+  }
+}
+
+function createRedirect(locNode, node, settings) {
+  var locPath = deriveLocationPath(locNode);
+  var rootPath = deriveLocationPath(node.parent);
+
+  var redirectArgs = node.values;
+  var realRouter = new router.RedirectRouter(
+      rootPath, redirectArgs.url, redirectArgs.statusCode, redirectArgs.exact);
+  if (locPath !== rootPath) {
+    return new router.PrefixFilterRouter(realRouter, locPath);
+  } else {
+    return realRouter;
+  }
+}
+
+/**
+ * Validates the location node, and returns the appropriate type of router
+ * it represents.
+ */
+function createLocation(locNode) {
+  // TODO: Include ancestor locations in path
+  var path = locNode.values.path;
+
+  var terminalLocation = !locNode.getOne('location', false);
+  var node = locNode.getOne(/^site_dir|user_dirs|app_dir|user_apps|redirect$/);
+  if (!node) {
+    // No directives. Only allow this if child locations exist.
+    if (terminalLocation)
+      throwForNode(locNode, new Error('location directive must contain (or inherit) one of site_dir, user_apps, app_dir, or redirect'));
+    else
+      return null;
+  }
+
+  var settings = map.create();
+  var gaid = locNode.getValues('google_analytics_id').gaid;
+  if (gaid)
+    settings.gaTrackingId = gaid;
+
+  var logAsUser = locNode.getValues('log_as_user').enabled;
+  settings.logAsUser = logAsUser;
+
+  // Add the templateDir to the AppSpec, if we have one.
+  var templateDir = locNode.getValues('template_dir').dir;
+  if (templateDir){
+    settings.templateDir = templateDir;
+  }
+
+  settings = configRouterUtil.parseApplication(settings, 
+      locNode, true);
+
+  switch (node.name) {
+    case 'site_dir':
+      return createSiteDir(locNode, node, settings);
+    case 'app_dir':
+      return createAppDir(locNode, node, settings);
+    case 'user_apps':
+      return createUserApps(locNode, node, settings);
+    case 'user_dirs':
+      return createUserApps(locNode, node, settings, true);
+    case 'redirect':
+      return createRedirect(locNode, node, settings);
+    default:
+      throwForNode(locNode, new Error('Node name ' + node.name + ' was not expected here'));
+  }
+}
diff --git a/lib/router/directory-router.js b/lib/router/directory-router.js
new file mode 100644
index 0000000..4909501
--- /dev/null
+++ b/lib/router/directory-router.js
@@ -0,0 +1,449 @@
+/*
+ * directory-router.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+var fs = require('fs');
+var path = require('path');
+var send = require('send');
+var url = require('url');
+var util = require('util');
+var Q = require('q');
+var regexp_quote = require('../core/re-quote');
+var _ = require('underscore');
+var qutil = require('../core/qutil');
+var render = require('../core/render');
+var AppSpec = require('../worker/app-spec');
+var configRouterUtil = require('../router/config-router-util');
+var appConfig = require('../config/app-config');
+
+send.mime.define({'text/R': ['r']});
+
+module.exports = DirectoryRouter;
+/**
+ * @param {String} root The root directory from which to serve content/apps
+ * @param {String} runas The username of the user we should impersonate when
+ *   serving up apps
+ * @param {Boolean} dirIndex True if directory index should be shown when
+ *   index.html is not found.
+ * @param {String} logdir Directory in which to create log files for apps
+ * @param {Object} settings Settings to pass through to AppSpec
+ * @param {RegExp} blacklist If present, will forbid any request that matches
+ *   this pattern with the URL starting after the end of the prefix.
+ */
+function DirectoryRouter(root, runas, dirIndex, prefix, logdir, settings, 
+    blacklist) {
+  prefix = prefix.replace(/\/$/m, ''); // strip trailing slash, if any
+  this.$root = root;
+  this.$runas = runas;
+  this.$dirIndex = dirIndex;
+  this.$logdir = logdir;
+  this.$settings = settings;
+  this.$blacklist = blacklist;
+
+  // ?= used for 0-width positive lookahead. So match any string starting with
+  // the prefix followed by a / or one that ends after the prefix.
+  this.$rePath = new RegExp('^' + regexp_quote(prefix) + '(?=/|$)');
+}
+
+(function() {
+  this.getAppSpec_p = function(req, res) {
+    var self = this;
+
+    var reqUrl = url.parse(req.url);
+
+    var pathname = reqUrl.pathname;
+    var m = this.$rePath.exec(pathname);
+    if (!m)
+      return Q.resolve(null);
+    var prefix = m[0];
+    var suffix = pathname.substring(prefix.length);
+
+    // Disallow hidden path elements, ".", and ".."
+    if (/\/\./.test(unescape(pathname)) || 
+        (this.$blacklist && this.$blacklist.exec(suffix))) {
+      render.sendPage(res, 403, 'Forbidden', {
+        template: 'error-403',
+        templateDir: self.$settings.templateDir
+      });
+      return Q.resolve(true);
+    }
+
+    return this.$findShinyDir_p(suffix)
+    .fail(function(err) {
+      logger.error('Error finding Shiny dir: ' + err.message);
+      return null;
+    })
+    .then(function(subpath) {
+      if (subpath && subpath.res) {
+        // Shiny directory was found
+        if (subpath.res.rawPath == suffix) {
+          // Trailing slash was missing; redirect to add it
+          res.writeHead(301, {
+            'Location': reqUrl.pathname + '/' + (reqUrl.search || '')
+          });
+          res.end();
+          return false;
+        }
+        else {
+          var appDir = path.join(self.$root, subpath.res.path);
+          var relURL = suffix.substring(subpath.res.rawPath.length);
+
+          if (subpath.indexHtml && relURL === '/'){
+            logger.debug("Serving index.html in lieu of index.Rmd.");
+            // This implies that we did not have an index.Rmd (in which case
+            // we would just run the app normally), but that we DO have an 
+            // index.html file. We want to make an exception to our general flow
+            // and, if the base URL is requested, serve this index.html file as
+            // rmarkdown would just return a 404.
+            Q.nfcall(fs.readFile, path.join(appDir, subpath.indexHtml))
+            .then(function(content){
+              res.writeHead(200, { 'Content-Type': 'text/html' });
+              res.end(content, 'utf-8');
+            }, function(err){
+              logger.warn("Attempting to serve an index.html file for an Rmd directory, but unable to read file: " + path.join(appDir, subpath.indexHtml));
+              render.sendPage(res, 500, 'Error Reading Index', {
+                template: 'error-500',
+                templateDir: self.$settings.templateDir
+              });
+            })
+            .done();
+            return true;
+          }
+
+          // deep clone settings to avoid others updating this object
+          var settings =  JSON.parse(JSON.stringify(self.$settings));
+
+          // Specify whether we're dealing with an Rmd or a Shiny app.
+          settings.mode = subpath.type;
+
+          // Valid request in an application path
+          var as = new AppSpec(appDir,
+            self.$runas, prefix + subpath.res.rawPath, self.$logdir,
+              settings);
+          return as;
+        }
+      } else {
+        // Not in Shiny app directory; serve statically
+        return self.$staticServe_p(req, res, reqUrl, suffix);
+      }
+    });
+  };
+
+  this.$staticServe_p = function(req, res, reqUrl, suffix) {
+    var self = this;
+    var deferred = Q.defer();
+
+    function onError(err) {
+      if (err.status == 404)
+        deferred.resolve(null);
+      else
+        deferred.reject(err);
+    }
+
+    // Called when the URL requested is a directory
+    function onDirectory() {
+      var this_SendStream = this;
+      if (!/\/$/.test(reqUrl.pathname)) {
+        // No trailing slash? Redirect to add one
+        res.writeHead(301, {
+          'Location': reqUrl.pathname + '/' + (reqUrl.search || '')
+        });
+        res.end();
+        deferred.resolve(true);
+        return;
+      }
+
+      var indexPath = path.normalize(path.join(
+        self.$root, unescape(this.path), 'index.html'));
+
+      fs.exists(indexPath, function(exists) {
+        if (exists) {
+          // index.html exists, just serve that. This is the same as
+          // the below call except without onDirectory and without
+          // .index(false)
+          send(req, suffix, {root: self.$root})
+            .on('error', onError)
+            .on('stream', onStream)
+            .pipe(res);
+        } else {
+          // Either serve up 404, or the directory auto-index
+          if (!self.$dirIndex) {
+            deferred.resolve(null);
+          } else {
+            deferred.resolve(
+              self.$autoindex_p(req, res, this_SendStream.path, self.$blacklist)
+            );
+          }
+        }
+      });
+    }
+
+    function onStream() {
+      deferred.resolve(true);
+    }
+
+    send(req, suffix, {root: this.$root, index: false})
+      .on('error', onError)
+      .on('directory', onDirectory)
+      .on('stream', onStream)
+      .pipe(res);
+
+    return deferred.promise;
+  };
+
+  this.$autoindex_p = function(req, res, apath, filter) {
+    var unescapedPath = unescape(apath);
+    var dirpath = path.normalize(path.join(this.$root, unescapedPath));
+    var self = this;
+    return Q.nfcall(fs.readdir, dirpath)
+    .then(function(files) {
+      files = _.reject(files, function(file) {
+        // reject hidden files
+        var hidden = /^\./.test(file);
+        var filtered = false;
+        if (filter){
+          filtered = filter.test(unescapedPath + file);
+        }
+        
+        return filtered || hidden;
+      });
+      files.sort(function (a, b) {
+        return a.toLowerCase().localeCompare(b.toLowerCase());
+      });
+      return qutil.map_p(files, function(file) {
+        return Q.nfcall(fs.stat, path.join(dirpath, file))
+        .then(function(stat) {
+          return {name: file, stat: stat};
+        });
+      });
+    })
+    .then(function(fileInfos) {
+      var files = [];
+      var dirs = [];
+      _.each(fileInfos, function(file) {
+        if (file.stat.isDirectory()) {
+          dirs.push(file.name);
+        } else {
+          files.push(file.name);
+        }
+      });
+      function linkifyAll(names) {
+        return _.map(names, function(name) {
+          return {
+            name: name,
+            url: escape(name)
+          };
+        });
+      }
+      files = linkifyAll(files);
+      dirs = linkifyAll(dirs);
+
+      render.sendPage(res, 200, 'Index of ' + unescapedPath, {
+        template: 'directoryIndex',
+        templateDir: self.$settings.templateDir,
+        vars: {
+          files: files,
+          dirs: dirs,
+        }
+      });
+
+      return true;
+    });
+  };
+
+  this.$findShinyDir_p = function(pathname) {
+    var self = this;
+
+    if (pathname.length < 1)
+      return Q.resolve(null);
+    if (pathname.charAt(0) != '/')
+      return Q.resolve(null);
+
+    var candidates = extractUnescapedDirs(pathname);
+    if (!candidates || candidates.length == 0)
+      return Q.resolve(null);
+
+    // Returns { exists: true|false, isApp: true|false, isRmd: true|false }
+    function isAppDir_p(dirPath) {
+      var deferred = Q.defer();
+      fs.exists(dirPath, function(exists) {
+        if (!exists)
+          deferred.resolve({
+            exists: false, 
+            isApp: false, 
+            isRmd: false, 
+            index: false
+          });
+        else {
+          deferred.resolve(
+            Q.nfcall(fs.readdir, dirPath)
+            .then(
+              function(entries) {
+                var app = _.find(entries, function(entry) {
+                  return /^server\.r$/i.test(entry);
+                });
+                var singleApp = _.find(entries, function(entry) {
+                  return /^app\.r$/i.test(entry);
+                });
+                var rmd = _.find(entries, function(entry) {
+                  return /\.rmd$/i.test(entry);
+                });
+                var indexRmd = _.find(entries, function(entry) {
+                  if (/^index\.rmd$/i.test(entry)){
+                    return entry;
+                  }
+                  return false;
+                });
+                var indexHtm = _.find(entries, function(entry) {
+                  if (/index\.htm(l)?$/i.test(entry)){
+                    return entry;
+                  }
+                  return false;
+                });
+
+                // Give priority to an Rmd index, the fall back to an html index
+                return {
+                  exists: true, 
+                  isApp: (!!app || !!singleApp), 
+                  isRmd: !!rmd, 
+                  indexType: indexRmd ? 'rmd' : indexHtm ? 'html' : null,
+                  indexPath: indexRmd || indexHtm
+                };
+              },
+              function(err) {
+                if (err.code == 'ENOTDIR') {
+                  return {
+                    exists: false, 
+                    isApp: false, 
+                    isRmd: false, 
+                    indexType: null
+                  };
+                }
+                else {
+                  logger.error('Error reading dir: ' + err.message);
+                  return {
+                    exists: true, 
+                    isApp: false, 
+                    isRmd: false, 
+                    indexType: null
+                  };
+                }
+              }
+            )
+          );
+        }
+      });
+
+      return deferred.promise;
+    }
+
+    return qutil.forEachPromise_p(
+      candidates,
+      function(subpath) {
+        return isAppDir_p(path.join(self.$root, subpath.path))
+        .then(function(testDirResult) {
+          if (!testDirResult.exists)
+            return {res : false}; // terminate loop and return false
+          else if (!testDirResult.isApp && !testDirResult.isRmd)
+            return {res: null}; // continue loop
+          else{
+            var type;
+            var indexHtml = false;
+
+            if (testDirResult.isApp){
+              type = 'shiny';
+            } else if (testDirResult.isRmd){
+              type = 'rmd';
+              if (testDirResult.indexType === 'html'){
+                indexHtml = testDirResult.indexPath;
+              }
+            }
+
+
+            // terminate loop and return subpath
+            return {
+              res: subpath, 
+              type: type, 
+              indexHtml: indexHtml
+            };
+          }
+        });
+      },
+      function(result) {
+        return (result.res !== null);
+      },
+      null
+    )
+    .fail(function(err) {
+      logger.debug(err.message);
+      return {res: null};
+    });
+  };
+}).call(DirectoryRouter.prototype);
+
+/**
+ * Convert the given /-delimited path into a list of dirs and its original
+ * representation in the escaped path. For example:
+ *
+ * Input: /foo/bar//./this%20that/blah
+ * Output: [
+ *   { path: 'foo', rawPath: '/foo' },
+ *   { path: 'foo/bar', rawPath: '/foo/bar' },
+ *   { path: 'foo/bar/this that', rawPath: '/foo/bar//./this%20that' }
+ *
+ * If .. or escaped / is detected in any of the path elements then null is
+ * returned, indicating that the given path could not be safely mapped to
+ * a path.
+ */
+function extractUnescapedDirs(p) {
+  var re = /\/|$/g;
+  var m;
+
+  var pathAccum = '';
+  var pathElements = [];
+
+  // It's always possible that the root is an app dir; the regex won't
+  // catch that so we add it explicitly
+  pathElements.unshift({path: '', rawPath: ''});
+
+  var lastpos = 0;
+  var element;
+  while (m = re.exec(p)) {
+    // This can happen if p ends with /, since we match on $
+    if (lastpos > p.length)
+      break;
+    element = unescape(p.substring(lastpos, m.index));
+    lastpos = m.index + 1;
+
+    // empty? ignore.
+    if (!element) {
+      continue;
+    }
+    // only spaces? bail.
+    if (/^\s*$/.test(element))
+      return null;
+    // ..? bail.
+    if (element === '..')
+      return null;
+    if (element === '.')
+      continue;
+    // contains \ or / (possible if the / was escaped)? bail.
+    if (/[\/\\]/.test(element))
+      return null;
+
+    if (pathAccum)
+      pathAccum += path.sep;
+    pathAccum += element;
+
+    pathElements.push({path: pathAccum, rawPath: p.substring(0, m.index)});
+  }
+  return pathElements;
+}
diff --git a/lib/router/local-config-router.js b/lib/router/local-config-router.js
new file mode 100644
index 0000000..cb13c48
--- /dev/null
+++ b/lib/router/local-config-router.js
@@ -0,0 +1,63 @@
+/*
+ * local-config-router.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+var AppConfig = require("../config/app-config").AppConfig;
+var configRouterUtil = require('./config-router-util');
+
+// Router that supplements an appSpec with the local app config file
+//   provided in this app's directory. 
+// Historical note: This behavior can't be contained inside the config-
+//   router as the appConfig must be aware of the restart timestamp in 
+//   order to properly cache the local configurations, so this router
+//   must sit /outside/ of the RestartRouter. And that level of caching
+//   is important as it can offer 20-30% speedup on connection open.
+module.exports = LocalConfigRouter;
+function LocalConfigRouter(router, eventBus) {
+  this.$router = router;
+  this.$appConfig = new AppConfig(eventBus);
+}
+(function() {
+  this.getAppSpec_p = function(req, res) {
+    var self = this;
+    return this.$router.getAppSpec_p.apply(this.$router, arguments)
+    .then(function(appSpec) {
+      if (appSpec && typeof(appSpec) === 'object' && self.$allowAppOverride) {
+  			// Supplement the global appSpec with a local one, if it exists.
+  			return self.$appConfig.readConfig_p(appSpec)
+  			.then(function(config){
+          if (config){
+            // parse the global config
+  			    var appSettings = configRouterUtil.parseApplication({}, config);
+            // Merge the global config with this app's local conf, if it exists.
+            appSpec = self.$appConfig.addLocalConfig(appSpec,
+  			       appSettings);
+  			  }
+  			  return (appSpec);
+  			})
+        .fail(function(err){
+          throw new Error("Invalid local app configuration file: " + err);
+        });
+      } else {
+        return appSpec;
+      }
+    });
+  };
+
+  /**
+   * Set whether or not local apps should be allowed to override 
+   * global server settings with local .shiny_app.conf files.
+   */
+  this.setAppOverride = function(enabled){
+    this.$allowAppOverride = enabled;
+  }
+}).call(LocalConfigRouter.prototype);
\ No newline at end of file
diff --git a/lib/router/router.js b/lib/router/router.js
new file mode 100644
index 0000000..fb5b1ec
--- /dev/null
+++ b/lib/router/router.js
@@ -0,0 +1,333 @@
+/*
+ * router.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+var assert = require('assert');
+var fs = require('fs');
+var path = require('path');
+var url = require('url');
+var util = require('util');
+var Q = require('q');
+var _ = require('underscore');
+var fsutil = require('../core/fsutil');
+var qutil = require('../core/qutil');
+var posix = require('../../build/Release/posix');
+var appConfig = require('../config/app-config');
+var configRouterUtil = require('../router/config-router-util');
+var AppSpec = require('../worker/app-spec');
+var regexp_quote = require('../core/re-quote');
+var render = require('../core/render');
+
+exports.NullRouter = NullRouter;
+function NullRouter() {
+}
+(function() {
+  this.getAppSpec_p = function(req, res) {
+    return Q.resolve(null);
+  };
+}).call(NullRouter.prototype);
+
+exports.DummyRouter = DummyRouter;
+function DummyRouter(appDir, runAs) {
+  this.appSpec = new AppSpec(appDir, runAs, '', null, {});
+}
+
+(function() {
+  /**
+   * Possible input values:
+   * - In the case of normal HTTP request, req is a ServerRequest and res is a
+   *   ServerResponse
+   * - In the case of a SockJS request, req is a SockJS connection object and
+   *   res is undefined
+   *
+   * Possible return values:
+   * - An AppSpec object means to launch/reuse the specified app and proxy the
+   *   request to it
+   * - true means we already fully responded to the request, no other action is
+   *   necessary (such as performing a redirect, for example)
+   * - Falsy values means this router doesn't know how to handle this
+   *   particular request
+   */
+  this.getAppSpec_p = function(req, res) {
+    return Q.resolve(this.appSpec);
+  };
+}).call(DummyRouter.prototype);
+
+
+exports.IndirectRouter = IndirectRouter;
+/**
+ * A router that wraps another router, where the latter can be replaced at any
+ * time.
+ */
+function IndirectRouter(router) {
+  this.$router = router;
+}
+(function() {
+  this.getAppSpec_p = function(req, res) {
+    return this.$router.getAppSpec_p.apply(this.$router, arguments);
+  };
+  this.setRouter = function(router) {
+    this.$router = router;
+  };
+}).call(IndirectRouter.prototype);
+
+
+// Router that annotates the AppSpec with $APPDIR/restart.txt timestamp,
+// if the file exists. This makes it possible for app developers and
+// admins to use "touch restart.txt" to force an app to start a fresh
+// instance on subsequent loads.
+exports.RestartRouter = RestartRouter;
+function RestartRouter(router) {
+  this.$router = router;
+}
+(function() {
+  this.getAppSpec_p = function(req, res) {
+    return this.$router.getAppSpec_p.apply(this.$router, arguments)
+    .then(function(appSpec) {
+      if (appSpec && typeof(appSpec) === 'object') {
+        var restartPath = path.join(appSpec.appDir, 'restart.txt');
+        return fsutil.safeStat_p(restartPath)
+        .then(
+          function(stat) {
+            if (stat)
+              appSpec.settings.restart = stat.mtime.getTime();
+            return appSpec;
+          },
+          function(err) {
+            logger.error('Error checking restart.txt: ' + err.message);
+            return appSpec;
+          }
+        );
+      } else {
+        return appSpec;
+      }
+    });
+  };
+}).call(RestartRouter.prototype);
+
+
+exports.getFirstAppSpec_p = getFirstAppSpec_p;
+function getFirstAppSpec_p(routers, req, res) {
+  return qutil.forEachPromise_p(
+    routers,
+    function(router) {
+      return router.getAppSpec_p(req, res);
+    },
+    function(appSpec) {
+      return !!appSpec;
+    },
+    null
+  );
+}
+
+
+/**
+ * A router that takes a router and a prefix. For each request, if the
+ * request matches the prefix, then the request is delegated to the
+ * underlying router; otherwise, null is returned.
+ */
+exports.PrefixFilterRouter = PrefixFilterRouter;
+function PrefixFilterRouter(router, prefix) {
+  this.$router = router;
+
+  prefix = prefix.replace(/\/$/m, ''); // strip trailing slash, if any
+  this.$rePath = new RegExp('^' + regexp_quote(prefix) + '(?=/|$)');
+  this.$_prefix = prefix;
+}
+
+(function() {
+  this.getAppSpec_p = function(req, res) {
+    var reqUrl = url.parse(req.url);
+    var pathname = reqUrl.pathname;
+    var m = this.$rePath.exec(pathname);
+    if (!m)
+      return Q.resolve(null);
+    else
+      return this.$router.getAppSpec_p(req, res);
+  };
+}).call(PrefixFilterRouter.prototype);
+
+
+exports.CompositeRouter = CompositeRouter;
+/**
+ * A router that takes a list of routers, and for each request, tries each
+ * router in order until one of them returns a truthy value.
+ *
+ * @param {Router[]} routers - Array of routers or getAppSpec functions.
+ *   You can also include falsy values, which will be ignored.
+ */
+function CompositeRouter(routers) {
+  this.$routers = _.compact(_.map(routers, function(router) {
+    if (typeof router === 'function')
+      return {
+        getAppSpec_p: function(req, res) {
+          assert(req);
+          return Q.resolve(router.apply(null, arguments));
+        }
+      };
+    else
+      return router;
+  }));
+}
+
+(function() {
+  this.getAppSpec_p = function(req, res) {
+    return getFirstAppSpec_p(this.$routers, req, res);
+  };
+}).call(CompositeRouter.prototype);
+
+/**
+ * Combines any number of routers into a single router that will try them all
+ * in order until one of them returns a truthy value.
+ *
+ * @param {...Router} routers - Any number of routers or getAppSpec functions.
+ *   You can also pass falsy values, which will be ignored.
+ */
+exports.join = function() {
+  return new CompositeRouter(arguments);
+};
+
+exports.SingleAppRouter = SingleAppRouter;
+function SingleAppRouter(appdir, runas, prefix, logDir, settings) {
+  prefix = prefix.replace(/\/$/m, ''); // strip trailing slash, if any
+  settings = settings || {};
+  this.$rePath = new RegExp('^' + regexp_quote(prefix) + '(?:(/)|$)');
+  
+  this.$appdir = appdir;
+  this.$runas = runas;
+  this.$prefix = prefix + '/';
+  this.$logDir = logDir;
+  this.$settings = settings;
+
+  var self = this;
+}
+
+(function() {
+  this.getAppSpec_p = function(req, res) {
+    
+    var self = this;
+
+    // Creates a /shallow/ copy of the settings object. So we can independently
+    // mutate top-level properties (but not nested objects) without affecting 
+    // the class variable.
+    var settings = _.clone(self.$settings);
+
+    // Create a local appSpec that we can modify.
+    var appSpec = new AppSpec(self.$appdir, self.$runas, self.$prefix, 
+      self.$logDir, settings);
+
+    return Q.nfcall(fs.readdir, appSpec.appDir)
+    .then(function(files){ 
+      var app = _.find(files, function(entry) {
+        return /^server\.r$/i.test(entry);
+      });
+
+      var singleApp = _.find(files, function(entry) {
+        return /^app\.r$/i.test(entry);
+      });
+      var rmd = _.find(files, function(entry) {
+        return /\.rmd$/i.test(entry);
+      });
+      var indexRmd = _.find(files, function(entry) {
+        if (/^index\.rmd$/i.test(entry)){
+          return entry;
+        }
+        return false;
+      });
+      var indexHtm = _.find(files, function(entry) {
+        if (/index\.htm(l)?$/i.test(entry)){
+          return entry;
+        }
+        return false;
+      });
+
+      var indexType  = indexRmd ? 'rmd' : indexHtm ? 'html' : null;
+      var indexPath = indexRmd || indexHtm;
+
+      if (app || singleApp){
+        appSpec.settings.mode = 'shiny';
+      } else if(rmd){
+        appSpec.settings.mode = 'rmd';
+        if (indexType === 'html' && self.$prefix === req.url){
+          logger.debug("Serving index.html in lieu of index.Rmd.");
+          // This implies that we did not have an index.Rmd (in which case
+          // we would just run the app normally), but that we DO have an 
+          // index.html file. We want to make an exception to our general flow
+          // and, if the base URL is requested, serve this index.html file as
+          // rmarkdown would just return a 404.
+          Q.nfcall(fs.readFile, path.join(appSpec.appDir, indexPath))
+          .then(function(content){
+            res.writeHead(200, { 'Content-Type': 'text/html' });
+            res.end(content, 'utf-8');
+          }, function(err){
+            logger.warn("Attempting to serve an index.html file for an Rmd directory, but unable to read file: " + 
+              path.join(appSpec.appDir, indexPath));
+            render.sendPage(res, 500, 'Error Reading Index', {
+              template: 'error-500',
+              templateDir: appSpec.settings.templateDir
+            });
+          })
+          .done();
+          return true;
+        }
+      } else{
+        // No app found.
+      }
+
+      var reqUrl = url.parse(req.url);
+      var m = self.$rePath.exec(reqUrl.pathname);
+      if (!m)
+        return null;
+
+      if (!m[1]) {
+        if (res) {
+          res.writeHead(301, {
+            'Location': reqUrl.pathname + '/' + (reqUrl.search || '')
+          });
+          res.end();
+          return true;
+        } else {
+          // SockJS case, this is not expected
+          return null;
+        }
+      }
+      return appSpec;
+    });
+  }
+}).call(SingleAppRouter.prototype);
+
+
+exports.RedirectRouter = RedirectRouter;
+function RedirectRouter(prefix, url, statusCode, exact) {
+  var prefix = prefix.replace(/\/$/m, '');
+  if (exact)
+    this.$rePath = new RegExp('^' + regexp_quote(prefix) + '/?$');
+  else
+    this.$rePath = new RegExp('^' + regexp_quote(prefix) + '(?:/|$)');
+  this.$url = url;
+  this.$statusCode = statusCode;
+}
+(function() {
+  this.getAppSpec_p = qutil.wrap(function(req, res) {
+    if (!res)
+      return null;
+
+    var reqUrl = url.parse(req.url);
+    if (!this.$rePath.test(reqUrl.pathname))
+      return null;
+
+    res.writeHead(this.$statusCode, {
+      Location: this.$url
+    });
+    res.end();
+    return true;
+  });
+}).call(RedirectRouter.prototype);
diff --git a/lib/router/squash-run-as-router.js b/lib/router/squash-run-as-router.js
new file mode 100644
index 0000000..96ec8fa
--- /dev/null
+++ b/lib/router/squash-run-as-router.js
@@ -0,0 +1,48 @@
+/*
+ * squash-run-as-router.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+/**
+ * Squash the runas array down to a single, non-special username, or undefined 
+ * if it doesn't find any acceptable users.
+ *
+ * Any username that matches /^:.*:$/ will be treated as a special username.
+ * Internal routers may replace such special usernames as they see fit. This
+ * router will be the outer-most and will condense the array of runAs users 
+ * down to a single user. Any username that is not a string will not be
+ * considered as a candidate.
+ **/
+
+ var _ = require('underscore');
+
+module.exports = SquashRouter;
+function SquashRouter(router) {
+  this.$router = router;
+}
+(function() {
+  this.getAppSpec_p = function(req, res) {
+    return this.$router.getAppSpec_p.apply(this.$router, arguments)
+    .then(function(appSpec) {
+      if (appSpec && typeof(appSpec) === 'object' && 
+          appSpec.runAs && _.isArray(appSpec.runAs)) {
+        appSpec.runAs = _.find(appSpec.runAs, function(user){
+          if (!_.isString(user))
+            return false;
+          
+          return !(/^:.*:$/.test(user));
+        });
+      }
+
+      return appSpec;      
+    });
+  };
+}).call(SquashRouter.prototype);
diff --git a/lib/router/user-dirs-router.js b/lib/router/user-dirs-router.js
new file mode 100644
index 0000000..a21c8ac
--- /dev/null
+++ b/lib/router/user-dirs-router.js
@@ -0,0 +1,85 @@
+/*
+ * user-dirs-router.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+var Q = require('q');
+var qutil = require('../core/qutil');
+var posix = require('../../build/Release/posix');
+var AppSpec = require('../worker/app-spec');
+var regexp_quote = require('../core/re-quote');
+var fs = require('fs');
+var path = require('path');
+var url = require('url');
+var _ = require('underscore');
+var DirectoryRouter = require('./directory-router');
+
+module.exports = UserDirsRouter;
+/**
+ * @param runas An optional object representing the run_as object in the config 
+ *   file at this location. If not provided, apps will respect the runas setting.
+ *   If falsey, it will run as the user to whom the application belongs (as 
+ *   determined by the URL).
+ **/
+function UserDirsRouter(prefix, groups, settings, runas, dirIndex) {
+  prefix = prefix.replace(/\/$/m, ''); // strip trailing slash, if any
+  this.$prefix = prefix;
+  this.$groups = groups || [];
+  this.$settings = settings || {};
+  this.$runas = runas;
+  this.$dirIndex = dirIndex || false;
+  this.$rePath = new RegExp(
+      '^' + regexp_quote(prefix) + '/([^/]+)(?=/|$)');
+}
+
+(function() {
+  this.getAppSpec_p = function(req, res) {
+    var reqUrl = url.parse(req.url);
+    var m = this.$rePath.exec(reqUrl.pathname);
+    if (!m)
+      return Q(null);
+
+    var prefix = m[0];
+    
+    var username = m[1];
+    var runas = _.clone(this.$runas);
+    if (!runas){
+      // In user_apps mode, we don't pass in a run_as. Expected that we'd use
+      // the username.
+      runas = username;
+    } else{
+      var homeIndex;
+      // Replace any instance of ':HOME_USER:' with the username from the URL.
+      while ((homeIndex = _.indexOf(runas, ':HOME_USER:')) >= 0){    
+        runas[homeIndex] = username;
+      }
+    }
+
+    var pw = posix.getpwnam(username);
+    if (!pw)
+      return Q(null);
+
+    if (this.$groups.length) {
+      // Check if user is a member of one of the required groups
+      if (!_.intersection(posix.getgrouplist(username), this.$groups).length)
+        return Q(null);
+    }
+
+    var dir = path.join(pw.home, 'ShinyApps');
+    var logDir = path.join(pw.home, 'ShinyApps', 'log');
+    var settings = JSON.parse(JSON.stringify(this.$settings));
+
+    var dirRouter = new DirectoryRouter(dir, runas, this.$dirIndex, prefix, 
+      logDir, this.$settings, /^(\/)?log(\/)?/); // Exclude log dir.
+
+    return dirRouter.getAppSpec_p(req, res);
+  };
+}).call(UserDirsRouter.prototype);
diff --git a/lib/scheduler/scheduler-registry.js b/lib/scheduler/scheduler-registry.js
new file mode 100644
index 0000000..c24608b
--- /dev/null
+++ b/lib/scheduler/scheduler-registry.js
@@ -0,0 +1,91 @@
+/*
+ * scheduler-registry.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+var crypto = require('crypto');
+var fs = require('fs');
+var net = require('net');
+var os = require('os');
+var path = require('path');
+var moment = require('moment');
+var util = require('util');
+var Q = require('q');
+var _ = require('underscore');
+require('../core/log');
+var OutOfCapacityError = require('../core/errors').OutOfCapacity;
+var map = require('../core/map');
+var fsutil = require('../core/fsutil');
+var AppWorkerHandle = require('../worker/app-worker-handle');
+var app_worker = require('../worker/app-worker');
+var SimpleScheduler = require('./simple-scheduler');
+var events = require('events');
+
+var SchedulerRegistry = function(eventBus) {
+  events.EventEmitter.call(this);
+
+  this.$eventBus = eventBus;
+  this.$schedulers = map.create();
+
+  var self = this;
+  this.$eventBus.on('vacantSched', function(appKey){
+    // Delete this scheduler from the registry, if it exists.
+    delete self.$schedulers[appKey];
+  });
+};
+SchedulerRegistry.prototype.__proto__ = events.EventEmitter.prototype;
+module.exports = SchedulerRegistry;
+
+(function() {
+
+  this.setTransport = function(transport) {
+    this.$transport = transport;
+    //tell all the schedulers what transport to use
+    _.each(this.$schedulers, function(sched) {
+      sched.setTransport(transport);
+    });
+  };
+
+  /**
+   * Asynchronously retrieves an already-existant worker or attempts to create
+   * a new one, and returns a promise for the AppWorkerHandle.
+   *
+   * @param {AppSpec} appSpec - Contains the basic details about the app to
+   *   launch
+   * @param {String} worker - The ID of the worker which this request is
+   *   targetting. If left blank, an arbitrary worker will be selected. Ignored
+   *   when using the SimpleScheduler.
+   */
+  this.getWorker_p = function(appSpec, url, worker) {
+    var key = appSpec.getKey();
+    if (!this.$schedulers[key]){
+      //no scheduler, instantiate a simple scheduler
+      this.$schedulers[key] = new SimpleScheduler(this.$eventBus, appSpec, 
+        appSpec.settings.appDefaults.sessionTimeout);
+      if (this.$transport){
+        this.$schedulers[key].setTransport(this.$transport);
+      }
+    }  
+    
+    return this.$schedulers[key].acquireWorker_p(appSpec, url, worker); 
+  };
+
+  this.shutdown = function() {
+    _.each(this.$schedulers, function(sched) {
+      sched.shutdown();
+    });
+  };
+
+  this.dump = function(){
+    _.each(this.$schedulers, function(sched) {
+      sched.dump();
+    });
+  };
+}).call(SchedulerRegistry.prototype);
diff --git a/lib/scheduler/scheduler.js b/lib/scheduler/scheduler.js
new file mode 100644
index 0000000..b644f48
--- /dev/null
+++ b/lib/scheduler/scheduler.js
@@ -0,0 +1,400 @@
+/*
+ * scheduler.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+var child_process = require('child_process');
+var crypto = require('crypto');
+var fs = require('fs');
+var os = require('os');
+var events = require('events');
+var net = require('net');
+var map = require('../core/map');
+var crypto = require('crypto');
+var path = require('path');
+var moment = require('moment');
+var util = require('util');
+var Q = require('q');
+var _ = require('underscore');
+require('../core/log');
+var fsutil = require('../core/fsutil');
+var AppWorkerHandle = require('../worker/app-worker-handle');
+var app_worker = require('../worker/app-worker');
+var posix = require('../../build/Release/posix');
+
+/**
+ * @param appSpec the appSpec associated
+ * with this scheduler. Will be used to identify the scheduler if/when it 
+ * runs out of workers and needs to be terminated.
+ */
+var Scheduler = function(eventBus, appSpec) {
+  events.EventEmitter.call(this);
+
+  this.$eventBus = eventBus;
+  this.$workers = map.create();
+  this.$appSpec = appSpec;
+};
+Scheduler.prototype.__proto__ = events.EventEmitter.prototype;
+
+module.exports = Scheduler;
+
+// Attempt to connect to the endpoint, but don't take longer than `timeout`.
+// The shouldContinue callback can be used to short-circuit waiting (in case
+// the caller learns some other way that the connect will never succeed, i.e.
+// the process we're trying to connect to has exited).
+//
+// On success, the promise resolves to true.
+// On timeout, the promise is rejected with an error.
+// On shouldContinue() == false, the promise is rejected with an error.
+function connectEndpoint_p(endpoint, timeout, shouldContinue) {
+  var deferred = Q.defer();
+
+  // Retry using increasing intervals; when we use them all, use maxInterval
+  var intervals = [50, 50, 100, 100, 100, 100, 100, 200, 200, 300, 300, 300];
+  var maxInterval = 500;
+  
+  // The previously used interval
+  var lastInterval = 0;
+  var elapsed = 0;
+  var timeoutId = null;
+
+  attemptToConnect();
+
+  // Represents a single attempt to connect. If it fails, will try again later
+  // unless the elapsed time exceeds the timeout we were given.
+  function attemptToConnect() {
+    elapsed += lastInterval;
+
+    if (elapsed > timeout) {
+      logger.trace('Giving up on connecting to ' + endpoint.toString());
+      deferred.reject(new Error('The application took too long to respond.'));
+      return;
+    }
+    if (!deferred.promise.isPending()) {
+      return;
+    }
+    if (!shouldContinue()) {
+      logger.trace('Aborting connection attempts to ' + endpoint.toString());
+      deferred.reject(new Error('Connection attempt was aborted.'));
+      return;
+    }
+
+    logger.trace('Attempting to connect to ' + endpoint.toString());
+    endpoint.connect_p()
+    .then(function(connected) {
+      if (connected) {
+        logger.trace('Successfully connected to ' + endpoint.toString() +
+          ' after ' + elapsed + 'ms');
+        clearTimeout(timeoutId);
+        deferred.resolve(true);
+        return;
+      } else {
+        logger.trace('Failed to connect to ' + endpoint.toString());
+      }
+    })
+    .eat();
+
+    lastInterval = intervals.shift() || maxInterval;
+    setTimeout(attemptToConnect, lastInterval);
+  }
+
+  return deferred.promise;
+}
+
+(function() {
+  this.setTransport = function(transport) {
+    this.$transport = transport;
+  };
+
+  /**
+   * @param preemptive true if we're creating this worker before we're actually
+   * allocating a request to it. false if we're creating this request as we 
+   * allocate a request to it. This is needed to maintain a proper count of
+   * how many connections are active for a worker.
+   */
+  this.spawnWorker_p = function(appSpec, workerData, preemptive){
+    var self = this;
+
+    // Because appSpec will no longer uniquely identify a worker, assign a
+    // random ID to each worker for addressing purposes.
+    var workerId = crypto.randomBytes(8).toString('hex');
+
+    var defer = Q.defer();
+
+    this.$workers[workerId] = { 
+      promise : defer.promise, 
+      data : workerData || map.create()
+    }
+
+    //initialize open connections counters.
+    this.$workers[workerId].data['sockConn'] = 0;
+    this.$workers[workerId].data['httpConn'] = 0;
+    this.$workers[workerId].data['pendingConn'] = 0;
+    if (!preemptive){
+      this.$workers[workerId].data['pendingConn']++;
+    }
+
+    var logFilePath = null;
+    var doReject = function(err) {
+      err.consoleLogFile = logFilePath;
+      defer.reject(err);
+    };
+    // Once defer is fulfilled, doReject is essentially a no-op. The following
+    // (making doReject *actually* a no-op) may seem redundant, but it is
+    // necessary to prevent a memory leak!
+    // jcheng 8/22/2013: I'm not sure we still need this; the leak might have
+    // been because connectEndpoint_p (er, its predecessor) did not resolve or
+    // reject the deferred when shouldContinue was false.
+    defer.promise
+    .fin(function() {
+      doReject = function() {};
+    })
+    .eat();
+
+    this.$transport.alloc_p()
+    .then(function(endpoint) {
+
+      logFilePath = self.getLogFilePath(appSpec, endpoint);
+      if (!appSpec.runAs)
+        throw new Error("No user specified");
+
+      var pw = posix.getpwnam(appSpec.runAs);
+
+      var deleteLogFileOnExit = false;
+      logger.trace('Launching ' + appSpec.appDir + ' as ' + appSpec.runAs +
+        ' with on ' + endpoint.toString());
+      var workerPromise = app_worker.launchWorker_p(
+        appSpec, pw, endpoint, logFilePath, workerId);
+      
+      var exitPromise = workerPromise.invoke('getExit_p');
+      exitPromise
+      .fin(function() {
+        delete self.$workers[workerId];
+        if (_.size(self.$workers) === 0) {
+          // There aren't any workers left, kill this scheduler.
+          self.$eventBus.emit('vacantSched', self.$appSpec.getKey());
+        }
+        endpoint.free();
+        logger.trace(endpoint.ToString() + ' returned');
+      })
+      .then(function(status) {
+        if (deleteLogFileOnExit) {
+          if (logFilePath === '/dev/null'){
+            logger.trace("Refusing to delete /dev/null.");
+            return;
+          }
+
+          if (!appSpec.settings || !appSpec.settings.appDefaults || 
+              !appSpec.settings.appDefaults.preserveLogs){
+            logger.trace('Normal exit, deleting log file ' + logFilePath);
+
+            if(appSpec.logAsUser){
+              var rm = child_process.spawn("rm", [logFilePath], 
+                {uid: pw.uid, gid: pw.gid});
+              rm.on('close', function (code) {
+                if (code != 0){
+                  logger.warn('Failed to delete log file ' + logFilePath + ': exit code ' + code);
+                }
+              });
+            } else {
+              fs.unlink(logFilePath, function(err){
+                if (err){
+                  logger.warn('Failed to delete log file ' + logFilePath + ': ' + err.message);
+                }
+              });
+            }
+
+          } else {
+            logger.trace('Declining to delete log file of successful execution: ' + logFilePath);
+          }
+        }
+      })
+      .eat();
+
+      return workerPromise.then(function(appWorker) {
+        var appWorkerHandle = new AppWorkerHandle(appSpec, endpoint,
+          logFilePath, exitPromise,
+          _.bind(appWorker.kill, appWorker));
+
+
+        var initTimeout = 60 * 1000;
+        if (appSpec.settings.appDefaults && appSpec.settings.appDefaults.initTimeout){
+          initTimeout = appSpec.settings.appDefaults.initTimeout * 1000;
+        }
+        
+        var connectPromise = connectEndpoint_p(endpoint,
+            initTimeout, _.bind(exitPromise.isPending, exitPromise));
+
+        connectPromise
+        .then(function() {
+          /**
+           * Supplement the workerHandle with acquire and release functions.
+           */
+          (function() {
+            this.acquire = function(type){
+              if(type == 'http'){
+                self.$workers[workerId].data['httpConn']++;
+              } else if(type == 'sock'){
+                self.$workers[workerId].data['sockConn']++;
+              } else{
+                throw Error('Unrecognized type to be acquired: "' + type + '"');
+              }
+
+              //We just realized a pending connection. Decrement the counter.
+              if (self.$workers[workerId].data.pendingConn > 0){
+                self.$workers[workerId].data.pendingConn--;
+              }
+
+              logger.trace('Worker #'+workerId+' acquiring '+type+' port. ' + 
+                self.$workers[workerId].data['httpConn'] + ' open HTTP connection(s), ' + 
+                self.$workers[workerId].data['sockConn'] + ' open WebSocket connection(s).')
+
+              //clear the timer to ensure this process doesn't get destroyed.
+              var timerId = self.$workers[workerId].data['timer'];
+              if (timerId){
+                clearTimeout(timerId);                
+                self.$workers[workerId].data['timer'] = null;
+              }              
+            };
+            
+            var idleTimeout = 5 * 1000;
+            if (appSpec.settings.appDefaults && (appSpec.settings.appDefaults.idleTimeout || appSpec.settings.appDefaults.idleTimeout === 0)){
+              idleTimeout = appSpec.settings.appDefaults.idleTimeout * 1000;
+
+              if (idleTimeout > Math.pow(2,31) - 1) {
+                // Node currently only supports 32-bit setTimeouts
+                // http://stackoverflow.com/questions/16314750/settimeout-fires-immediately-if-the-delay-more-than-2147483648-milliseconds
+                logger.warn('Idle timeout value "' + appSpec.settings.appDefaults.idleTimeout + '" too high. Using the maximum of 2147483 seconds instead. Consider using a negative value if you want to disable the timeout altogether.');
+                idleTimeout = 2147483;
+              }
+            }
+
+            this.release = function(type){
+              if (!_.has(self.$workers, workerId)){
+                // Must have already been deleted. Don't need to do any work.
+                return;
+              }
+
+              if(type == 'http'){
+                self.$workers[workerId].data['httpConn']--;
+                self.$workers[workerId].data['httpConn'] = 
+                  Math.max(0, self.$workers[workerId].data['httpConn']);
+              } else if(type == 'sock'){
+                self.$workers[workerId].data['sockConn']--;
+                self.$workers[workerId].data['sockConn'] = 
+                  Math.max(0, self.$workers[workerId].data['sockConn']);
+              } else{
+                throw Error('Unrecognized type to be released: "' + type + '"');
+              }
+
+              logger.trace('Worker #'+workerId+' releasing '+type+' port. ' + 
+                self.$workers[workerId].data['httpConn'] + ' open HTTP connection(s), ' + 
+                self.$workers[workerId].data['sockConn'] + ' open WebSocket connection(s).')
+              
+              if (self.$workers[workerId].data['sockConn'] + 
+                    self.$workers[workerId].data['httpConn'] === 0) {
+                if (idleTimeout > 0){
+                  logger.trace("No clients connected to worker #" + workerId + ". Starting timer");
+                  self.$workers[workerId].data['timer'] = global.setTimeout(function() {
+                    logger.trace("Timeout expired. Killing process.");
+                    deleteLogFileOnExit = true;
+                    if (!appWorker.isRunning()) {
+                      logger.trace('Process on ' + endpoint.toString() + ' is already gone');
+                    } else {
+                      logger.trace('Interrupting process on socket ' + endpoint.toString());
+                      try {
+                        appWorker.kill();
+                      } catch (err) {
+                        logger.error('Failed to kill process on ' + endpoint.toString() + ': ' + err.message);
+                      }
+                    }
+                  }, idleTimeout);
+                }
+                else {
+                  logger.trace("No clients connected to worker #" + workerId + ", but refusing to reap due to non-positive idle_timeout.");
+                }
+              }
+            
+            };
+
+          }).call(appWorkerHandle);
+
+          // Call release initially. This won't change the value of the counter, 
+          // as they have a minimum of 0, but it will trigger a timer to ensure
+          // that this worker doesn't spin up, never get used, and run 
+          // indefinitely.
+          appWorkerHandle.release('http');
+
+          return defer.resolve(appWorkerHandle);
+          },
+          doReject
+        )
+        .done();
+
+        exitPromise.fin(function() {
+          doReject(new Error('The application exited during initialization.'));
+        })
+        .eat();
+      });
+    })
+    .fail(function(error) {
+      doReject(error);
+    })
+    .done();
+
+    return defer.promise;
+  };
+
+  this.getLogFilePath = function(appSpec, endpoint) {
+    if (!appSpec.logDir)
+      return '/dev/null'; // TODO: Windows-compatible equivalent?
+
+    var timestamp = moment().format('YYYYMMDD-HHmmss');
+    var filename = path.basename(appSpec.appDir) + '-' +
+      appSpec.runAs + '-' + timestamp + '-' + endpoint.getLogFileSuffix() +
+      '.log';
+    return path.join(appSpec.logDir, filename);
+  };
+
+  this.shutdown = function() {
+    return _.each(_.values(this.$workers), function(worker) {
+      if (worker.promise.isFulfilled()) {
+        try {
+          worker.promise.inspect().value.kill();
+        } catch (err) {
+          logger.error('Failed to kill process: ' + err.message);
+        }
+      }
+    });
+  };
+
+  function summarizeWorker(worker) {
+    return {
+      appSpec: worker.appSpec,
+      endpoint: worker.endpoint.ToString(),
+      logFilePath: worker.logFilePath
+    };
+  }
+
+  this.dump = function() {
+    logger.info('Dumping up to ' + _.size(this.$workers) + ' worker(s)');
+    _.each(_.values(this.$workers), function(worker) {
+      if (worker.promise.isFulfilled()) {
+        console.log(util.inspect(summarizeWorker(worker.promise.inspect().value),
+          false, null, true
+        ));
+      }
+      else {
+        console.log('[unresolved promise]');
+      }
+    });
+    logger.info('Dump completed')
+  };
+}).call(Scheduler.prototype);
diff --git a/lib/scheduler/simple-scheduler.js b/lib/scheduler/simple-scheduler.js
new file mode 100644
index 0000000..ac9b443
--- /dev/null
+++ b/lib/scheduler/simple-scheduler.js
@@ -0,0 +1,71 @@
+/*
+ * simple-scheduler.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+var Scheduler = require('./scheduler');
+var OutOfCapacityError = require('../core/errors').OutOfCapacity;
+var map = require('../core/map');
+var util = require('util');
+var _ = require('underscore');
+var Q = require('q');
+
+var SimpleScheduler = function(eventBus, appSpec) {
+	//run base Scheduler's constructor.
+	Scheduler.call(this, eventBus, appSpec);
+}
+module.exports = SimpleScheduler;
+
+//inherit Scheduler's methods.
+util.inherits(SimpleScheduler, Scheduler);
+
+(function() {
+	/**
+	 * Defines how this schedule will identify and select an R process to 
+	 * fulfill its requests.
+	 */
+	this.acquireWorker_p = function(appSpec, url){
+	    if (this.$workers && _.size(this.$workers) > 0) {
+	      logger.trace('Reusing existing instance');
+
+	      if (appSpec.settings.scheduler.simple && 
+	      	appSpec.settings.scheduler.simple.maxRequests){
+	      	var hardLimit = appSpec.settings.scheduler.simple.maxRequests;
+	      	if (hardLimit == 0){
+	      		hardLimit = Infinity;
+	      	} 
+
+	      	var thisWorker = this.$workers[_.keys(this.$workers)[0]];
+	      	var conns = thisWorker.data.sockConn + 
+	      							thisWorker.data.pendingConn;
+
+	      	if (conns < hardLimit){
+	      		// We have room for at least 1 more request
+	      		return Q.resolve(this.$workers[Object.keys(this.$workers)[0]].promise);
+	      	}
+	      	else if (url != 'ws' && url != '/'){
+	      		// We're not trying to create a new session, fulfill all HTTP traffic.
+	      		return Q.resolve(this.$workers[Object.keys(this.$workers)[0]].promise);
+	      	}
+	      	else {
+	      		throw new OutOfCapacityError("This application cannot handle " + 
+	      			"another connection.");
+	      	}
+	      } else{
+	      	// No limit specified, just direct an unlimited amount of traffic here.
+	      	return Q.resolve(this.$workers[Object.keys(this.$workers)[0]].promise);
+	      }
+	    }
+
+	    logger.trace('Spawning new instance');
+	    return this.spawnWorker_p(appSpec);
+	};
+
+}).call(SimpleScheduler.prototype);
diff --git a/lib/server/server.js b/lib/server/server.js
new file mode 100644
index 0000000..bfb830e
--- /dev/null
+++ b/lib/server/server.js
@@ -0,0 +1,203 @@
+/*
+ * server.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+var assert = require('assert');
+var events = require('events');
+var http = require('http');
+var util = require('util');
+var _ = require('underscore');
+var map = require('../core/map');
+
+module.exports = Server;
+/**
+ * Presents the same events as a regular NodeJS HTTP server, but can listen on
+ * multiple host/port combinations at the same time. If distinguishing between
+ * the different hosts/ports is important, then look at request.host and
+ * request.port in the event listener.
+ *
+ * You can change the set of hosts/ports that are listened to on the fly by
+ * calling setAddresses multiple times. Any host/port combinations that are
+ * already being listened on are undisturbed; any obsoleted servers are shut
+ * down; and any new host/ports have new servers instantiated and started.
+ */
+function Server() {
+  var this_Server = this;
+  events.EventEmitter.call(this);
+
+  this.$wildcards = map.create();
+  this.$hosts = map.create();
+  this.$eventNames = [];
+
+  // When a caller adds a new listener, we need to see if it's an event name
+  // we've never seen before; if so, we need to make sure this event gets
+  // forwarded by all current and future HTTP server instances.
+  this.on('newListener', function(eventName, listener) {
+    // Never forward newListener, too confusing.
+    if (eventName == 'newListener')
+      return;
+
+    // If this is an event name we've seen before, no need to do anything.
+    if (_.contains(this.$eventNames, eventName))
+      return;
+
+    // Make all current servers forward this event.
+    _.each(_.values(this.$wildcards).concat(_.values(this.$hosts)),
+      function(server) {
+        forwardEvent(server, this_Server, eventName);
+      }
+    );
+
+    // Ensure all future servers forward this event.
+    this.$eventNames.push(eventName);
+  });
+}
+
+util.inherits(Server, events.EventEmitter);
+
+(function() {
+  this.setAddresses = function(addresses) {
+    var wildcardKeys = [];
+    var hostKeys = [];
+    _.each(addresses, function(address) {
+      if (address.address === '*' || address.address === '0.0.0.0')
+        wildcardKeys.push('0.0.0.0' + ':' + address.port);
+      else
+        hostKeys.push(address.address + ':' + address.port);
+    });
+
+    // It's possible for there to be duplicate address/port combos because
+    // you can have server scopes that listen on the same address/port but
+    // are distinguished by hostname (server_name)
+    wildcardKeys = _.uniq(wildcardKeys);
+    hostKeys = _.uniq(hostKeys);
+
+    var toCloseW = _.difference(_.keys(this.$wildcards), wildcardKeys);
+    var toOpenW = _.difference(wildcardKeys, _.keys(this.$wildcards));
+    var toCloseH = _.difference(_.keys(this.$hosts), hostKeys);
+    var toOpenH = _.difference(hostKeys, _.keys(this.$hosts));
+
+    this.$close(this.$wildcards, toCloseW);
+    this.$close(this.$hosts, toCloseH);
+
+    this.$open(this.$wildcards, toOpenW);
+    this.$open(this.$hosts, toOpenH);
+  };
+
+  this.destroy = function() {
+    this.$close(this.$wildcards, _.keys(this.$wildcards));
+    this.$close(this.$hosts, _.keys(this.$hosts));
+  };
+
+  this.$close = function(table, keys) {
+    function doClose(server, key) {
+      if (!server.listening)
+        return;
+
+      logger.info('Stopping listener on ' + key);
+      try {
+        server.close(function(err) {
+          if (err) {
+            logger.error(
+              'Error closing HTTP listener at ' + key + ': ' + err.message);
+          }
+        });
+      } catch(ex) {
+        logger.error(
+          'Error closing HTTP listener at ' + key + ': ' + ex.message);
+      }
+    }
+
+    var server;
+    _.each(keys, function(key) {
+      assert(_.has(table, key));
+      server = table[key];
+      doClose(server, key);
+      removeFromTable(table, key, server);
+    });
+  };
+
+  this.$open = function(table, keys) {
+    var this_Server = this;
+
+    function doOpen(table, key) {
+      assert(!_.has(table, key));
+
+      var match = /^([^:]+):(\d+)$/.exec(key);
+      assert(match, 'Invalid HTTP server key: ' + key);
+
+      var hostname = match[1];
+      var port = +(match[2]);
+
+      var server = http.createServer();
+
+      server.on('close', function() {
+        // If the server closes, make a note of it and delete it from its table
+        server.listening = false;
+        removeFromTable(table, key, server);
+      });
+      server.on('listening', function() {
+        // Once the server starts listening successfully, make a note of it
+        server.listening = true;
+      });
+      server.on('error', function(err) {
+        if (!server.listening) {
+          // If server errored before successfully binding, we won't ever get a
+          // close event and need to delete now.
+          removeFromTable(table, key, server);
+        }
+        // Annotate the error with some additional info so other error event
+        // listeners can get to it if needed.
+        err.listenKey = key;
+        err.source = server;
+      });
+
+      this_Server.$forwardAll(server);
+
+      logger.info('Starting listener on ' + key);
+      server.listen(port, hostname, function(err) {
+        if (err)
+          logger.error('Error listening on ' + key + ': ' + err.message);
+      });
+      table[key] = server;
+    }
+
+    _.each(keys, function(key) {
+      doOpen(table, key);
+    });
+  };
+
+  function removeFromTable(table, key, server) {
+    if (table[key] === server)
+      delete table[key];
+  };
+
+  this.$forwardAll = function(server) {
+    var this_Server = this;
+
+    function emitter(evt) {
+      this.emit.apply(this, arguments);
+    }
+
+    _.each(this.$eventNames, function(eventName) {
+      forwardEvent(server, this_Server, eventName);
+    });
+  };
+}).call(Server.prototype);
+
+
+function forwardEvent(from, to, eventName) {
+  function emitter(evt) {
+    this.emit.apply(this, arguments);
+  }
+  
+  from.on(eventName, _.bind(emitter, to, eventName));
+}
diff --git a/lib/transport/shared.js b/lib/transport/shared.js
new file mode 100644
index 0000000..af6439d
--- /dev/null
+++ b/lib/transport/shared.js
@@ -0,0 +1,29 @@
+/*
+ * shared.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+var crypto = require('crypto');
+var _ = require('underscore');
+
+exports.BaseEndpoint = BaseEndpoint;
+function BaseEndpoint() {
+  // The purpose of this shared secret is to make a Shiny app only respond to
+  // requests that come from the process that spawned it, rather than opening
+  // it up to just anyone.
+  this.$sharedSecret = crypto.randomBytes(16).toString('hex');
+}
+
+(function() {
+  this.getSharedSecret = function() {
+    return this.$sharedSecret;
+  };
+}).call(BaseEndpoint.prototype);
diff --git a/lib/transport/tcp.js b/lib/transport/tcp.js
new file mode 100644
index 0000000..21522fc
--- /dev/null
+++ b/lib/transport/tcp.js
@@ -0,0 +1,154 @@
+/*
+ * tcp.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+var net = require('net');
+var util = require('util');
+var websocket = require('faye-websocket');
+var Q = require('q');
+var _ = require('underscore');
+var BaseEndpoint = require('./shared').BaseEndpoint;
+
+exports.Transport = Transport;
+function Transport() {
+}
+
+(function() {
+
+  this.setSocketDir = function(path) {
+    // TCP transport doesn't use socket dir
+  };
+
+  /**
+   * Return a port number that we believe to be unused. When finished, call
+   * freePort to make the port available again.
+   *
+   * (Actually this implementation lets the OS pick a random port, and then
+   * checks if the port is in use. If so, it retries. It's not actually
+   * necessary to use freePort with this implementation but it seems like
+   * a good idea to keep that discipline in case we later need to switch
+   * to a preallocated list of ports for some reason.)
+   */
+  this.alloc_p = function() {
+    var self = this;
+    var deferred = Q.defer();
+
+    var tries = arguments.length > 0 ? arguments[0] : 0;
+
+    try {
+      var server = net.createServer(function(conn) {conn.destroy();});
+      server.on('error', function(e) {
+        
+        try {
+          server.close();
+        } catch (closeErr) {
+        }
+
+        try {
+          if (e.code == 'EADDRINUSE') {
+            logger.info('Could not bind port: ' + e.message);
+            if (tries == 5) {
+              logger.error('Giving up on binding port after 5 tries');
+              deferred.reject(new Error("Couldn't find a free port"));
+            } else {
+              deferred.resolve(self.alloc_p(tries+1));
+            }
+          } else {
+            deferred.reject(e);
+          }
+        } catch (err) {
+          deferred.reject(err);
+        }
+      });
+      server.listen(0, '127.0.0.1', function() {
+        var port = server.address().port;
+        server.close();
+        deferred.resolve(new Endpoint(port));
+      });
+    } catch (ex) {
+      deferred.reject(ex);
+    }
+
+
+    return deferred.promise;
+  };
+
+}).call(Transport.prototype);
+
+
+function Endpoint(port) {
+  BaseEndpoint.call(this);
+  this.$port = port;
+}
+util.inherits(Endpoint, BaseEndpoint);
+
+(function() {
+
+  // Return true if there is an active listener on the port
+  this.connect_p = function() {
+    var deferred = Q.defer();
+    var client = net.connect({
+      host: '127.0.0.1',
+      port: this.$port
+    });
+    client.on('connect', function() {
+      deferred.resolve(true);
+      client.destroy();
+    });
+    client.on('error', function() {
+      deferred.resolve(false);
+      client.destroy();
+    });
+    return deferred.promise;
+  };
+
+  // Get the object that can be used as the "target" option for an http-proxy
+  this.getHttpProxyTarget = function() {
+    return {
+      host: '127.0.0.1',
+      port: this.$port
+    };
+  };
+
+  // Return a new websocket.Client instance that's pointed at the target
+  this.createWebSocketClient = function(path, headers) {
+    return new websocket.Client('ws://127.0.0.1:' + this.$port + path, undefined,
+      {
+        headers: _.extend(
+          {'Shiny-Shared-Secret': this.getSharedSecret()},
+          headers || {}
+        )
+      }
+    );
+  };
+
+  this.getAppWorkerPort = function() {
+    return this.$port + '';
+  };
+
+  this.getLogFileSuffix = function() {
+    return this.getAppWorkerPort();
+  };
+
+  this.toString = function() {
+    return "port " + this.$port;
+  };
+
+  // differs from toString by capitalization ;)
+  this.ToString = function() {
+    return "Port " + this.$port;
+  };
+
+  this.free = function() {
+  };
+
+}).call(Endpoint.prototype);
diff --git a/lib/transport/unix-socket.js b/lib/transport/unix-socket.js
new file mode 100644
index 0000000..4cc40f0
--- /dev/null
+++ b/lib/transport/unix-socket.js
@@ -0,0 +1,129 @@
+/*
+ * unix-socket.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+var crypto = require('crypto');
+var fs = require('fs');
+var net = require('net');
+var path = require('path');
+var util = require('util');
+var websocket = require('faye-websocket');
+var Q = require('q');
+var _ = require('underscore');
+var fsutil = require('../core/fsutil');
+var BaseEndpoint = require('./shared').BaseEndpoint;
+
+exports.Transport = Transport;
+function Transport() {
+  this.$socketDir = null;
+}
+
+(function() {
+
+  this.setSocketDir = function(socketDir) {
+    if (!socketDir) {
+      socketDir = '/var/shiny-server/sockets'
+    }
+
+    this.$socketDir = socketDir;
+    logger.info('Socket dir: ' + socketDir);
+    if (!fsutil.directoryExistsSync(socketDir)) {
+      logger.info('Socket dir does not exist, will create it');
+      fs.mkdirSync(socketDir, 0733);
+      // Not sure why but mkdirSync's mode parameter doesn't have the desired
+      // effect. Do a chmodSync to ensure the perms get set correctly.
+      fs.chmodSync(socketDir, 0733);
+    }
+  };
+
+  /**
+   * Return a random filename to go in $socketDir.
+   */
+  this.alloc_p = function() {
+    var self = this;
+
+    return Q.nfcall(crypto.randomBytes, 16)
+    .then(function(buf) {
+      return new Endpoint(self.$socketDir, buf.toString('hex'));
+    }); 
+  };
+
+}).call(Transport.prototype);
+
+
+function Endpoint(socketDir, name) {
+  BaseEndpoint.call(this);
+  this.$socketPath = path.join(socketDir, name + '.sock');
+  this.$sockName = name.substring(0, 12);
+}
+util.inherits(Endpoint, BaseEndpoint);
+
+(function() {
+
+  // Return true if there is an active listener on the port
+  this.connect_p = function() {
+    var deferred = Q.defer();
+    var client = net.connect({
+      path: this.$socketPath
+    });
+    client.on('connect', function() {
+      deferred.resolve(true);
+      client.destroy();
+    });
+    client.on('error', function() {
+      deferred.resolve(false);
+      client.destroy();
+    });
+    return deferred.promise;
+  };
+
+  // Get the object that can be used as the "target" option for an http-proxy
+  this.getHttpProxyTarget = function() {
+    return {
+      host: '127.0.0.1',
+      socketPath: this.$socketPath
+    };
+  };
+
+  // Return a new websocket.Client instance that's pointed at the target
+  this.createWebSocketClient = function(path, headers) {
+    return new websocket.Client('ws://127.0.0.1:' + this.$port + path, undefined,
+      {
+        socketPath: this.$socketPath,
+        headers: _.extend(
+          {'Shiny-Shared-Secret': this.getSharedSecret()},
+          headers || {}
+        )
+      });
+  };
+
+  this.getAppWorkerPort = function() {
+    return this.$socketPath;
+  };
+
+  this.getLogFileSuffix = function() {
+    return this.$sockName;
+  };
+
+  this.toString = function() {
+    return "socket " + this.$sockName;
+  };
+
+  // differs from toString by capitalization ;)
+  this.ToString = function() {
+    return "Socket " + this.$sockName;
+  };
+
+  this.free = function() {
+  };
+
+}).call(Endpoint.prototype);
diff --git a/lib/worker/app-spec.js b/lib/worker/app-spec.js
new file mode 100644
index 0000000..061629a
--- /dev/null
+++ b/lib/worker/app-spec.js
@@ -0,0 +1,36 @@
+/*
+ * app-spec.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+var _ = require('underscore');
+
+var AppSpec = function(appDir, runAs, prefix, logDir, settings) {
+  this.appDir = appDir;
+  this.runAs = runAs;
+  this.prefix = prefix;
+  this.logDir = logDir;
+  this.settings = settings;
+  this.logAsUser = settings.logAsUser;
+};
+module.exports = AppSpec;
+
+(function() {
+
+  this.getKey = function() {
+    return this.appDir + "\n" +
+      this.runAs + "\n" +
+      this.prefix + "\n" +
+      this.logDir + "\n" +
+      JSON.stringify(this.settings);
+  };
+
+}).call(AppSpec.prototype);
diff --git a/lib/worker/app-worker-handle.js b/lib/worker/app-worker-handle.js
new file mode 100644
index 0000000..e8ff0a1
--- /dev/null
+++ b/lib/worker/app-worker-handle.js
@@ -0,0 +1,40 @@
+/*
+ * app-worker-handle.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+var events = require('events');
+var util = require('util');
+
+var AppWorkerHandle = function(appSpec, endpoint, logFilePath,
+    exitPromise, kill) {
+
+  events.EventEmitter.call(this);
+  this.appSpec = appSpec;
+  this.endpoint = endpoint;
+  this.logFilePath = logFilePath;
+  this.exitPromise = exitPromise;
+  this.kill = kill;
+};
+module.exports = AppWorkerHandle;
+
+util.inherits(AppWorkerHandle, events.EventEmitter);
+
+(function() {
+  /**
+   * Kill the worker using the given signal (defaults to 'SIGABRT' on OSX, 
+   * 'SIGINT' on all other platforms). This is
+   * actually overridden in the constructor, this stub is just here for
+   * documentation purposes.
+   */
+  this.kill = function(signal) {
+  };
+
+}).call(AppWorkerHandle.prototype);
diff --git a/lib/worker/app-worker.js b/lib/worker/app-worker.js
new file mode 100644
index 0000000..9f558fa
--- /dev/null
+++ b/lib/worker/app-worker.js
@@ -0,0 +1,430 @@
+/*
+ * app-worker.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+/**
+ * An AppWorker is responsible for:
+ *
+ * - Launching a Shiny application with the proper user/group permissions
+ * - Ensuring that stderr is written to the specified path
+ * - Returning a promise that resolves when the worker process exits
+ */
+
+var child_process = require('child_process');
+var fs = require('fs');
+var path = require('path');
+var util = require('util');
+var bash = require('bash');
+var Q = require('q');
+var _ = require('underscore');
+var map = require('../core/map');
+var paths = require('../core/paths');
+var permissions = require('../core/permissions');
+var split = require('split');
+var posix = require('../../build/Release/posix');
+
+var rprog = process.env.R || 'R';
+var scriptPath = paths.projectFile('R/SockJSAdapter.R');
+
+function exists_p(path) {
+  var defer = Q.defer();
+  fs.exists(path, function(exists) {
+    defer.resolve(exists);
+  });
+  return defer.promise;
+}
+
+function spawnUserLog_p(pw, appSpec, endpoint, logFilePath, workerId) {
+  var prom = Q.defer();
+  var logDir = path.dirname(logFilePath); 
+
+  // Ensure that the log directory exists.
+  var rm = child_process.spawn(paths.projectFile('scripts/mkdir.sh'), [logDir, pw.uid, pw.gid], 
+    {uid: pw.uid, gid: pw.gid});
+  rm.on('close', function (code) {
+    if (code != 0){
+      var err = "Failed to create log directory: " + logDir +", " + pw.uid + ":" + pw.gid;
+      logger.error(err);
+      prom.reject(err);
+    }
+
+    // Have R do the logging
+    var worker = new AppWorker(appSpec, endpoint, logFilePath, workerId, 
+      pw.home);
+    prom.resolve(worker);
+  });
+
+  return prom.promise;
+}
+
+
+/**
+ * Begins launching the worker; returns a promise that resolves when
+ * the worker is constructed (doesn't necessarily mean the process has
+ * actually begun running though).
+ *
+ * @param {AppSpec} appSpec - Contains the basic details about the app to
+ *   launch
+ * @param pw - the user info, a result of `posix.getpwnam()`
+ * @param {Endpoint} endpoint - The endpoint that the Shiny app should
+ *   listen on.
+ * @param {String} logFilePath - The file path to write stderr to.
+ */
+function launchWorker_p(appSpec, pw, endpoint, logFilePath, workerId) {
+   if (!pw)
+    return Q.reject(new Error("User " + appSpec.runAs + " does not exist"));
+
+  if (!pw.home)
+    return Q.reject(new Error("User " + appSpec.runAs + 
+      " does not have a home directory"));
+
+  if (!appSpec.appDir)
+    return Q.reject(new Error("No app directory specified"));
+
+
+  return exists_p(appSpec.appDir).then(function(exists) { // TODO: does this need to be as user?
+    if (!exists) {
+      var err = new Error("App dir " + appSpec.appDir + " does not exist");
+      err.code = 'ENOTFOUND';
+      throw err;
+    }
+   
+    if (!appSpec.logAsUser){
+      var logDir = path.dirname(logFilePath);
+      // Ensure that the log directory exists.
+      try {
+        fs.mkdirSync(logDir, '755');
+        fs.chownSync(logDir, pw.uid, pw.gid);
+      } catch (ex) {
+        try {
+          var stat = fs.statSync(logDir);
+          if (!stat.isDirectory()) {
+            logger.error('Log directory existed, was a file: ' + logDir);
+            logDir = null;
+          }
+        } catch (ex2) {
+          logger.error('Log directory creation failed: ' + ex2.message);
+          logDir = null;
+        }
+      }
+
+      // Manage the log file as root
+      // Open the log file asynchronously, then create the worker
+      return Q.nfcall(fs.open, logFilePath, 'a', 0660).then(function(logStream) {
+        fs.fchown(logStream, pw.uid, pw.gid, function(err) {
+          if (err)
+            logger.error('Error attempting to change permissions on log file at ' + logFilePath + ': ' + err.message);
+        });
+
+        // Create the worker; when it exits (or fails to start), close
+        // the logStream.
+        var worker = new AppWorker(appSpec, endpoint, logStream, workerId, 
+          pw.home);
+        worker.getExit_p()
+        .fin(function() {
+          fs.close(logStream, function(err) {
+            if (err)
+              logger.error("Couldn't close logStream: " + err.message);
+          });
+        })
+        .eat();
+
+        return worker;
+      });
+    } else {
+      return spawnUserLog_p(pw, appSpec, endpoint, logFilePath, workerId);
+    }
+  });
+};
+exports.launchWorker_p = launchWorker_p;
+
+/**
+ * Like launchWorker_p, but the promise it returns doesn't resolve until
+ * the worker process exits.
+ */
+function runWorker_p(appSpec, endpoint, logFilePath) {
+  return launchWorker_p(appSpec, endpoint, logFilePath).invoke('getExit_p');
+};
+exports.runWorker_p = runWorker_p;
+
+
+/**
+ * Creates the top-level (system) bookmark state directory, then the user's
+ * bookmark state directory, and then the app's bookmark state directory.
+ */
+function createBookmarkStateDirectory_p(bookmarkStateDir, username) {
+  if (bookmarkStateDir === null || bookmarkStateDir === "") {
+    return Q();
+  }
+
+  // Capitalize first character
+  function capFirst(str) {
+    return str.charAt(0).toUpperCase() + str.slice(1);
+  }
+
+  function createDir_p(dir, mode, username, label) {
+    if (label) {
+      label = label + ' ';
+    } else {
+      label = '';
+    }
+
+    return Q.nfcall(fs.mkdir, dir, mode)
+    .then(function() {
+      logger.info(
+        capFirst('created ' + label + 'bookmark state directory: ' + dir)
+      );
+    })
+    .then(function() {
+      // chown if username was supplied
+      if (typeof(username) === 'string') {
+        var pw = posix.getpwnam(username);
+        return Q.nfcall(fs.chown, dir, pw.uid, pw.gid);
+      }
+    })
+    .fail(function(err) {
+      return Q.nfcall(fs.stat, dir)
+      .then(function(stat) {
+        if (!stat.isDirectory()) {
+          logger.error(
+            capFirst(label + 'bookmark state directory existed, was a file: ' + dir)
+          );
+          throw err;
+        }
+      })
+      .fail(function(err2) {
+        logger.error(
+          capFirst(label + 'bookmark state directory creation failed: ' + dir)
+        );
+        throw err2;
+      });
+    });
+  }
+
+  var userBookmarkStateDir = path.join(bookmarkStateDir, username);
+
+  return createDir_p(bookmarkStateDir, '711')
+  .then(function() {
+    createDir_p(userBookmarkStateDir, '700', username, 'user');
+  })
+  .fail(function(err) {
+    throw err;
+  });
+}
+
+
+/**
+ * An AppWorker models a single R process that is running a Shiny app.
+ *
+ * @constructor
+ * @param {AppSpec} appSpec - Contains the basic details about the app to
+ *   launch
+ * @param {String} endpoint - The transport endpoint the app should listen on
+ * @param {Stream} logStream - The stream to dump stderr to, or the path to 
+ *   the file where the logging should happen. If just the path, pass it in
+ *   to the R proc to have R handle the logging itself.
+ */
+var AppWorker = function(appSpec, endpoint, logStream, workerId, home) {
+  this.$dfEnded = Q.defer();
+  var self = this;
+
+  this.exited = false;
+  this.$pid = null;
+
+  // Spawn worker process via su, to ensure proper setgid, initgroups, setuid,
+  // etc. are called correctly.
+  //
+  // We use stdin to tell SockJSAdapter what app dir, port, etc. to use, so
+  // that non-root users on the system can't use ps to discover what apps are
+  // available and on what ports.
+
+  logger.trace("Starting R");
+
+  try {
+    // Run R
+    var executable, args;
+    var switchUser = appSpec.runAs !== null &&
+        permissions.getProcessUser() !== appSpec.runAs;
+
+    // Set mode to either 'shiny' or 'rmd'
+    var mode = 'shiny';
+    if (appSpec.settings && appSpec.settings.mode){
+      mode = appSpec.settings.mode;
+    }
+
+    if (!switchUser && permissions.isSuperuser())
+      throw new Error("Aborting attempt to launch R process as root");
+
+    if (switchUser) {
+      executable = 'su';
+      args = [
+        '-p',
+        '--',
+        appSpec.runAs,
+        '-c',
+        'cd ' + bash.escape(appSpec.appDir) + ' && ' + bash.escape(rprog) + " --no-save --slave -f " + bash.escape(scriptPath)
+      ];
+      
+      if (process.platform === 'linux') {
+        // -s option not supported by OS X (or FreeBSD, or Sun)
+        args = ['-s', '/bin/bash', '--login'].concat(args);
+      } else {
+        // Other platforms don't clear out env vars, so simulate user env
+        args.unshift('-');
+      }
+    } else {
+      executable = rprog;
+      args = ['--no-save', '--slave', '-f', scriptPath];
+    }
+
+    // The file where R should send stderr, or empty if it should leave it alone.
+    var logFile = '';
+    if (_.isString(logStream)){
+      logFile = logStream;
+      logStream = 'ignore'; // Tell the child process to drop stderr
+      logger.trace('Asking R to send stderr to ' + logFile);
+    }
+
+    var self = this;
+
+    Q.nfcall(fs.stat, appSpec.appDir)
+    .then(function(stat){
+      if (!stat.isDirectory()){
+        throw new Error("Trying to launch an application that is not a directory: " + 
+          appSpec.appDir);
+      }
+
+      return createBookmarkStateDirectory_p(
+        appSpec.settings.appDefaults.bookmarkStateDir,
+        appSpec.runAs
+      );
+    })
+    .then(function() {
+      self.$proc = child_process.spawn(executable, args, {
+        stdio: ['pipe', 'pipe', logStream],
+        cwd: appSpec.appDir,
+        env: map.compact({
+          'HOME' : home,
+          'LANG' : process.env['LANG'],
+          'PATH' : process.env['PATH']
+        }),
+        detached: true  // So that we can send SIGINT not just to su but to the
+                        // R process that it spawns as well
+      });
+      self.$proc.on('exit', function(code, signal) {
+        self.exited = true;
+        self.$dfEnded.resolve({code: code, signal: signal});
+      });
+      self.$proc.stdin.on('error', function(){
+        logger.warn("Unable to write to Shiny process. Attempting to kill it.");
+        self.kill();
+      });
+      self.$proc.stdin.end(
+        appSpec.appDir + '\n' +
+        endpoint.getAppWorkerPort() + '\n' +
+        (appSpec.settings.gaTrackingId || '') + '\n' +
+        endpoint.getSharedSecret() + '\n' +
+        SHINY_SERVER_VERSION + '\n' +
+        workerId + '\n' +
+        mode + '\n' +
+        paths.projectFile('ext/pandoc') + '\n' +
+        logFile + '\n' +
+        appSpec.settings.appDefaults.disableProtocols.join(",") + '\n' +
+        appSpec.settings.appDefaults.reconnect + '\n' +
+        appSpec.settings.appDefaults.sanitizeErrors + '\n' +
+        appSpec.settings.appDefaults.bookmarkStateDir + '\n'
+      );
+      self.$proc.stdout.pipe(split()).on('data', function(line){
+        var match = null;
+        if (line.match(/^Starting Shiny with process ID: '(\d+)'$/)){
+          var pid = parseInt(
+            line.match(/^Starting Shiny with process ID: '(\d+)'$/)[1]);
+          self.$pid = pid;
+          logger.trace("R process spawned with PID " + pid);
+        } else if (match = line.match(/^Shiny version: (\d+)\.(\d+)\.(\d+)(\.(\d+))?$/)){
+          logger.trace("Using shiny version: " + match[1] + "." + match[2] + 
+              "." + match[3] + ((match[5])?"."+match[5]:""));
+        } else if (match = line.match(/^R version: (\d+)\.(\d+)\.(\d+)$/)){
+          logger.trace("Using R version: " + match[1] + "." + match[2] + 
+              "." + match[3]);
+        } else if (match = line.match(/^rmarkdown version: (\d+)\.(\d+)\.(\d+)(\.(\d+))?$/)){
+          logger.trace("Using rmarkdown version: " + match[1] + "." + match[2] + 
+              "." + match[3] + ((match[5])?"."+match[5]:""));
+        }
+      });
+    })
+    .fail(function(err){
+      // An error occured spawning the process, could be we tried to launch a file
+      // instead of a directory.
+      logger.warn(err.message);
+      
+      self.$dfEnded.resolve({code: -1, signal: null});
+    })
+    .done();
+  } catch (e) {
+    logger.trace(e);
+    this.$dfEnded.reject(e);
+  }
+};
+
+(function() {
+
+  /**
+   * Returns a promise that is resolved when the process exits.
+   * If the process terminated normally, code is the final exit
+   * code of the process, otherwise null. If the process
+   * terminated due to receipt of a signal, signal is the string
+   * name of the signal, otherwise null.
+   */
+  this.getExit_p = function() {
+    return this.$dfEnded.promise;
+  };
+
+  this.isRunning = function() {
+    return !this.exited;
+  };
+
+  /**
+   * Attempts to kill the process using the signal provided by
+   * sending a SIGINT signal to the R process; if the process
+   * is still alive after a few seconds, we send SIGTERM.
+   */
+  this.kill = function() {
+    var exitPromise = this.getExit_p();
+    if (!exitPromise.isPending())
+      return;
+
+    var pid = this.$pid;
+    logger.trace('Sending SIGINT to ' + pid);
+
+    try {
+      process.kill(pid, 'SIGINT');
+      
+      var timerId = setTimeout(function() {
+        logger.debug('Process ' + pid + ' did not exit on SIGINT; sending SIGTERM');
+        try {
+          process.kill(pid, 'SIGTERM');
+        } catch (e) {
+          logger.trace("Failure sending SIGTERM: " + e);
+        }
+      }, 20000); // TODO: Should this be configurable?
+
+      exitPromise
+      .then(function() {
+        clearTimeout(timerId);
+      })
+      .eat();
+    } catch (e) {
+      logger.trace("Failure sending SIGINT: " + e);
+    }
+  };
+}).call(AppWorker.prototype);
diff --git a/lib/worker/run-as.js b/lib/worker/run-as.js
new file mode 100644
index 0000000..f626974
--- /dev/null
+++ b/lib/worker/run-as.js
@@ -0,0 +1,56 @@
+/*
+ * run-as.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+// Intended to be launched via child_process.exec. It merely drops
+// permissions and runs the given command/args. stdio is passed
+// through, and the exit code of the subcommand will also be the
+// exit code of this command.
+//
+// Usage: run-as.js <user> <command> <args>
+
+var unixgroups = require('unixgroups');
+var child_process = require('child_process');
+var _ = require('underscore');
+var posix = require('../../build/Release/posix');
+var map = require('../core/map');
+
+
+// Drop permissions first
+var user = process.argv[2];
+unixgroups.initgroups(user, true); // also calls setgid
+process.setuid(user);
+
+
+var cmd = process.argv[3];
+var args = _.rest(process.argv, 4);
+
+// Change a few env variables to match user's identity
+var env = map.create();
+var pwd = posix.getpwnam(user);
+env.USER = user;
+env.LOGNAME = user;
+env.HOME = pwd.home;
+env.SHELL = pwd.shell;
+
+env.SHINY_PORT = process.env.SHINY_PORT;
+env.SHINY_APP = process.env.SHINY_APP;
+env.SHINY_GAID = process.env.SHINY_GAID;
+env.SOCKJSADAPTER = process.env.SOCKJSADAPTER;
+
+var proc = child_process.spawn(cmd, args, {
+  stdio: 'inherit',
+  env: env
+});
+
+proc.on('exit', function(code) {
+  process.exit(code);
+});
diff --git a/manual.test/config/bad1.config b/manual.test/config/bad1.config
new file mode 100644
index 0000000..c21ba63
--- /dev/null
+++ b/manual.test/config/bad1.config
@@ -0,0 +1,2 @@
+hi {
+}
\ No newline at end of file
diff --git a/manual.test/config/bad2.config b/manual.test/config/bad2.config
new file mode 100644
index 0000000..7e0161a
--- /dev/null
+++ b/manual.test/config/bad2.config
@@ -0,0 +1 @@
+run_as;
\ No newline at end of file
diff --git a/manual.test/config/bad3.config b/manual.test/config/bad3.config
new file mode 100644
index 0000000..14f7f08
--- /dev/null
+++ b/manual.test/config/bad3.config
@@ -0,0 +1 @@
+run_as foo bar baz;
\ No newline at end of file
diff --git a/manual.test/config/good.config b/manual.test/config/good.config
new file mode 100644
index 0000000..31708dd
--- /dev/null
+++ b/manual.test/config/good.config
@@ -0,0 +1,46 @@
+run_as shiny;
+#access_log /var/shiny-server/access_log tiny;
+
+server {
+  listen 80;
+  #server_name localhost;
+
+  location /users {
+    user_apps on;
+  }
+
+  location / {
+    #runas nobody;
+    #app_dir /var/shiny-server/www;
+    redirect http://www.rstudio.com/;
+  }
+
+  location /08_html {
+    app_dir /home/jcheng/ShinyApps/08_html;
+    run_as jcheng;
+    log_dir /home/jcheng/ShinyApps/08_html/log;
+  }
+}
+
+server {
+  listen 81;
+
+  location / {
+    user_apps on;
+  }
+
+  location / {
+    app_dir /home/jcheng/ShinyApps/08_html;
+    run_as jcheng;
+    log_dir /home/jcheng/ShinyApps/08_html/log;
+  }
+}
+
+server {
+  listen 82;
+  location /site {
+    site_dir /Users/jcheng/ShinyApps;
+    run_as jcheng;
+    log_dir /Users/jcheng/ShinyApps/log;
+  }
+}
diff --git a/manual.test/config/schemaBad1.config b/manual.test/config/schemaBad1.config
new file mode 100644
index 0000000..526b377
--- /dev/null
+++ b/manual.test/config/schemaBad1.config
@@ -0,0 +1,3 @@
+foo {
+  param Number name "desc";
+}
\ No newline at end of file
diff --git a/manual.test/config/schemaBad2.config b/manual.test/config/schemaBad2.config
new file mode 100644
index 0000000..667cf83
--- /dev/null
+++ b/manual.test/config/schemaBad2.config
@@ -0,0 +1,4 @@
+foo {
+  param Number name "desc";
+  at *;
+}
\ No newline at end of file
diff --git a/manual.test/loadtest-xhr.js b/manual.test/loadtest-xhr.js
new file mode 100644
index 0000000..2364621
--- /dev/null
+++ b/manual.test/loadtest-xhr.js
@@ -0,0 +1,56 @@
+#!../bin/node/bin/node
+
+var http = require('http');
+var parse = require('url').parse;
+http.globalAgent.maxSockets = 750;
+
+var appUrl = "http://localhost:3838/01_hello/";
+
+process.on('uncaughtException', function (err) {
+  console.log('Caught exception: ' + err);
+});
+
+function startSession(appUrl, callback) {
+  require('crypto').randomBytes(12, function(ex, buf) {
+    if (ex) {
+      throw ex;
+    }
+    var token = buf.toString('hex');
+    var sockjsEndpoint = appUrl + "__sockjs__/123/" + token + "/";
+    callback(sockjsEndpoint);
+  });
+}
+
+var requestId = 0;
+function go() {
+  startSession(appUrl, function(sockjsUrl) {
+    var id = requestId++;
+    var opts = parse(sockjsUrl + "xhr_streaming");
+    opts.method = "POST";
+    var req = http.request(opts, function(res) {
+      console.log(id + ' xhr_streaming response: ' + res.statusCode);
+      res.on('data', function(chunk) {
+        //console.log('data: ' + chunk);
+        console.log(id + ' data');
+      });
+    });
+    req.setTimeout(3000, function() {
+      console.log(id + ' xhr_streaming timed out');
+      req.abort();
+      go();
+    });
+    req.end();
+    console.log(id + ' xhr_streaming request sent');
+
+    var opts2 = parse(sockjsUrl + "xhr_send");
+    opts2.method = "POST";
+    var req2 = http.request(opts2, function(res) {
+      console.log(id + ' xhr_send response: ' + res.statusCode);
+    });
+    req2.end("[\"{\\\"method\\\":\\\"init\\\",\\\"data\\\":{\\\"obs\\\":500,\\\".clientdata_output_distPlot_width\\\":840,\\\".clientdata_output_distPlot_height\\\":400,\\\".clientdata_output_distPlot_hidden\\\":false,\\\".clientdata_pixelratio\\\":1,\\\".clientdata_url_protocol\\\":\\\"http:\\\",\\\".clientdata_url_hostname\\\":\\\"localhost\\\",\\\".clientdata_url_port\\\":\\\"3838\\\",\\\".clientdata_url_pathname\\\":\\\"/01_hello/\\\",\\\".clientdata_url_search\\\":\\\"\\\",\\\".clie [...]
+  });
+}
+
+for (var i = 0; i < 50; i++) {
+  go();
+}
\ No newline at end of file
diff --git a/manual.test/loadtest.js b/manual.test/loadtest.js
new file mode 100755
index 0000000..3d79aec
--- /dev/null
+++ b/manual.test/loadtest.js
@@ -0,0 +1,264 @@
+#!../bin/node/bin/node
+
+var http = require('http');
+var parse = require('url').parse;
+
+var Q = require('q');
+var WebSocket = require('faye-websocket');
+
+
+// Set SOCKET_PATH to a full path to force all HTTP and WS requests
+// to go to a Unix domain socket rather than over TCP/IP.
+var SOCKET_PATH = null;
+
+// Set to true if the target URL is a Shiny Server instance; set it
+// to false if the target URL is running on a bare Shiny instance.
+var SHINY_SERVER = true;
+
+// This class creates a UrlLoader and WebSocketLoader that are pointed
+// to the appUrl that you give it. The appUrl should end with a slash.
+function AppLoader(appUrl, initMsg) {
+  var urls = [
+    "",
+    "shared/jquery.js",
+    "shared/shiny.js",
+    "shared/shiny.css",
+    "shared/slider/css/jquery.slider.min.css",
+    "shared/slider/js/jquery.slider.min.js",
+    "shared/bootstrap/css/bootstrap.min.css",
+    "shared/bootstrap/js/bootstrap.min.js",
+    "shared/bootstrap/css/bootstrap-responsive.min.css",
+    "__assets__/sockjs-0.3.min.js",
+    "shared/bootstrap/img/glyphicons-halflings.png",
+    "shared/slider/img/jslider.plastic.png",
+    "__sockjs__/info"
+  ];
+
+  this._urlLoader = new UrlLoader(appUrl, urls, function(req, data) {
+    return data.replace(/"entropy":\d+/, '"entropy":[redacted]');
+  });
+
+  var ent1 = Math.floor(100 + Math.random() * 899);
+  var ent2 = Math.floor(100000000 + Math.random() * 900000000);
+  var wsUrl = appUrl.replace(/^http/, 'ws') + '__sockjs__/' + ent1 + '/' + ent2 + '/websocket';
+  this._wsLoader = new WebSocketLoader(wsUrl, initMsg);
+}
+
+(function() {
+
+  this.run_p = function() {
+    return Q.all([
+      this._urlLoader.run_p(),
+      this._wsLoader.run_p()
+    ]);
+  };
+
+}).call(AppLoader.prototype);
+
+function WebSocketLoader(url, initMsg) {
+  this._url = url;
+  this._initMsg = initMsg;
+}
+
+(function() {
+  this.run_p = function() {
+    var self = this;
+
+    var deferred = Q.defer();
+
+    var outputReceived = false;
+
+    var options = {};
+    if (typeof(SOCKET_PATH) === 'string')
+      options.socketPath = SOCKET_PATH;
+    var ws = new WebSocket.Client(this._url, undefined, options);
+    function onOpen() {
+      var initMsg = JSON.stringify(self._initMsg);
+      
+      // This line is only needed for shiny-server, since SockJS expects
+      // this framing of messages
+      if (SHINY_SERVER)
+        initMsg = JSON.stringify([initMsg]);
+      ws.send(initMsg);
+    }
+    ws.on('open', function(event) {
+      if (!SHINY_SERVER)
+        onOpen();
+    });
+    ws.on('message', function(event) {
+     try {
+        var messages;
+        // Check for SockJS framing; see "Protocol and framing" at
+        // http://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html
+        if (event.data === 'o') {
+          if (SHINY_SERVER)
+            onOpen();
+          return;
+        } else if (event.data === 'h' || event.data[0] === 'c') {
+          return;
+        } else if (event.data[0] === 'a') {
+          messages = JSON.parse(event.data.substr(1)).map(function(msg) {
+            return JSON.parse(msg);
+          });
+        } else {
+          // Raw WebSocket
+          messages = [JSON.parse(event.data)];
+        }
+
+        messages.forEach(function(msg) {
+          if (!msg.config && !msg.values && !msg.progress && !msg.errors) {
+            throw new Error('Unexpected message: ' + event.data);
+          }
+          if (msg.values) {
+            outputReceived = true;
+            ws.close();
+          }
+        })
+      } catch (e) {
+        console.log(event.data);
+        deferred.reject(e);
+      }
+    });
+    ws.on('close', function(event) {
+      if (!outputReceived) {
+        deferred.reject(new Error('Websocket closed without receiving output'));
+      } else {
+        deferred.resolve(true);
+      }
+    });
+    ws.on('error', function(event) {
+      deferred.reject(new Error('ws error event fired'));
+    });
+
+    return deferred.promise
+    .fin(function() {
+      ws.close();
+    });
+  };
+}).call(WebSocketLoader.prototype);
+
+function UrlLoader(baseUrl, urls, filter) {
+  this._results = {};
+  this._urls = urls.map(function(url) { return baseUrl + url; });
+  this._filter = filter;
+  this.requestCount = 0;
+}
+
+(function() {
+
+  this.run_p = function() {
+    var self = this;
+    var promises = this._urls.map(function(url) {
+      var options = parse(url);
+      
+      if (typeof(SOCKET_PATH) === 'string') {
+        options.host = null;
+        options.hostname = null;
+        options.port = null;
+        options.socketPath = SOCKET_PATH;
+      }
+      
+      //options.agent = false;
+      var deferred = Q.defer();
+
+      var req = http.get(options, function(res) {
+        var respdata = res.statusCode + "\n";
+        res.setEncoding('utf8');
+        res.on('data', function(chunk) {
+          respdata += chunk.toString();
+        });
+        res.on('end', function() {
+          if (res.statusCode !== 200) {
+            // Allow 404s for URLs that are only expected for Shiny Server
+            if (!/__assets__|__sockjs__/.test(url) || res.statusCode != 404) {
+              console.log(respdata);
+              deferred.reject(new Error('Status code ' + res.statusCode + ' for URL ' + url));
+              return;
+            }
+          }
+
+          respdata = self._filter(req, respdata);
+          if (typeof(self._results[url]) === 'undefined') {
+            self._results[url] = respdata;
+          } else {
+            if (self._results[url] !== respdata) {
+              console.log(
+                '\n\n\nEXPECTED: =================\n\n' +
+                self._results[url] +
+                '\n\n\nACTUAL: ===================\n\n' +
+                respdata +
+                '\n\n'
+              );
+              deferred.reject(
+                new Error('Unexpected results for URL ' + url)
+              );
+              return;
+            }
+          }
+          deferred.resolve(respdata);
+        });
+      })
+      .on('error', function(e) {
+        deferred.reject(e);
+      });
+      this.requestCount += 1;
+      return deferred.promise;
+    }, this);
+
+    return Q.all(promises);
+  };
+
+}).call(UrlLoader.prototype);
+
+var argv = process.argv;
+
+if (argv.length < 3) {
+  console.error('\nUsage: loadtest.js <shiny-url> [session-count]\n');
+  console.error('where <shiny-url> points to a shiny-server-hosted 01_hello.');
+  console.error('The default value for session-count is 200.');
+  console.error();
+  console.error('To change to a different app, capture the "init" websocket');
+  console.error('message using the Network tab in Chrome Developer Tools and');
+  console.error('modify the end of this script.');
+  console.error();
+  console.error('To use a raw Shiny app instead of shiny-server, modify the');
+  console.error('SHINY_SERVER variable at the top of this script.')
+  console.error();
+  process.exit(1);
+}
+
+// Make sure we can open a whole lot of concurrent HTTP connections
+http.globalAgent.maxSockets = 750;
+var url = process.argv[2];
+var sessionCount = +(argv[3] || 200);
+console.log('URL:        ' + url);
+console.log('Sessions:   ' + sessionCount);
+
+// The second parameter is the message that will be sent to the server upon
+// websocket connection. This needs to be tailored for each application; this 
+// one is for the Shiny 01_hello example app.
+var appLoader = new AppLoader(url, {
+  "method":"init",
+  "data":{
+    "obs":500,
+    ".clientdata_output_distPlot_width":910,
+    ".clientdata_output_distPlot_height":400,
+    ".clientdata_output_distPlot_hidden":false,
+    ".clientdata_pixelratio":1,
+    ".clientdata_url_protocol":"http:",
+    ".clientdata_url_hostname":"localhost",
+    ".clientdata_url_port":"8100",
+    ".clientdata_url_pathname":"/",
+    ".clientdata_url_search":"",
+    ".clientdata_url_hash_initial":"",
+    ".clientdata_singletons":"d9824d41b9a6aefe883ba073d83925ecd8434247",
+    ".clientdata_allowDataUriScheme":true,
+
+  }
+});
+
+var promises = [];
+for (var i = 0; i < sessionCount; i++)
+  promises.push(appLoader.run_p());
+Q.all(promises)
+.done();
\ No newline at end of file
diff --git a/manual.test/phantomjs/loadtest-user.js b/manual.test/phantomjs/loadtest-user.js
new file mode 100644
index 0000000..80d88b1
--- /dev/null
+++ b/manual.test/phantomjs/loadtest-user.js
@@ -0,0 +1,51 @@
+// This is a PhantomJS script
+
+var webpage = require('webpage');
+
+function Visit(duration, url) {
+  console.log('Launching ' + url);
+  var page = webpage.create();
+  page.open(url, function() {
+    setTimeout(function() {
+      if (page.close)
+        page.close(); // phantomjs 1.6
+      else
+        page.release(); // phantomjs 1.8+
+    }, duration);
+  });
+}
+
+/*setInterval(function() {
+  new Visit(Math.random() * 10000, 'http://localhost:3838/06_tabsets');
+  return true;
+}, 50);
+
+setInterval(function() {
+  new Visit(Math.random() * 10000, 'http://localhost:3838/09_upload');
+  return true;
+}, 50);
+
+setInterval(function() {
+  new Visit(Math.random() * 10000, 'http://localhost:3838/bad');
+  return true;
+}, 50);
+
+setInterval(function() {
+  new Visit(Math.random() * 100, 'http://localhost:3838/05_sliders');
+  return true;
+}, 6000);*/
+
+setInterval(function() {
+  new Visit(Math.random() * 10000, 'http://localhost:3838/users/jcheng/06_tabsets/');
+  return true;
+}, 30);
+
+/*setInterval(function() {
+  new Visit(Math.random() * 10000, 'http://localhost:3838/users/jcheng/10_download/');
+  return true;
+}, 50);*/
+
+// setInterval(function() {
+//   new Visit(Math.random() * 100, 'http://localhost:3838/users/jcheng/08_html/');
+//   return true;
+// }, 6000);
diff --git a/manual.test/phantomjs/loadtest.js b/manual.test/phantomjs/loadtest.js
new file mode 100644
index 0000000..c5b67d2
--- /dev/null
+++ b/manual.test/phantomjs/loadtest.js
@@ -0,0 +1,52 @@
+// This is a PhantomJS script
+
+var webpage = require('webpage');
+
+function Visit(duration, url) {
+  console.log('Launching ' + url);
+  var page = webpage.create();
+  page.open(url, function() {
+    setTimeout(function() {
+      if (page.close)
+        page.close(); // phantomjs 1.6
+      else
+        page.release(); // phantomjs 1.8+
+    }, duration);
+  });
+}
+
+setInterval(function() {
+  new Visit(Math.random() * 10000, 'http://localhost:3838/06_tabsets');
+  return true;
+}, 50);
+
+setInterval(function() {
+  new Visit(Math.random() * 10000, 'http://localhost:3838/09_upload');
+  return true;
+}, 50);
+
+setInterval(function() {
+  new Visit(Math.random() * 10000, 'http://localhost:3838/bad');
+  return true;
+}, 50);
+/*
+setInterval(function() {
+  new Visit(Math.random() * 100, 'http://localhost:3838/05_sliders');
+  return true;
+}, 6000);
+*/
+/*
+setInterval(function() {
+  new Visit(Math.max(0.3, Math.random()) * 10000, 'http://localhost:3838/users/jcheng/06_tabsets/');
+  return true;
+}, 30);
+*/
+/*setInterval(function() {
+  new Visit(Math.random() * 10000, 'http://localhost:3838/users/jcheng/10_download/');
+  return true;
+}, 50);*/
+
+// setInterval(function() {
+//   new Visit(Math.random() * 100, 'http://localhost:3838/users/jcheng/08_html/');
+//   return true;
+// }, 6000);
diff --git a/manual.test/phantomjs/loadtime.js b/manual.test/phantomjs/loadtime.js
new file mode 100644
index 0000000..434426c
--- /dev/null
+++ b/manual.test/phantomjs/loadtime.js
@@ -0,0 +1,32 @@
+var page = require('webpage').create(),
+    system = require('system'),
+    t, address;
+
+if (system.args.length === 1) {
+    console.log('Usage: loadtime.js <some URL>');
+    phantom.exit();
+}
+
+t = Date.now();
+var iter = 20;
+var i = 0;
+
+address = system.args[1];
+function runTest() {
+page.open(address, function (status) {
+    if (status !== 'success') {
+        console.log('FAIL to load the address');
+    }
+    i++;
+
+   if ( i >= iter ){
+	t = Date.now() - t;
+	console.log('Loading time for ' + iter + ' iterations = ' + t + ' msec');
+	phantom.exit();
+   } else{
+	runTest();
+   }
+});
+}
+
+runTest();
diff --git a/manual.test/test-app-worker.js b/manual.test/test-app-worker.js
new file mode 100644
index 0000000..93f6d17
--- /dev/null
+++ b/manual.test/test-app-worker.js
@@ -0,0 +1,17 @@
+var util = require('util');
+var AppSpec = require('../lib/worker/app-spec');
+var app_worker = require('../lib/worker/app-worker');
+
+var rw_p = app_worker.runWorker_p(new AppSpec(
+  '/Users/jcheng/ShinyApps/diamonds', 'jcheng', '', null, {}),
+  '/tmp/test-app-worker.sock', './testlog.log');
+
+rw_p
+.then(
+  function(status) {
+    console.log('exit with status: ' + util.inspect(status));
+  },
+  function(err) {
+    console.log('err: ' + err);
+  }
+);
diff --git a/manual.test/test-config-config.js b/manual.test/test-config-config.js
new file mode 100644
index 0000000..19b42f2
--- /dev/null
+++ b/manual.test/test-config-config.js
@@ -0,0 +1,50 @@
+var assert = require('assert');
+var fs = require('fs');
+var path = require('path');
+var util = require('util');
+var config = require('../lib/config/config');
+var schema = require('../lib/config/schema');
+var config_router = require('../lib/router/config-router');
+var paths = require('../lib/core/paths');
+
+var schemaPath = paths.projectFile('config/shiny-server-rules.config');
+
+var configData = config.parse('\
+run_as nobody;\n\
+server {\n\
+  location / {\n\
+    run_as jcheng;\n\
+    app_dir ~/myshinyapp;\n\
+  }\n\
+}');
+
+assert.equal(configData.getValue('run_as'), 'nobody');
+assert.deepEqual(configData.getOne('run_as').args, ['nobody']);
+assert.equal(configData.getOne('server').getOne('location').getValue('run_as'), 'jcheng');
+assert.equal(configData.search('location')[0].getValue('run_as'), 'jcheng');
+
+var validationRules = config.parse(fs.readFileSync(schemaPath, 'utf8'));
+schema.applySchema(configData, validationRules);
+
+var configGood = config.readSync(path.join(__dirname, 'config/good.config'), schemaPath);
+assertBad('bad1', schemaPath, /Unknown directive/);
+assertBad('bad2', schemaPath, /too few arguments/);
+assertBad('bad3', schemaPath, /too many arguments/);
+assertBad('good', path.join(__dirname, 'config/schemaBad1.config'), /Missing "at"/);
+assertBad('good', path.join(__dirname, 'config/schemaBad2.config'), /Unknown type "Number"/);
+
+function assertBad(file, schemaPath, regex) {
+  try {
+    config.readSync(path.join(__dirname, 'config', file + '.config'), schemaPath);
+    assert(false, file + " passed parse when it should've failed");
+  } catch(err) {
+    if (!regex.test(err.message))
+      throw err;
+  }
+}
+
+config_router.createRouter_p(path.join(__dirname, 'config/good.config'))
+.then(function(router) {
+  //console.log(util.inspect(router, false, null, true));
+})
+.done();
diff --git a/manual.test/test-config-lexer.js b/manual.test/test-config-lexer.js
new file mode 100644
index 0000000..c99ad39
--- /dev/null
+++ b/manual.test/test-config-lexer.js
@@ -0,0 +1,116 @@
+var assert = require('assert');
+var util = require('util');
+var config_lexer = require('../lib/config/lexer');
+var TT = config_lexer.TT;
+
+var lex = new config_lexer.Lexer("");
+
+var c_ = 1;
+var C_ALPHA = c_++;
+var C_DIGIT = c_++;
+var C_OPENBRACE = c_++;    // {
+var C_CLOSEBRACE = c_++;   // }
+var C_SEMICOLON = c_++;    // ;
+var C_HASH = c_++;         // #
+var C_SQUOTE = c_++;       // '
+var C_DQUOTE = c_++;       // "
+var C_BACKSLASH = c_++;
+var C_EOL = c_++;
+var C_WS = c_++;           // whitespace
+var C_CONTROL = c_++;
+var C_OTHER = c_ + 100;
+var C_EOD = c_ + 101;
+
+assert(lex.$classify('a') == C_ALPHA);
+assert(lex.$classify('Z') == C_ALPHA);
+assert(lex.$classify('1') == C_DIGIT);
+assert(lex.$classify('{') == C_OPENBRACE);
+assert(lex.$classify('}') == C_CLOSEBRACE);
+assert(lex.$classify(';') == C_SEMICOLON);
+assert(lex.$classify('#') == C_HASH);
+assert(lex.$classify("'") == C_SQUOTE);
+assert(lex.$classify('"') == C_DQUOTE);
+assert(lex.$classify('\\') == C_BACKSLASH);
+assert(lex.$classify(' ') == C_WS);
+assert(lex.$classify('\t') == C_WS);
+assert(lex.$classify('\r') == C_WS);
+assert(lex.$classify('\n') == C_EOL);
+assert(lex.$classify('\x01') == C_CONTROL);
+assert(lex.$classify('?') == C_OTHER);
+
+assertLex('foo', [
+  [TT.WORD, 'foo', 1, 1],
+]);
+
+assertLex('foo  \t    b12?ar', [
+  [TT.WORD, 'foo', 1, 1],
+  [TT.WS, '  \t    ', 1, 4],
+  [TT.WORD, 'b12?ar', 1, 11],
+]);
+
+assertLex('"hel\\"\\\'\'\\l\\\\o"', [
+  [TT.WORD, 'hel"\'\'l\\o', 1, 1],
+]);
+
+assertLex('foo # "hello"', [
+  [TT.WORD, 'foo', 1, 1],
+  [TT.WS, ' ', 1, 4],
+  [TT.COMMENT, ' "hello"', 1, 5],
+]);
+
+assertLex('foo# "hello"', [
+  [TT.WORD, 'foo', 1, 1],
+  [TT.COMMENT, ' "hello"', 1, 4],
+]);
+
+assertLex('\'foo # "hello"\'', [
+  [TT.WORD, 'foo # "hello"', 1, 1],
+]);
+
+// \r\n is normalized to \n
+assertLex('foo\r\nbar', [
+  [TT.WORD, 'foo', 1, 1],
+  [TT.WS, '\n', 1, 4],
+  [TT.WORD, 'bar', 2, 1],
+]);
+
+assertLex('foo"\nbar"baz', [
+  [TT.WORD, 'foo', 1, 1],
+  [TT.WORD, '\nbar', 1, 4],
+  [TT.WORD, 'baz', 2, 5],
+]);
+
+assertLex('#\nhi', [
+  [TT.COMMENT, '', 1, 1],
+  [TT.WS, '\n', 1, 2],
+  [TT.WORD, 'hi', 2, 1],
+]);
+
+assert.throws(lexAll('"'));
+assert.throws(lexAll('"\\'));
+assert.throws(lexAll('"\\"'));
+assert.throws(lexAll('\x01'));
+
+function assertLex(data, tokens) {
+  var tmpLex = new config_lexer.Lexer(data);
+  var tok;
+  while ((tok = tmpLex.nextToken()).type != TT.EOD) {
+    var testTok = tokens.shift();
+    if (!testTok)
+      assert(false, 'Too many tokens were returned');
+    assert.equal(tok.type, testTok[0]);
+    assert.equal(tok.content, testTok[1]);
+    assert.equal(tok.position.line, testTok[2]);
+    assert.equal(tok.position.col, testTok[3]);
+  }
+  if (tokens.length > 0)
+    assert(false, 'Not all tokens were matched');
+}
+
+function lexAll(data) {
+  return function() {
+    var tmpLex = new config_lexer.Lexer(data);
+    while (tmpLex.nextToken().type != TT.EOD)
+    {}
+  };
+}
\ No newline at end of file
diff --git a/manual.test/test-config-parser.js b/manual.test/test-config-parser.js
new file mode 100644
index 0000000..c2f6c29
--- /dev/null
+++ b/manual.test/test-config-parser.js
@@ -0,0 +1,15 @@
+var util = require('util');
+var config_lexer = require('../lib/config/lexer');
+var config_parser = require('../lib/config/parser');
+
+var TT = config_lexer.TT;
+var ConfigParser = config_parser.ConfigParser;
+
+var parser = new ConfigParser("foo#hi\n    bar");
+
+parser.$nextToken().type === TT.WORD;
+parser.$nextToken().type === TT.WORD;
+parser.$nextToken() === null;
+
+parser = new ConfigParser("foo ~ /hello/\n[L];\nlocation / { bar baz 'wha'; }");
+console.log(util.inspect(parser.parse(), false, null, true));
diff --git a/manual.test/test-proxy.js b/manual.test/test-proxy.js
new file mode 100644
index 0000000..a5957af
--- /dev/null
+++ b/manual.test/test-proxy.js
@@ -0,0 +1,15 @@
+var proxy_http = require('../lib/proxy/http');
+var proxy_sockjs = require('../lib/proxy/sockjs');
+var router = require('../lib/router/router')
+var WorkerRegistry = require('../lib/worker/worker-registry');
+
+var rout = new router.AutouserRouter();
+var workers = new WorkerRegistry();
+
+var shinyProxy = new proxy_http.ShinyProxy(
+  rout,
+  workers,
+  {}
+);
+proxy_sockjs.createServer(rout, workers).installHandlers(shinyProxy.httpServer);
+shinyProxy.httpServer.listen(8001);
diff --git a/manual.test/test-serialized.js b/manual.test/test-serialized.js
new file mode 100644
index 0000000..3b07288
--- /dev/null
+++ b/manual.test/test-serialized.js
@@ -0,0 +1,16 @@
+var Q = require('q');
+var qutil = require('../lib/core/qutil');
+
+var sersleep = qutil.serialized(function(ms) {
+  var defer = Q.defer();
+  console.log('Sleeping for ' + ms);
+  setTimeout(function() {
+    defer.resolve(null);
+  }, ms);
+  return defer.promise;
+});
+
+sersleep(1000).then(function() {console.log('1 done');});
+sersleep(2000).then(function() {console.log('2 done');});
+sersleep(3000).then(function() {console.log('3 done');});
+sersleep(4000).then(function() {console.log('4 done');});
diff --git a/manual.test/test-worker-registry-leak.js b/manual.test/test-worker-registry-leak.js
new file mode 100644
index 0000000..239249b
--- /dev/null
+++ b/manual.test/test-worker-registry-leak.js
@@ -0,0 +1,31 @@
+var util = require('util');
+require('../lib/core/log')
+var fs = require('fs');
+var AppSpec = require('../lib/worker/app-spec');
+var WorkerRegistry = require('../lib/worker/worker-registry');
+
+SHINY_SERVER_VERSION = "0.3.4";
+
+var registry = new WorkerRegistry();
+setInterval(function() {
+  registry.getWorker_p(new AppSpec(
+    '/var/shiny-server/www/09_upload', 'shiny', '', '/var/shiny-server/log', {
+      random: process.uptime()
+    }))
+  .then(function(info) {
+    info.acquire();
+    info.release();
+  })
+  .done();
+
+  return true;
+}, 100);
+
+agent = require('webkit-devtools-agent')
+
+setInterval(function() {
+  var mu = process.memoryUsage();
+  var info = process.uptime() + ',' + mu.rss + ',' + mu.heapTotal + ',' + mu.heapUsed + '\n';
+  fs.appendFile('mem-' + process.pid + '.csv', info);
+  return true;
+}, 3000);
\ No newline at end of file
diff --git a/manual.test/test-worker-registry.js b/manual.test/test-worker-registry.js
new file mode 100644
index 0000000..0014b58
--- /dev/null
+++ b/manual.test/test-worker-registry.js
@@ -0,0 +1,19 @@
+var util = require('util');
+require('../lib/core/log')
+var AppSpec = require('../lib/worker/app-spec');
+var WorkerRegistry = require('../lib/worker/worker-registry');
+
+var registry = new WorkerRegistry();
+registry.getWorker_p(new AppSpec(
+  '/Users/jcheng/ShinyApps/diamonds', 'jcheng', '', null, {}))
+.then(function(info) {
+  logger.info(util.inspect(info));
+})
+.done();
+
+registry.getWorker_p(new AppSpec(
+  '/Users/jcheng/ShinyApps/diamonds', 'jcheng', '', null, {}))
+.then(function(info) {
+  logger.info(util.inspect(info));
+})
+.done();
diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json
new file mode 100644
index 0000000..18e68fb
--- /dev/null
+++ b/npm-shrinkwrap.json
@@ -0,0 +1,885 @@
+{
+  "name": "shiny-server",
+  "version": "1.5.0",
+  "dependencies": {
+    "accepts": {
+      "version": "1.3.3",
+      "from": "accepts@>=1.3.3 <1.4.0",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz"
+    },
+    "align-text": {
+      "version": "0.1.4",
+      "from": "align-text@>=0.1.3 <0.2.0",
+      "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz"
+    },
+    "amdefine": {
+      "version": "1.0.0",
+      "from": "amdefine@>=0.0.4",
+      "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.0.tgz"
+    },
+    "array-flatten": {
+      "version": "1.1.1",
+      "from": "array-flatten at 1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz"
+    },
+    "async": {
+      "version": "1.5.2",
+      "from": "async@>=1.4.0 <2.0.0",
+      "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz"
+    },
+    "balanced-match": {
+      "version": "0.4.2",
+      "from": "balanced-match@>=0.4.1 <0.5.0",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz"
+    },
+    "bash": {
+      "version": "0.0.1",
+      "from": "bash at 0.0.1",
+      "resolved": "https://registry.npmjs.org/bash/-/bash-0.0.1.tgz"
+    },
+    "basic-auth": {
+      "version": "1.0.4",
+      "from": "basic-auth@>=1.0.3 <1.1.0",
+      "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.0.4.tgz"
+    },
+    "brace-expansion": {
+      "version": "1.1.6",
+      "from": "brace-expansion@>=1.0.0 <2.0.0",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz"
+    },
+    "browser-stdout": {
+      "version": "1.3.0",
+      "from": "browser-stdout at 1.3.0",
+      "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz"
+    },
+    "camelcase": {
+      "version": "1.2.1",
+      "from": "camelcase@>=1.0.2 <2.0.0",
+      "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz",
+      "optional": true
+    },
+    "center-align": {
+      "version": "0.1.3",
+      "from": "center-align@>=0.1.1 <0.2.0",
+      "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz",
+      "optional": true
+    },
+    "client-sessions": {
+      "version": "0.7.0",
+      "from": "client-sessions@>=0.7.0 <0.8.0",
+      "resolved": "https://registry.npmjs.org/client-sessions/-/client-sessions-0.7.0.tgz"
+    },
+    "cliui": {
+      "version": "2.1.0",
+      "from": "cliui@>=2.1.0 <3.0.0",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
+      "optional": true,
+      "dependencies": {
+        "wordwrap": {
+          "version": "0.0.2",
+          "from": "wordwrap at 0.0.2",
+          "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz",
+          "optional": true
+        }
+      }
+    },
+    "commander": {
+      "version": "2.9.0",
+      "from": "commander at 2.9.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz"
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "from": "concat-map at 0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
+    },
+    "content-disposition": {
+      "version": "0.5.1",
+      "from": "content-disposition at 0.5.1",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.1.tgz"
+    },
+    "content-type": {
+      "version": "1.0.2",
+      "from": "content-type@>=1.0.2 <1.1.0",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz"
+    },
+    "cookie": {
+      "version": "0.3.1",
+      "from": "cookie at 0.3.1",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz"
+    },
+    "cookie-signature": {
+      "version": "1.0.6",
+      "from": "cookie-signature at 1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz"
+    },
+    "cookies": {
+      "version": "0.5.0",
+      "from": "cookies at 0.5.0",
+      "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.5.0.tgz"
+    },
+    "core-util-is": {
+      "version": "1.0.2",
+      "from": "core-util-is@>=1.0.0 <1.1.0",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
+    },
+    "debug": {
+      "version": "2.2.0",
+      "from": "debug@~2.2.0",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz"
+    },
+    "decamelize": {
+      "version": "1.2.0",
+      "from": "decamelize@>=1.0.0 <2.0.0",
+      "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+      "optional": true
+    },
+    "depd": {
+      "version": "1.1.0",
+      "from": "depd@>=1.1.0 <1.2.0",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz"
+    },
+    "destroy": {
+      "version": "1.0.4",
+      "from": "destroy@>=1.0.4 <1.1.0",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz"
+    },
+    "diff": {
+      "version": "1.4.0",
+      "from": "diff at 1.4.0",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz"
+    },
+    "ee-first": {
+      "version": "1.1.1",
+      "from": "ee-first at 1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz"
+    },
+    "encodeurl": {
+      "version": "1.0.1",
+      "from": "encodeurl@>=1.0.1 <1.1.0",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz"
+    },
+    "escape-html": {
+      "version": "1.0.3",
+      "from": "escape-html@>=1.0.3 <1.1.0",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz"
+    },
+    "escape-string-regexp": {
+      "version": "1.0.5",
+      "from": "escape-string-regexp at 1.0.5",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
+    },
+    "etag": {
+      "version": "1.7.0",
+      "from": "etag@>=1.7.0 <1.8.0",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.7.0.tgz"
+    },
+    "eventemitter3": {
+      "version": "1.2.0",
+      "from": "eventemitter3@>=1.0.0 <2.0.0",
+      "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-1.2.0.tgz"
+    },
+    "express": {
+      "version": "4.14.0",
+      "from": "express@>=4.14.0 <5.0.0",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.14.0.tgz",
+      "dependencies": {
+        "qs": {
+          "version": "6.2.0",
+          "from": "qs at 6.2.0",
+          "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.0.tgz"
+        }
+      }
+    },
+    "faye-websocket": {
+      "version": "0.11.0",
+      "from": "faye-websocket@>=0.11.0 <0.12.0",
+      "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.0.tgz"
+    },
+    "finalhandler": {
+      "version": "0.5.0",
+      "from": "finalhandler at 0.5.0",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.5.0.tgz"
+    },
+    "formatio": {
+      "version": "1.1.1",
+      "from": "formatio at 1.1.1",
+      "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz"
+    },
+    "forwarded": {
+      "version": "0.1.0",
+      "from": "forwarded@>=0.1.0 <0.2.0",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz"
+    },
+    "fresh": {
+      "version": "0.3.0",
+      "from": "fresh at 0.3.0",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.3.0.tgz"
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "from": "fs.realpath@>=1.0.0 <2.0.0",
+      "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
+    },
+    "glob": {
+      "version": "7.0.5",
+      "from": "glob at 7.0.5",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.5.tgz"
+    },
+    "graceful-fs": {
+      "version": "4.1.9",
+      "from": "graceful-fs@>=4.1.0 <4.2.0",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.9.tgz"
+    },
+    "graceful-readlink": {
+      "version": "1.0.1",
+      "from": "graceful-readlink@>=1.0.0",
+      "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz"
+    },
+    "growl": {
+      "version": "1.9.2",
+      "from": "growl at 1.9.2",
+      "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz"
+    },
+    "handlebars": {
+      "version": "4.0.5",
+      "from": "handlebars at 4.0.5",
+      "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.5.tgz"
+    },
+    "has-flag": {
+      "version": "1.0.0",
+      "from": "has-flag@>=1.0.0 <2.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz"
+    },
+    "http-errors": {
+      "version": "1.5.0",
+      "from": "http-errors@>=1.5.0 <1.6.0",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.5.0.tgz"
+    },
+    "http-proxy": {
+      "version": "1.15.2",
+      "from": "http-proxy@>=1.15.2 <2.0.0",
+      "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.15.2.tgz"
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "from": "inflight@>=1.0.4 <2.0.0",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz"
+    },
+    "inherits": {
+      "version": "2.0.1",
+      "from": "inherits at 2.0.1",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
+    },
+    "ipaddr.js": {
+      "version": "1.1.1",
+      "from": "ipaddr.js at 1.1.1",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.1.1.tgz"
+    },
+    "is-buffer": {
+      "version": "1.1.4",
+      "from": "is-buffer@>=1.0.2 <2.0.0",
+      "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.4.tgz"
+    },
+    "isarray": {
+      "version": "0.0.1",
+      "from": "isarray at 0.0.1",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
+    },
+    "json3": {
+      "version": "3.3.2",
+      "from": "json3 at 3.3.2",
+      "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz"
+    },
+    "keygrip": {
+      "version": "1.0.1",
+      "from": "keygrip@>=1.0.0 <1.1.0",
+      "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.0.1.tgz"
+    },
+    "kind-of": {
+      "version": "3.0.4",
+      "from": "kind-of@>=3.0.2 <4.0.0",
+      "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.0.4.tgz"
+    },
+    "lazy-cache": {
+      "version": "1.0.4",
+      "from": "lazy-cache@>=1.0.3 <2.0.0",
+      "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz",
+      "optional": true
+    },
+    "lodash._baseassign": {
+      "version": "3.2.0",
+      "from": "lodash._baseassign@>=3.0.0 <4.0.0",
+      "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz"
+    },
+    "lodash._basecopy": {
+      "version": "3.0.1",
+      "from": "lodash._basecopy@>=3.0.0 <4.0.0",
+      "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz"
+    },
+    "lodash._basecreate": {
+      "version": "3.0.3",
+      "from": "lodash._basecreate@>=3.0.0 <4.0.0",
+      "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz"
+    },
+    "lodash._getnative": {
+      "version": "3.9.1",
+      "from": "lodash._getnative@>=3.0.0 <4.0.0",
+      "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz"
+    },
+    "lodash._isiterateecall": {
+      "version": "3.0.9",
+      "from": "lodash._isiterateecall@>=3.0.0 <4.0.0",
+      "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz"
+    },
+    "lodash.create": {
+      "version": "3.1.1",
+      "from": "lodash.create at 3.1.1",
+      "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz"
+    },
+    "lodash.isarguments": {
+      "version": "3.1.0",
+      "from": "lodash.isarguments@>=3.0.0 <4.0.0",
+      "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz"
+    },
+    "lodash.isarray": {
+      "version": "3.0.4",
+      "from": "lodash.isarray@>=3.0.0 <4.0.0",
+      "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz"
+    },
+    "lodash.keys": {
+      "version": "3.1.2",
+      "from": "lodash.keys@>=3.0.0 <4.0.0",
+      "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz"
+    },
+    "log4js": {
+      "version": "0.6.38",
+      "from": "log4js@>=0.6.0 <0.7.0",
+      "resolved": "https://registry.npmjs.org/log4js/-/log4js-0.6.38.tgz"
+    },
+    "lolex": {
+      "version": "1.3.2",
+      "from": "lolex at 1.3.2",
+      "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.3.2.tgz"
+    },
+    "longest": {
+      "version": "1.0.1",
+      "from": "longest@>=1.0.1 <2.0.0",
+      "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz"
+    },
+    "media-typer": {
+      "version": "0.3.0",
+      "from": "media-typer at 0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz"
+    },
+    "merge-descriptors": {
+      "version": "1.0.1",
+      "from": "merge-descriptors at 1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz"
+    },
+    "methods": {
+      "version": "1.1.2",
+      "from": "methods@>=1.1.2 <1.2.0",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz"
+    },
+    "mime": {
+      "version": "1.3.4",
+      "from": "mime at 1.3.4",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz"
+    },
+    "mime-db": {
+      "version": "1.24.0",
+      "from": "mime-db@>=1.24.0 <1.25.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.24.0.tgz"
+    },
+    "mime-types": {
+      "version": "2.1.12",
+      "from": "mime-types@>=2.1.11 <2.2.0",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.12.tgz"
+    },
+    "minimatch": {
+      "version": "3.0.3",
+      "from": "minimatch@>=3.0.2 <4.0.0",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz"
+    },
+    "minimist": {
+      "version": "0.0.10",
+      "from": "minimist@>=0.0.1 <0.1.0",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz"
+    },
+    "mkdirp": {
+      "version": "0.5.1",
+      "from": "mkdirp at 0.5.1",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+      "dependencies": {
+        "minimist": {
+          "version": "0.0.8",
+          "from": "minimist at 0.0.8",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
+        }
+      }
+    },
+    "mocha": {
+      "version": "3.1.2",
+      "from": "mocha@>=3.1.2 <4.0.0",
+      "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.1.2.tgz"
+    },
+    "moment": {
+      "version": "2.15.2",
+      "from": "moment@>=2.15.2 <3.0.0",
+      "resolved": "https://registry.npmjs.org/moment/-/moment-2.15.2.tgz"
+    },
+    "morgan": {
+      "version": "1.7.0",
+      "from": "morgan@>=1.7.0 <2.0.0",
+      "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.7.0.tgz"
+    },
+    "ms": {
+      "version": "0.7.1",
+      "from": "ms at 0.7.1",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz"
+    },
+    "negotiator": {
+      "version": "0.6.1",
+      "from": "negotiator at 0.6.1",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz"
+    },
+    "on-finished": {
+      "version": "2.3.0",
+      "from": "on-finished@>=2.3.0 <2.4.0",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz"
+    },
+    "on-headers": {
+      "version": "1.0.1",
+      "from": "on-headers@>=1.0.1 <1.1.0",
+      "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz"
+    },
+    "once": {
+      "version": "1.4.0",
+      "from": "once@>=1.3.0 <2.0.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz"
+    },
+    "optimist": {
+      "version": "0.6.1",
+      "from": "optimist at 0.6.1",
+      "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz"
+    },
+    "parseurl": {
+      "version": "1.3.1",
+      "from": "parseurl@>=1.3.1 <1.4.0",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz"
+    },
+    "path-is-absolute": {
+      "version": "1.0.1",
+      "from": "path-is-absolute@>=1.0.0 <2.0.0",
+      "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz"
+    },
+    "path-to-regexp": {
+      "version": "0.1.7",
+      "from": "path-to-regexp at 0.1.7",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz"
+    },
+    "pause": {
+      "version": "0.1.0",
+      "from": "pause at 0.1.0",
+      "resolved": "https://registry.npmjs.org/pause/-/pause-0.1.0.tgz"
+    },
+    "proxy-addr": {
+      "version": "1.1.2",
+      "from": "proxy-addr@>=1.1.2 <1.2.0",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.2.tgz"
+    },
+    "q": {
+      "version": "1.4.1",
+      "from": "q@>=1.4.0 <1.5.0",
+      "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz"
+    },
+    "qs": {
+      "version": "6.3.0",
+      "from": "qs@>=6.3.0 <7.0.0",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.0.tgz"
+    },
+    "range-parser": {
+      "version": "1.2.0",
+      "from": "range-parser@>=1.2.0 <1.3.0",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz"
+    },
+    "readable-stream": {
+      "version": "1.0.34",
+      "from": "readable-stream@>=1.0.2 <1.1.0",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz"
+    },
+    "regexp-quote": {
+      "version": "0.0.0",
+      "from": "regexp-quote at 0.0.0",
+      "resolved": "https://registry.npmjs.org/regexp-quote/-/regexp-quote-0.0.0.tgz"
+    },
+    "repeat-string": {
+      "version": "1.6.1",
+      "from": "repeat-string@>=1.5.2 <2.0.0",
+      "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz"
+    },
+    "requires-port": {
+      "version": "1.0.0",
+      "from": "requires-port@>=1.0.0 <2.0.0",
+      "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz"
+    },
+    "rewire": {
+      "version": "2.5.2",
+      "from": "rewire@>=2.5.0 <2.6.0",
+      "resolved": "https://registry.npmjs.org/rewire/-/rewire-2.5.2.tgz"
+    },
+    "right-align": {
+      "version": "0.1.3",
+      "from": "right-align@>=0.1.1 <0.2.0",
+      "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
+      "optional": true
+    },
+    "samsam": {
+      "version": "1.1.2",
+      "from": "samsam at 1.1.2",
+      "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.1.2.tgz"
+    },
+    "semver": {
+      "version": "4.3.6",
+      "from": "semver@>=4.3.3 <4.4.0",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz"
+    },
+    "send": {
+      "version": "0.14.1",
+      "from": "send@>=0.14.0 <0.15.0",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.14.1.tgz"
+    },
+    "serve-static": {
+      "version": "1.11.1",
+      "from": "serve-static@>=1.11.1 <1.12.0",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.11.1.tgz"
+    },
+    "setprototypeof": {
+      "version": "1.0.1",
+      "from": "setprototypeof at 1.0.1",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.1.tgz"
+    },
+    "shiny-server-client": {
+      "version": "1.0.0",
+      "from": "https://github.com/rstudio/shiny-server-client/archive/f214eae20.tar.gz",
+      "resolved": "https://github.com/rstudio/shiny-server-client/archive/f214eae20.tar.gz",
+      "dependencies": {
+        "commander": {
+          "version": "2.3.0",
+          "from": "commander at 2.3.0",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz"
+        },
+        "escape-string-regexp": {
+          "version": "1.0.2",
+          "from": "escape-string-regexp at 1.0.2",
+          "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz"
+        },
+        "formatio": {
+          "version": "1.1.1",
+          "from": "formatio at 1.1.1",
+          "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.1.1.tgz"
+        },
+        "graceful-fs": {
+          "version": "2.0.3",
+          "from": "graceful-fs@>=2.0.0 <2.1.0",
+          "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-2.0.3.tgz"
+        },
+        "inherits": {
+          "version": "2.0.1",
+          "from": "inherits@>=2.0.0 <2.1.0",
+          "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz"
+        },
+        "jade": {
+          "version": "0.26.3",
+          "from": "jade at 0.26.3",
+          "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz",
+          "dependencies": {
+            "commander": {
+              "version": "0.6.1",
+              "from": "commander at 0.6.1",
+              "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz"
+            },
+            "mkdirp": {
+              "version": "0.3.0",
+              "from": "mkdirp at 0.3.0",
+              "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz"
+            }
+          }
+        },
+        "lolex": {
+          "version": "1.3.2",
+          "from": "lolex at 1.3.2",
+          "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.3.2.tgz"
+        },
+        "lru-cache": {
+          "version": "2.7.3",
+          "from": "lru-cache@>=2.0.0 <3.0.0",
+          "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz"
+        },
+        "minimatch": {
+          "version": "0.2.14",
+          "from": "minimatch@>=0.2.11 <0.3.0",
+          "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz"
+        },
+        "minimist": {
+          "version": "0.0.8",
+          "from": "minimist at 0.0.8",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
+        },
+        "mkdirp": {
+          "version": "0.5.0",
+          "from": "mkdirp at 0.5.0",
+          "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz"
+        },
+        "pinkyswear": {
+          "version": "2.2.2",
+          "from": "pinkyswear@>=2.2.0 <2.3.0",
+          "resolved": "https://registry.npmjs.org/pinkyswear/-/pinkyswear-2.2.2.tgz"
+        },
+        "promises-aplus-tests": {
+          "version": "2.0.5",
+          "from": "promises-aplus-tests@>=2.0.4 <2.1.0",
+          "resolved": "https://registry.npmjs.org/promises-aplus-tests/-/promises-aplus-tests-2.0.5.tgz",
+          "dependencies": {
+            "debug": {
+              "version": "2.0.0",
+              "from": "debug at 2.0.0",
+              "resolved": "https://registry.npmjs.org/debug/-/debug-2.0.0.tgz"
+            },
+            "diff": {
+              "version": "1.0.8",
+              "from": "diff at 1.0.8",
+              "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.8.tgz"
+            },
+            "glob": {
+              "version": "3.2.3",
+              "from": "glob at 3.2.3",
+              "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz"
+            },
+            "growl": {
+              "version": "1.8.1",
+              "from": "growl at 1.8.1",
+              "resolved": "https://registry.npmjs.org/growl/-/growl-1.8.1.tgz"
+            },
+            "mocha": {
+              "version": "1.21.5",
+              "from": "mocha@>=1.21.4 <1.22.0",
+              "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.21.5.tgz"
+            },
+            "ms": {
+              "version": "0.6.2",
+              "from": "ms at 0.6.2",
+              "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz"
+            }
+          }
+        },
+        "samsam": {
+          "version": "1.1.2",
+          "from": "samsam at 1.1.2",
+          "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.1.2.tgz"
+        },
+        "sigmund": {
+          "version": "1.0.1",
+          "from": "sigmund@>=1.0.0 <1.1.0",
+          "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz"
+        },
+        "sinon": {
+          "version": "1.17.3",
+          "from": "sinon@>=1.10.3 <2.0.0",
+          "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.17.3.tgz"
+        },
+        "underscore": {
+          "version": "1.6.0",
+          "from": "underscore@>=1.6.0 <1.7.0",
+          "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz"
+        },
+        "util": {
+          "version": "0.10.3",
+          "from": "util@>=0.10.3 <1.0.0",
+          "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz"
+        }
+      }
+    },
+    "should": {
+      "version": "11.1.1",
+      "from": "should@>=11.1.0 <11.2.0",
+      "resolved": "https://registry.npmjs.org/should/-/should-11.1.1.tgz"
+    },
+    "should-equal": {
+      "version": "1.0.1",
+      "from": "should-equal@>=1.0.0 <2.0.0",
+      "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-1.0.1.tgz"
+    },
+    "should-format": {
+      "version": "3.0.2",
+      "from": "should-format@>=3.0.2 <4.0.0",
+      "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.2.tgz"
+    },
+    "should-type": {
+      "version": "1.4.0",
+      "from": "should-type@>=1.4.0 <2.0.0",
+      "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz"
+    },
+    "should-type-adaptors": {
+      "version": "1.0.1",
+      "from": "should-type-adaptors@>=1.0.1 <2.0.0",
+      "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.0.1.tgz"
+    },
+    "should-util": {
+      "version": "1.0.0",
+      "from": "should-util@>=1.0.0 <2.0.0",
+      "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.0.tgz"
+    },
+    "sinon": {
+      "version": "1.17.6",
+      "from": "sinon@>=1.17.0 <1.18.0",
+      "resolved": "https://registry.npmjs.org/sinon/-/sinon-1.17.6.tgz"
+    },
+    "sockjs": {
+      "version": "0.3.18",
+      "from": "sockjs at 0.3.18",
+      "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.18.tgz",
+      "dependencies": {
+        "faye-websocket": {
+          "version": "0.10.0",
+          "from": "faye-websocket@>=0.10.0 <0.11.0",
+          "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz"
+        }
+      }
+    },
+    "source-map": {
+      "version": "0.4.4",
+      "from": "source-map@>=0.4.4 <0.5.0",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz"
+    },
+    "split": {
+      "version": "1.0.0",
+      "from": "split@>=1.0.0 <1.1.0",
+      "resolved": "https://registry.npmjs.org/split/-/split-1.0.0.tgz"
+    },
+    "stable": {
+      "version": "0.1.5",
+      "from": "stable at 0.1.5",
+      "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.5.tgz"
+    },
+    "statuses": {
+      "version": "1.3.0",
+      "from": "statuses@>=1.3.0 <1.4.0",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.0.tgz"
+    },
+    "string_decoder": {
+      "version": "0.10.31",
+      "from": "string_decoder@>=0.10.0 <0.11.0",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
+    },
+    "supports-color": {
+      "version": "3.1.2",
+      "from": "supports-color at 3.1.2",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz"
+    },
+    "through": {
+      "version": "2.3.8",
+      "from": "through@>=2.0.0 <3.0.0",
+      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
+    },
+    "type-is": {
+      "version": "1.6.13",
+      "from": "type-is@>=1.6.13 <1.7.0",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.13.tgz"
+    },
+    "uglify-js": {
+      "version": "2.7.4",
+      "from": "uglify-js@>=2.6.0 <3.0.0",
+      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.7.4.tgz",
+      "optional": true,
+      "dependencies": {
+        "async": {
+          "version": "0.2.10",
+          "from": "async@~0.2.6",
+          "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
+          "optional": true
+        },
+        "source-map": {
+          "version": "0.5.6",
+          "from": "source-map@>=0.5.1 <0.6.0",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz",
+          "optional": true
+        }
+      }
+    },
+    "uglify-to-browserify": {
+      "version": "1.0.2",
+      "from": "uglify-to-browserify@>=1.0.0 <1.1.0",
+      "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz",
+      "optional": true
+    },
+    "underscore": {
+      "version": "1.8.3",
+      "from": "underscore at 1.8.3",
+      "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz"
+    },
+    "unixgroups": {
+      "version": "0.2.0",
+      "from": "https://github.com/rstudio/node-unixgroups/tarball/e6dfefe",
+      "resolved": "https://github.com/rstudio/node-unixgroups/tarball/e6dfefe"
+    },
+    "unpipe": {
+      "version": "1.0.0",
+      "from": "unpipe@>=1.0.0 <1.1.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz"
+    },
+    "util": {
+      "version": "0.10.3",
+      "from": "util@>=0.10.3 <1.0.0",
+      "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz"
+    },
+    "utils-merge": {
+      "version": "1.0.0",
+      "from": "utils-merge at 1.0.0",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz"
+    },
+    "uuid": {
+      "version": "2.0.3",
+      "from": "uuid@>=2.0.2 <3.0.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz"
+    },
+    "vary": {
+      "version": "1.1.0",
+      "from": "vary@>=1.1.0 <1.2.0",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.0.tgz"
+    },
+    "websocket-driver": {
+      "version": "0.6.5",
+      "from": "websocket-driver@>=0.5.1",
+      "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz"
+    },
+    "websocket-extensions": {
+      "version": "0.1.1",
+      "from": "websocket-extensions@>=0.1.1",
+      "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.1.tgz"
+    },
+    "window-size": {
+      "version": "0.1.0",
+      "from": "window-size at 0.1.0",
+      "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz",
+      "optional": true
+    },
+    "wordwrap": {
+      "version": "0.0.3",
+      "from": "wordwrap@>=0.0.2 <0.1.0",
+      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz"
+    },
+    "wrappy": {
+      "version": "1.0.2",
+      "from": "wrappy@>=1.0.0 <2.0.0",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
+    },
+    "yargs": {
+      "version": "3.10.0",
+      "from": "yargs@>=3.10.0 <3.11.0",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz",
+      "optional": true
+    }
+  }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..7ef2990
--- /dev/null
+++ b/package.json
@@ -0,0 +1,47 @@
+{
+  "name": "shiny-server",
+  "preferGlobal": "true",
+  "version": "1.5.0",
+  "author": "RStudio <node at rstudio.com>",
+  "description": "Application server for the Shiny web framework for R",
+  "bin": "./lib/main.js",
+  "scripts": {
+    "start": "node ./lib/main.js"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/rstudio/shiny-server.git"
+  },
+  "dependencies": {
+    "bash": "0.0.1",
+    "client-sessions": "^0.7.0",
+    "express": "^4.14.0",
+    "faye-websocket": "^0.11.0",
+    "graceful-fs": "4.1.x",
+    "handlebars": "4.0.5",
+    "http-proxy": "^1.15.2",
+    "log4js": "0.6.x",
+    "mocha": "^3.1.2",
+    "moment": "^2.15.2",
+    "morgan": "^1.7.0",
+    "optimist": "0.6.1",
+    "pause": "0.1.0",
+    "q": "1.4.x",
+    "qs": "^6.3.0",
+    "regexp-quote": "0.0.0",
+    "rewire": "2.5.x",
+    "send": "0.14.x",
+    "shiny-server-client": "https://github.com/rstudio/shiny-server-client/archive/f214eae20.tar.gz",
+    "should": "11.1.x",
+    "sinon": "1.17.x",
+    "sockjs": "0.3.18",
+    "split": "1.0.x",
+    "stable": "0.1.5",
+    "underscore": "1.8.3",
+    "unixgroups": "https://github.com/rstudio/node-unixgroups/tarball/e6dfefe"
+  },
+  "license": "AGPL-3.0",
+  "engines": {
+    "node": ">=6.6.0"
+  }
+}
diff --git a/packaging/debian-control/postinst.in b/packaging/debian-control/postinst.in
new file mode 100755
index 0000000..a55f9cf
--- /dev/null
+++ b/packaging/debian-control/postinst.in
@@ -0,0 +1,115 @@
+#!/bin/sh
+
+# errors shouldn't cause script to exit
+set +e
+
+ln -f -s "${CMAKE_INSTALL_PREFIX}/shiny-server/bin/shiny-server" /usr/bin/shiny-server
+# See if "shiny" user exists
+if id -u shiny >/dev/null 2>&1;
+then
+   echo User "shiny" already exists. Ensuring proper permissions on /home/shiny/.
+   mkdir -p /home/shiny
+   chown shiny:shiny /home/shiny
+else
+   echo Creating user "shiny"
+   useradd -r -m shiny
+fi
+
+if [ ! -d "/srv/shiny-server" ];
+then
+   mkdir -p /srv/shiny-server
+   # And seed with initial apps and index.html
+   ln -s ${CMAKE_INSTALL_PREFIX}/shiny-server/samples/welcome.html /srv/shiny-server/index.html
+   ln -s ${CMAKE_INSTALL_PREFIX}/shiny-server/samples/sample-apps /srv/shiny-server/sample-apps
+fi
+
+mkdir -p /etc/shiny-server
+if [ ! -f "/etc/shiny-server/shiny-server.conf" ];
+then
+   cp ${CMAKE_INSTALL_PREFIX}/shiny-server/config/default.config /etc/shiny-server/shiny-server.conf
+fi
+
+mkdir -p /var/log/shiny-server
+
+# Place the logrotate script, if applicable
+if test -d /etc/logrotate.d
+then
+   cp ${CMAKE_INSTALL_PREFIX}/shiny-server/config/logrotate /etc/logrotate.d/shiny-server
+fi
+
+# Log dir must be writable by "shiny" user
+chown shiny:shiny /var/log/shiny-server
+
+mkdir -p /var/lib/shiny-server
+
+# check lsb release
+LSB_RELEASE=`lsb_release --id --short`
+
+# # add apparmor profile
+# if test $LSB_RELEASE = "Ubuntu" && test -d /etc/apparmor.d/
+# then
+#    cp ${CMAKE_INSTALL_PREFIX}/extras/apparmor/rstudio-server /etc/apparmor.d/
+#    apparmor_parser -r /etc/apparmor.d/rstudio-server 2>/dev/null
+# fi
+
+# Ubuntu needs help setting LANG which we'll do in the Upstart script by 
+# injecting it into the script here.
+if [ -e /etc/default/locale ]; then
+   . /etc/default/locale
+fi
+ if [ $LANG ] && [ "$LANG" != "C" ]; then
+   # $LANG exists and is set. Just use it
+   SS_LANG=$LANG
+else 
+   # $LANG is not set, we need to infer it.
+   if (locale -a | grep -e '^C.UTF-8$' > /dev/null); then
+      # We have C.UTF-8, use it.
+      SS_LANG="C.UTF-8"
+   else 
+      SS_LANG="en_US.UTF-8"
+   fi
+fi
+
+# add upstart profile or init.d script and start the server
+INIT_SYSTEM=`cat /proc/1/comm`
+if test $INIT_SYSTEM = "systemd"
+then
+   systemctl stop shiny-server.service 2>/dev/null
+   systemctl disable shiny-server.service 2>/dev/null
+   cp ${CMAKE_INSTALL_PREFIX}/shiny-server/config/systemd/shiny-server.debian.service /etc/systemd/system/shiny-server.service
+   if ! grep -e "^Environment=\"LANG=" /etc/systemd/system/shiny-server.service; then
+      echo "Adding LANG to /etc/systemd/system/shiny-server.service, setting to $SS_LANG"
+      sed -i "11 a Environment=\"LANG=$SS_LANG\"" /etc/systemd/system/shiny-server.service
+   fi
+   systemctl daemon-reload
+   systemctl enable shiny-server.service
+   systemctl start shiny-server.service
+   systemctl --no-pager status shiny-server.service
+elif (test $LSB_RELEASE = "Ubuntu" || test $LSB_RELEASE = "LinuxMint") && test -d /etc/init/
+then
+   cp ${CMAKE_INSTALL_PREFIX}/shiny-server/config/upstart/shiny-server.conf /etc/init/
+
+   if ! grep -e "^env LANG=" /etc/init/shiny-server.conf > /dev/null; then
+      echo "Adding LANG to /etc/init/shiny-server.conf, setting to $SS_LANG"
+      sed -i "1 a env LANG='$SS_LANG'" /etc/init/shiny-server.conf
+   fi
+
+   initctl reload-configuration
+   initctl stop shiny-server 2>/dev/null
+   initctl start shiny-server
+else
+   cp ${CMAKE_INSTALL_PREFIX}/shiny-server/config/init.d/debian/shiny-server /etc/init.d/
+
+   if ! grep -e "^export LANG=" /etc/init/shiny-server.conf > /dev/null; then
+      echo "Adding LANG to /etc/init.d/shiny-server, setting to $SS_LANG"
+      sed -i "10 a export LANG='$SS_LANG'" /etc/init.d/shiny-server
+   fi
+
+   chmod +x /etc/init.d/shiny-server
+   update-rc.d shiny-server defaults
+   /etc/init.d/shiny-server stop  2>/dev/null
+   /etc/init.d/shiny-server start
+fi
+
+# clear error termination state
+set -e
diff --git a/packaging/debian-control/postrm.in b/packaging/debian-control/postrm.in
new file mode 100755
index 0000000..29bcbc4
--- /dev/null
+++ b/packaging/debian-control/postrm.in
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+# errors shouldn't cause script to exit
+set +e
+
+rm -f /usr/bin/shiny-server
+# systemd
+rm -f /etc/systemd/system/shiny-server.service
+# upstart
+rm -f /etc/init/shiny-server.conf
+# sysv init
+rm -f /etc/init.d/shiny-server
+
+# remove temporary sockets
+rm -rf /var/shiny-server/sockets
+
+# clear error termination state
+set -e
diff --git a/packaging/debian-control/prerm.in b/packaging/debian-control/prerm.in
new file mode 100755
index 0000000..6a8892f
--- /dev/null
+++ b/packaging/debian-control/prerm.in
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+# errors shouldn't cause script to exit
+set +e
+
+LSB_RELEASE=`lsb_release --id --short`
+
+# Stop the service and remove the init/upstart/init.d config.
+INIT_SYSTEM=`cat /proc/1/comm`
+if test $INIT_SYSTEM = "systemd"
+then
+    systemctl stop shiny-server.service 2>/dev/null
+    systemctl disable shiny-server.service 2>/dev/null
+    systemctl daemon-reload
+    rm -f /etc/systemd/system/shiny-server.service
+
+elif (test $LSB_RELEASE = "Ubuntu" || test $LSB_RELEASE = "LinuxMint") && test -d /etc/init/
+then
+    initctl stop shiny-server 2>/dev/null
+    rm -r /etc/init/shiny-server.conf
+
+else
+    /etc/init.d/shiny-server stop  2>/dev/null
+    update-rc.d shiny-server remove 2>/dev/null
+    rm -f /etc/init.d/shiny-server
+
+fi
+
+# clear error termination state
+set -e
diff --git a/packaging/make-package-jenkins.sh b/packaging/make-package-jenkins.sh
new file mode 100755
index 0000000..c812e8d
--- /dev/null
+++ b/packaging/make-package-jenkins.sh
@@ -0,0 +1,79 @@
+#!/usr/bin/env bash
+
+set -e
+set -x
+
+# This is needed for CentOS5/6 Jenkins workers to bootstrap the gcc-4.8 toolchain.
+
+cd "$(dirname $0)"
+
+# This will set up two global variables: NODE_ARCHIVE_FILENAME and SAVE_NODE_BUILD.
+init_nodejs_vars() {
+  local NODE_VER=`grep 'set(NODEJS_VERSION' ../external/node/CMakeLists.txt | sed 's/[^0-9.]//g'`
+  # Expect node version to be x.y.z format
+  if ! [[ "$NODE_VER" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
+    echo "Couldn't parse node version" >&2
+    exit 1
+  fi
+
+  if [ "$OS" == "" ]; then
+    echo "Missing 'OS' Jenkins environment variable" >&2
+    exit 1
+  fi
+
+  if [ "$ARCH" == "" ]; then
+    echo "Missing 'ARCH' Jenkins environment variable" >&2
+    exit 1
+  fi
+
+  # The filename of a suitable cached build of Node.js. We will either download
+  # and use such a file from S3, or, we will build from source and create this
+  # file (and expect Jenkins to upload it).
+  NODE_ARCHIVE_FILENAME="node_${NODE_VER}_${OS}_${ARCH}.tar.gz"
+
+  # If 1, then after we build, we'll want to save the ext/node directory as a
+  # .tar.gz in the packaging/build directory. Jenkins will see to getting it
+  # uploaded to S3. The setup_cached_nodejs step will set this to 0 if it finds
+  # a cached build already present on S3.
+  SAVE_NODE_BUILD=1
+}
+
+# Attempt to retrieve a cached Node.js build from S3. If one is found, then we
+# will unpack it in ../ext and set SAVE_NODE_BUILD=0.
+setup_cached_nodejs () {
+  # This is the URL where we'll expect to find a suitable cached build of Node.js, if one exists
+  local NODE_ARCHIVE_URL="https://s3.amazonaws.com/rstudio-shiny-server-os-build/node/${NODE_ARCHIVE_FILENAME}"
+  mkdir -p ../ext
+  # The local path where we'll put the cached build, if we can find it
+  local NODE_ARCHIVE_DEST="../ext/${NODE_ARCHIVE_FILENAME}"
+
+  wget -O "${NODE_ARCHIVE_DEST}" "${NODE_ARCHIVE_URL}" || rm -f "${NODE_ARCHIVE_DEST}"
+  if [ -f "${NODE_ARCHIVE_DEST}" ]; then
+    SAVE_NODE_BUILD=0
+    echo "Using cached Node.js build from $NODE_ARCHIVE_URL" >&2
+    (cd ../ext && tar xzf "$NODE_ARCHIVE_FILENAME" && rm "$NODE_ARCHIVE_FILENAME")
+  else
+    echo "No cached Node.js build found; will build from source (tried $NODE_ARCHIVE_URL)" >&2
+  fi
+}
+
+# If SAVE_NODE_BUILD=1, then tar-gzip ext/node and put it in packaging/build.
+archive_nodejs() {
+  if [ "$SAVE_NODE_BUILD" == "1" ]; then
+    echo "Saving node build as $NODE_ARCHIVE_FILENAME" >&2
+    (cd ../ext && tar czf "../packaging/build/$NODE_ARCHIVE_FILENAME" node)
+  fi
+}
+
+
+init_nodejs_vars
+setup_cached_nodejs
+
+if (which scl && scl -l | grep -q devtoolset-2);
+then
+	scl enable devtoolset-2 ./make-package.sh "$@"
+else
+	CC=gcc-4.8 CXX=g++-4.8 ./make-package.sh "$@"
+fi
+
+archive_nodejs
diff --git a/packaging/make-package.sh b/packaging/make-package.sh
new file mode 100755
index 0000000..cbf0496
--- /dev/null
+++ b/packaging/make-package.sh
@@ -0,0 +1,83 @@
+#!/usr/bin/env bash
+
+set -e
+set -x
+
+env
+
+if [ "$PYTHON" == "" ]
+then
+	# python26 is required for RedHat/CentOS 5
+	if [ -x /usr/bin/python26 ]
+	then
+		PYTHON=python26
+	else
+		PYTHON=python
+	fi
+fi
+
+if [ "$PYTHON" != "python" ]
+then
+	mkdir -p $(readlink --canonicalize .)/fake_python
+	ln -sf $(which $PYTHON) ./fake_python/python
+	export PATH=$(readlink --canonicalize  .)/fake_python:$PATH
+fi
+
+GENERATOR="$1"
+if [ "$GENERATOR" == "" ]
+then
+	if [ -f /etc/debian_version ]; then
+		GENERATOR=DEB
+	fi
+	if [ -f /etc/redhat-release ]; then
+		GENERATOR=RPM
+	fi
+fi
+
+if [ "$GENERATOR" != "DEB" -a "$GENERATOR" != "RPM" ]
+then
+	echo "Usage: make-package.sh [DEB|RPM]"
+	exit 1
+fi
+
+if [ "$CMAKE" == "" ]
+then
+	CMAKE="cmake"
+	if which cmake28
+	then
+		CMAKE="cmake28"
+	fi
+fi
+
+if [ "$CPACK" == "" ]
+then
+	CPACK="cpack"
+	if which cpack28
+	then
+		CPACK="cpack28"
+	fi
+fi
+
+DIR=`dirname $0`
+cd "$DIR"
+DIR=`pwd`
+# Add node, etc. to the path
+PATH=$DIR/../bin:$PATH
+mkdir -p build
+cd build
+"$CMAKE" -DCMAKE_INSTALL_PREFIX=/opt -DPYTHON="$PYTHON" ../..
+make
+
+# START: building in project root --------------------------
+pushd ../..
+
+./bin/npm --python="${PYTHON}" install
+./bin/npm --python="${PYTHON}" rebuild
+
+# Need to rebuild ourselves since 'npm install' won't run gyp for us.
+./bin/node ./ext/node/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js --python="$PYTHON" rebuild
+
+popd
+# END: building in project root ----------------------------
+
+"$CPACK" -G "$GENERATOR"
diff --git a/packaging/rpm-script/postinst.sh.in b/packaging/rpm-script/postinst.sh.in
new file mode 100755
index 0000000..fbb70d6
--- /dev/null
+++ b/packaging/rpm-script/postinst.sh.in
@@ -0,0 +1,94 @@
+#!/bin/sh
+
+# errors shouldn't cause script to exit
+set +e
+
+ln -f -s "${CMAKE_INSTALL_PREFIX}/shiny-server/bin/shiny-server" /usr/bin/shiny-server
+# See if "shiny" user exists
+if id -u shiny >/dev/null 2>&1;
+then
+   echo User "shiny" already exists. Ensuring proper permissions on /home/shiny/.
+   mkdir -p /home/shiny
+   chown shiny:shiny /home/shiny
+else
+   echo Creating group "shiny"
+   groupadd shiny
+
+   echo Creating user "shiny"
+   useradd -r -m shiny -g shiny -s /bin/sh
+fi
+
+if [ ! -d "/srv/shiny-server" ];
+then
+   mkdir -p /srv/shiny-server
+   # And seed with initial apps and index.html
+   ln -s ${CMAKE_INSTALL_PREFIX}/shiny-server/samples/welcome.html /srv/shiny-server/index.html
+   ln -s ${CMAKE_INSTALL_PREFIX}/shiny-server/samples/sample-apps /srv/shiny-server/sample-apps
+fi
+
+mkdir -p /etc/shiny-server
+if [ ! -f "/etc/shiny-server/shiny-server.conf" ];
+then
+   cp ${CMAKE_INSTALL_PREFIX}/shiny-server/config/default.config /etc/shiny-server/shiny-server.conf
+fi
+
+mkdir -p /var/log/shiny-server
+
+# Place the logrotate script, if applicable
+if test -d /etc/logrotate.d
+then
+   cp ${CMAKE_INSTALL_PREFIX}/shiny-server/config/logrotate /etc/logrotate.d/shiny-server
+fi
+
+# Log dir must be writable by "shiny" user
+chown shiny:shiny /var/log/shiny-server
+
+mkdir -p /var/lib/shiny-server
+
+RH_VER=""
+if test -e /etc/redhat-release
+then
+  RH_VER=`sed -nr "s/.*release\\ ([0-9]).*/\\1/p" /etc/redhat-release`
+fi
+
+# add upstart profile, or init.d/systemd script and start the server
+if test -d /etc/init/
+then
+   # remove any previously existing init.d based scheme
+   service shiny-server stop 2>/dev/null
+   rm -f /etc/init.d/shiny-server
+
+   cp ${CMAKE_INSTALL_PREFIX}/shiny-server/config/upstart/shiny-server.conf /etc/init/
+   initctl reload-configuration
+   initctl stop shiny-server 2>/dev/null
+   sleep 1
+   initctl start shiny-server
+elif [ "$RH_VER" == "7" ]
+then
+   cp ${CMAKE_INSTALL_PREFIX}/shiny-server/config/systemd/shiny-server.service /etc/systemd/system/
+   systemctl enable shiny-server
+   systemctl restart shiny-server
+else
+   if test -e /etc/SuSE-release
+   then
+      cp ${CMAKE_INSTALL_PREFIX}/shiny-server/config/init.d/suse/shiny-server /etc/init.d/
+   else
+      cp ${CMAKE_INSTALL_PREFIX}/shiny-server/config/init.d/redhat/shiny-server /etc/init.d/
+   fi
+
+   chmod +x /etc/init.d/shiny-server
+   chkconfig --add shiny-server
+   service shiny-server stop 2>/dev/null
+   sleep 1
+   service shiny-server start
+fi
+
+if [ -e /etc/SuSE-release ] || [ "$RH_VER" == "7" ]  || [ "$RH_VER" == "6" ]
+then
+   # The default Pandoc binaries aren't compatible, overwrite with the static versions.
+   cp -f ${CMAKE_INSTALL_PREFIX}/shiny-server/ext/pandoc/static/pandoc ${CMAKE_INSTALL_PREFIX}/shiny-server/ext/pandoc/pandoc
+   cp -f ${CMAKE_INSTALL_PREFIX}/shiny-server/ext/pandoc/static/pandoc-citeproc ${CMAKE_INSTALL_PREFIX}/shiny-server/ext/pandoc/pandoc-citeproc
+fi
+
+# clear error termination state
+set -e
diff --git a/packaging/rpm-script/postrm.sh.in b/packaging/rpm-script/postrm.sh.in
new file mode 100755
index 0000000..0940e1f
--- /dev/null
+++ b/packaging/rpm-script/postrm.sh.in
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+# errors shouldn't cause script to exit
+set +e
+
+if [ "$1" = "0" ] ; then
+    # uninstall
+    rm -f /usr/bin/shiny-server
+    rm -f /etc/init/shiny-server.conf
+
+    # remove temporary sockets
+    rm -rf /var/shiny-server/sockets
+fi
+
+# clear error termination state
+set -e
diff --git a/packaging/rpm-script/prerm.sh.in b/packaging/rpm-script/prerm.sh.in
new file mode 100755
index 0000000..fdb8ad7
--- /dev/null
+++ b/packaging/rpm-script/prerm.sh.in
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+# errors shouldn't cause script to exit
+set +e
+
+if [ "$1" = "0" ] ; then
+    # uninstall
+    if [ -f /etc/init/shiny-server.conf ]
+    then
+        sudo initctl stop shiny-server 2>/dev/null
+    elif [ -f /etc/systemd/system/shiny-server.service ]
+    then
+      systemctl stop shiny-server 2>/dev/null
+    elif [ -f /etc/init.d/shiny-server ]
+    then
+        sudo service shiny-server stop 2>/dev/null
+    fi
+fi
+
+# clear error termination state
+set -e
diff --git a/samples/sample-apps/hello/server.R b/samples/sample-apps/hello/server.R
new file mode 100644
index 0000000..edbc474
--- /dev/null
+++ b/samples/sample-apps/hello/server.R
@@ -0,0 +1,21 @@
+library(shiny)
+
+# Define server logic required to draw a histogram
+shinyServer(function(input, output) {
+
+  # Expression that generates a histogram. The expression is
+  # wrapped in a call to renderPlot to indicate that:
+  #
+  #  1) It is "reactive" and therefore should be automatically
+  #     re-executed when inputs change
+  #  2) Its output type is a plot
+
+  output$distPlot <- renderPlot({
+    x    <- faithful[, 2]  # Old Faithful Geyser data
+    bins <- seq(min(x), max(x), length.out = input$bins + 1)
+
+    # draw the histogram with the specified number of bins
+    hist(x, breaks = bins, col = 'darkgray', border = 'white')
+  })
+
+})
\ No newline at end of file
diff --git a/samples/sample-apps/hello/ui.R b/samples/sample-apps/hello/ui.R
new file mode 100644
index 0000000..c5939ea
--- /dev/null
+++ b/samples/sample-apps/hello/ui.R
@@ -0,0 +1,22 @@
+library(shiny)
+
+# Define UI for application that plots random distributions 
+shinyUI(pageWithSidebar(
+  
+  # Application title
+  headerPanel("It's Alive!"),
+  
+  # Sidebar with a slider input for number of observations
+  sidebarPanel(
+    sliderInput("bins",
+                  "Number of bins:",
+                  min = 1,
+                  max = 50,
+                  value = 30)
+  ),
+  
+  # Show a plot of the generated distribution
+  mainPanel(
+    plotOutput("distPlot", height=250)
+  )
+))
diff --git a/samples/sample-apps/rmd/index.Rmd b/samples/sample-apps/rmd/index.Rmd
new file mode 100755
index 0000000..59990bc
--- /dev/null
+++ b/samples/sample-apps/rmd/index.Rmd
@@ -0,0 +1,25 @@
+---
+title: "Shiny Doc"
+output: html_document
+runtime: shiny
+---
+
+```{r, echo=FALSE}
+shinyApp(
+  
+  ui = fluidPage(
+    selectInput("region", "Region:", 
+                choices = colnames(WorldPhones)),
+    plotOutput("phonePlot", height=270)
+  ),
+  
+  server = function(input, output) {
+    output$phonePlot <- renderPlot({
+      barplot(WorldPhones[,input$region]*1000, 
+              ylab = "Number of Telephones", xlab = "Year")
+    })
+  },
+  
+  options = list(height = 345)
+)
+```
\ No newline at end of file
diff --git a/samples/welcome.html b/samples/welcome.html
new file mode 100644
index 0000000..a5e68bf
--- /dev/null
+++ b/samples/welcome.html
@@ -0,0 +1,129 @@
+<!DOCTYPE html>
+<html lang="en-US" xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <title>Welcome to Shiny Server!</title>
+  <style type="text/css">
+    body, html {
+      font-family: Helvetica, Arial, sans-serif;
+      background-color: #F5F5F5;
+      color: #114;
+      margin: 0;
+      padding: 0;
+    }
+    a {
+      text-decoration: none;
+    }
+    a:hover {
+      text-decoration: underline;
+    }
+    #titleBar {
+      height: 80px;
+      background-color: #3475b4;
+      overflow: hidden;
+      border-bottom: 1px solid #3475b3;
+      -moz-box-shadow:    0px 0px 10px 3px #BBC;
+      -webkit-box-shadow: 0px 0px 10px 3px #BBC;
+      box-shadow:         0px 0px 10px 3px #BBC;
+    }
+    #titleBar #container {
+      margin-top: 14px;
+    }
+    #titleBar h1 {
+      margin: 0 auto .5em auto;
+      padding: .2em;
+      color: #EEE;
+      text-align: center;
+    }
+    #intro {
+      background-color: #DDD;
+      margin: 1em 1em 0 1em;
+      padding: .75em;
+      text-align: center;
+      border: 1px solid #CCC;
+      font-size: 18px;
+    }
+    #intro p {
+      margin: .3em 0 .3em 0;
+    }
+    #outer-content {
+      max-width: 910px;
+      margin-left: auto;
+      margin-right: auto;
+    }
+    #content {
+      margin: 1em auto 1em auto;
+      float: left;
+    }
+    #main{
+      margin-right: 350px;
+      float: left;
+      line-height: 24px;
+    }
+
+    #shiny{
+      float: left;
+      width: 305px;
+      margin-left: -330px;
+      padding-left: 20px;
+      border-left: 1px solid #AAA;
+    }
+    #shiny iframe {
+      margin-top: 30px;
+    }
+    .caption{
+      font-size: 13px;
+    }
+    code {
+      background-color: #E5E5E5;
+      border: 1px solid #AAA;
+      -webkit-border-radius: 3px;
+      -khtml-border-radius: 3px; 
+      -moz-border-radius: 3px;
+      border-radius: 3px;
+      padding: 0 .5em 0 .5em;
+    }
+  </style>
+</head>
+<body>
+  <div id="titleBar">
+    <div id="container">
+      <h1>Welcome to Shiny Server!</h1>
+    </div>
+  </div>
+  <div id="outer-content">
+    <div id="intro">
+      <p>If you're seeing this page, that means Shiny Server is installed and running. <strong>Congratulations!</strong> </p>
+    </div>
+    <div id="content">
+      <div id="main">
+        <h2>What's Next?</h2>
+        Now you're ready to setup Shiny — if you haven't already — and start deploying your Shiny applications.
+        <p>If you see a Shiny application running on the right side of this page, then Shiny is configured properly on your server and already running an example. Bravo! You can see this application on your server at <a href="./sample-apps/hello/">/sample-apps/hello/</a>.</p>
+        <p>If you see a gray box or an error message, then there's a bit more work to do to get Shiny running fully. You can continue with <a href="http://www.rstudio.com/shiny/server/install-opensource">the installation instructions</a> or use <a href="http://rstudio.github.io/shiny-server/latest/">the Admin Guide</a> for more information. If you're seeing an error message in the panel to the right, you can use it to help diagnose what may be wrong. If you think Shiny is installed and s [...]
+        <p>If you're really stuck <em>and you've read the relevant sections in <a href="http://rstudio.github.io/shiny-server/latest/">the Admin Guide</a></em> then please ask for help on <a href="https://groups.google.com/forum/#!forum/shiny-discuss">the mailing list</a>.</p>
+        
+        <h2><pre>rmarkdown</pre></h2>
+        <p>Once you have Shiny working properly (the top application on the right sidebar), you can optionally proceed to setup rmarkdown to enable your server to host Shiny docs using the <code>rmarkdown</code> package.</p>
+
+        <p>Once you have <code>rmarkdown</code> installed, the lower example to the right should also be available. Once both examples are running, you're all set to host both Shiny applications and Shiny docs!</p>
+
+        <h2>All Done?</h2>
+        <p>Once you can see the Shiny application on the right, you're off to the races! You can look at <a href="http://shiny.rstudio.com">shiny.rstudio.com</a> to take a deeper dive into Shiny or take a look at some of the <a href="http://rstudio.github.io/shiny-server/latest/#quick-start">Shiny Server Quick Start Guides</a> to learn about some of the different things Shiny Server can do.</p>
+        <p>When you're all setup, you can delete this page and/or the sample applications we installed for you if you don't want them anymore. You can delete this page by running <code>sudo rm /srv/shiny-server/index.html</code> or delete the sample applications by running <code>sudo rm -rf /srv/shiny-server/sample-apps</code>.</p>
+
+      </div>
+      <div id="shiny">
+        <iframe src="./sample-apps/hello/" style="border: 1px solid #AAA; width: 290px; height: 460px"></iframe>
+        <div class="caption">
+          When Shiny is properly configured on your server, you'll see a Shiny app above.
+        </div>
+
+        <iframe src="./sample-apps/rmd/" style="border: 1px solid #AAA; width: 290px; height: 420px"></iframe>
+        <div class="caption">
+          With Shiny and <code>rmarkdown</code> installed, you should see a Shiny doc above.
+        </div>
+      </div>
+    </div>
+  </div>
+</body>
+</html>
\ No newline at end of file
diff --git a/scripts/mkdir.sh b/scripts/mkdir.sh
new file mode 100755
index 0000000..12b2a66
--- /dev/null
+++ b/scripts/mkdir.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+set -e 
+
+if [ $# -ne "3" ]; then
+  echo "You must provide two arguments: the name of the directory, the user name, and the group name"
+  exit 1;
+fi
+
+# Create a directory and check its ownership
+echo "Creating directory $1 if it doesn't exist."
+[ -d "$1" ] || mkdir "$1"
+
+echo "Chmodding to 755"
+chmod 755 "$1"
+
+echo "Chowning directory $1 to $2:$3"
+chown "$2":"$3" "$1"
+
+if [ ! -d "$1" ]; then
+  echo "The given path is not a directory."
+  exit 1;
+fi
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..1e5f293
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,2 @@
+configure_file("${CMAKE_CURRENT_SOURCE_DIR}/launcher.h.in" "${CMAKE_CURRENT_SOURCE_DIR}/launcher.h")
+add_executable(shiny-server launcher.cc)
\ No newline at end of file
diff --git a/src/launcher.cc b/src/launcher.cc
new file mode 100644
index 0000000..d8d04b6
--- /dev/null
+++ b/src/launcher.cc
@@ -0,0 +1,114 @@
+/*
+ * launcher.cc
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+#include <unistd.h>
+#include <sys/param.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <libgen.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <string>
+#include <vector>
+#include <algorithm>
+
+#include "launcher.h"
+
+// The purpose of this executable is to provide a clean entry point for
+// shiny-server, that is capable of running either daemonized or not.
+
+int findBaseDir(std::string* shinyServerPath);
+
+int main(int argc, char **argv) {
+
+  // If the caller requested daemonizing, do it.
+  for (int i = 1; i < argc; i++) {
+    if (strcmp(argv[i], "--daemonize") == 0) {
+      daemon(1, 0);
+      break;
+    }
+  }
+
+
+  std::string shinyServerPath;
+  int result = findBaseDir(&shinyServerPath);
+  if (result != 0)
+    return result;
+
+  std::string nodePath = shinyServerPath + "/ext/node/bin/shiny-server";
+  std::string mainPath = shinyServerPath + "/lib/main.js";
+
+  // Two extra args: one for mainPath, one for NULL terminator
+  char** newargs = new char*[argc + 2];
+  newargs[0] = strdup(nodePath.c_str());
+  newargs[1] = strdup(mainPath.c_str());
+  for (int i = 0; i < argc - 1; i++) {
+    newargs[i + 2] = argv[i + 1];
+  }
+  newargs[argc + 1] = NULL;
+
+  execv(nodePath.c_str(), newargs);
+ 
+  // This will actually never get called.
+  free(newargs[0]);
+  free(newargs[1]);
+  delete newargs;
+
+  return 0;
+}
+
+// Determines the base dir of the shiny-server instance that's being invoked,
+// by calling readlink on /proc/<pid>/exe.
+int findBaseDir(std::string* shinyServerPath) {
+
+  char execPath[MAXPATHLEN + 1];
+  int cn = snprintf(execPath, MAXPATHLEN + 1, "/proc/%d/exe", getpid());
+  if (cn < 0 || cn > MAXPATHLEN) {
+    // Not expected
+    return 2;
+  }
+
+  struct stat execStat;
+  if (lstat(execPath, &execStat)) {
+    if (errno == ENOENT)
+      fprintf(stderr, "/proc/%d/exe doesn't exist--got Linux?\n", getpid());
+    else
+      fprintf(stderr, "Fatal error calling lstat: %d\n", errno);
+    return 1;
+  }
+
+  if (!S_ISLNK(execStat.st_mode)) {
+    fprintf(stderr, "/proc/%d/exe was not a symlink\n", getpid());
+    return 1;
+  }
+
+  if (execStat.st_size > MAXPATHLEN) {
+    fprintf(stderr, "Link resolved to an unexpectedly long path\n");
+    return 1;
+  }
+  ssize_t charsNeeded = execStat.st_size > 0 ? execStat.st_size : MAXPATHLEN;
+
+  std::vector<char> execBuf(charsNeeded + 1, 0);
+  ssize_t cb = readlink(execPath, &execBuf[0], execBuf.size());
+  if (cb < 0) {
+    fprintf(stderr, "Fatal error calling readlink: %d\n", errno);
+    return 1;
+  }
+  std::copy(execBuf.begin(), execBuf.begin() + cb, execPath);
+  execPath[cb] = '\0';
+
+  *shinyServerPath = dirname(dirname(execPath));
+
+  return 0;
+}
diff --git a/src/launcher.h.in b/src/launcher.h.in
new file mode 100644
index 0000000..ae7e940
--- /dev/null
+++ b/src/launcher.h.in
@@ -0,0 +1 @@
+#define SHINY_SERVER_DEFAULT_BIN_PATH "${CMAKE_INSTALL_PREFIX}/shiny-server/bin/shiny-server"
\ No newline at end of file
diff --git a/src/posix.cc b/src/posix.cc
new file mode 100644
index 0000000..a596d37
--- /dev/null
+++ b/src/posix.cc
@@ -0,0 +1,274 @@
+/*
+ * posix.cc
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+#include <node.h>
+#include <v8.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+#include <fcntl.h>
+#include <string>
+
+using namespace node;
+using namespace v8;
+
+void GetPwNam(const FunctionCallbackInfo<Value>& args) {
+  Isolate* isolate = Isolate::GetCurrent();
+  HandleScope scope(isolate);
+
+  if (args.Length() < 1) {
+    isolate->ThrowException(Exception::Error(
+          String::NewFromUtf8(isolate, "getpwnam requires 1 argument")));
+    return;
+  }
+
+  String::Utf8Value pwnam(args[0]->ToString());
+
+  int err = 0;
+  struct passwd pwd;
+  struct passwd *pwdp = NULL;
+
+  int bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
+  if (bufsize == -1)  // value was indeterminant
+    bufsize = 16384;
+  char buf[bufsize];
+
+  errno = 0;
+  if ((err = getpwnam_r(*pwnam, &pwd, buf, bufsize, &pwdp)) || pwdp == NULL) {
+    if (errno == 0) {
+      args.GetReturnValue().Set(Null(isolate));
+      return;
+    }
+    else
+      isolate->ThrowException(UVException(isolate, errno, "getpwnam_r"));
+      return;
+  }
+
+  Local<Object> userInfo = Object::New(isolate);
+  userInfo->Set(String::NewFromUtf8(isolate, "name"), String::NewFromUtf8(isolate, pwd.pw_name));
+  userInfo->Set(String::NewFromUtf8(isolate, "passwd"), String::NewFromUtf8(isolate, pwd.pw_passwd));
+  userInfo->Set(String::NewFromUtf8(isolate, "uid"), Number::New(isolate, pwd.pw_uid));
+  userInfo->Set(String::NewFromUtf8(isolate, "gid"), Number::New(isolate, pwd.pw_gid));
+  userInfo->Set(String::NewFromUtf8(isolate, "gecos"), String::NewFromUtf8(isolate, pwd.pw_gecos));
+  userInfo->Set(String::NewFromUtf8(isolate, "home"), String::NewFromUtf8(isolate, pwd.pw_dir));
+  userInfo->Set(String::NewFromUtf8(isolate, "shell"), String::NewFromUtf8(isolate, pwd.pw_shell));
+
+  args.GetReturnValue().Set(userInfo);
+}
+
+void GetPwUid(const FunctionCallbackInfo<Value>& args) {
+  Isolate* isolate = Isolate::GetCurrent();
+  HandleScope scope(isolate);
+
+  if (args.Length() < 1) {
+    isolate->ThrowException(Exception::Error(
+          String::NewFromUtf8(isolate, "getpwuid requires 1 argument")));
+    return;
+  }
+
+  uid_t pwuid = args[0]->IntegerValue();
+  printf("%d", pwuid);
+
+  int err = 0;
+  struct passwd pwd;
+  struct passwd *pwdp = NULL;
+
+  int bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
+  if (bufsize == -1)  // value was indeterminant
+    bufsize = 16384;
+  char buf[bufsize];
+
+  errno = 0;
+  if ((err = getpwuid_r(pwuid, &pwd, buf, bufsize, &pwdp)) || pwdp == NULL) {
+    if (errno == 0) {
+      args.GetReturnValue().Set(Null(isolate));
+      return;
+    }
+    else {
+      isolate->ThrowException(UVException(isolate, errno, "getpwuid_r"));
+      return;
+    }
+  }
+
+  Local<Object> userInfo = Object::New(isolate);
+  userInfo->Set(String::NewFromUtf8(isolate, "name"), String::NewFromUtf8(isolate, pwd.pw_name));
+  userInfo->Set(String::NewFromUtf8(isolate, "passwd"), String::NewFromUtf8(isolate, pwd.pw_passwd));
+  userInfo->Set(String::NewFromUtf8(isolate, "uid"), Number::New(isolate, pwd.pw_uid));
+  userInfo->Set(String::NewFromUtf8(isolate, "gid"), Number::New(isolate, pwd.pw_gid));
+  userInfo->Set(String::NewFromUtf8(isolate, "gecos"), String::NewFromUtf8(isolate, pwd.pw_gecos));
+  userInfo->Set(String::NewFromUtf8(isolate, "home"), String::NewFromUtf8(isolate, pwd.pw_dir));
+  userInfo->Set(String::NewFromUtf8(isolate, "shell"), String::NewFromUtf8(isolate, pwd.pw_shell));
+
+  args.GetReturnValue().Set(userInfo);
+  return;
+}
+
+void GetGroupList(const FunctionCallbackInfo<Value>& args) {
+  Isolate* isolate = Isolate::GetCurrent();
+  HandleScope scope(isolate);
+
+  if (args.Length() < 1) {
+    isolate->ThrowException(Exception::Error(
+          String::NewFromUtf8(isolate, "getgrouplist requires 1 argument")));
+    return;
+  }
+
+  String::Utf8Value name(args[0]);
+
+  int err = 0;
+  struct passwd pwd;
+  struct passwd *pwdp = NULL;
+
+  int bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
+  if (bufsize == -1)  // value was indeterminant
+    bufsize = 16384;
+  char buf[bufsize];
+
+#ifdef __linux__
+  typedef gid_t result_t;
+#else
+  typedef int result_t;
+#endif
+
+  errno = 0;
+  if ((err = getpwnam_r(*name, &pwd, buf, bufsize, &pwdp)) || pwdp == NULL) {
+    if (errno == 0) {
+      args.GetReturnValue().Set(Null(isolate));
+      return;
+    }
+    else {
+      isolate->ThrowException(UVException(isolate, errno, "getpwnam_r"));
+      return;
+    }
+  }
+
+  int ngrp = 64;
+  gid_t gid = pwd.pw_gid;
+
+  for (int i = 0; i < 3; i++) {
+
+    result_t groups[ngrp];
+
+    errno = 0;
+    err = getgrouplist(*name, gid, groups, &ngrp);
+    if (err == -1) {
+      // Not enough buffer space; ngrp has the necessary number
+      continue;
+#ifndef __linux__
+    } else if (err != 0) {
+      // On BSD, return value is 0 on success
+      isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "Unexpected error calling getgrouplist")));
+      return;
+#endif
+    }
+    else {
+      Local<Array> groupList = Array::New(isolate);
+      for (int j = 0; j < ngrp; j++) {
+        groupList->Set(j, Integer::New(isolate, groups[j]));
+      }
+      args.GetReturnValue().Set(groupList);
+      return;
+    }
+  }
+
+  isolate->ThrowException(Exception::Error(String::NewFromUtf8(isolate, "Unexpected getgrouplist behavior")));
+  return;
+}
+
+void GetGrNam(const FunctionCallbackInfo<Value>& args) {
+  Isolate* isolate = Isolate::GetCurrent();
+  HandleScope scope(isolate);
+
+  if (args.Length() < 1) {
+    isolate->ThrowException(Exception::Error(
+          String::NewFromUtf8(isolate, "getgrouplist requires 1 argument")));
+    return;
+  }
+
+  String::Utf8Value name(args[0]);
+
+  errno = 0;
+  struct group * group = getgrnam(*name);
+  if (!group) {
+    if (errno == 0) {
+      args.GetReturnValue().Set(Null(isolate));
+      return;
+    }
+    else {
+      isolate->ThrowException(UVException(isolate, errno, "getgrnam"));
+      return;
+    }
+  }
+
+  Local<Object> groupInfo = Object::New(isolate);
+  groupInfo->Set(String::NewFromUtf8(isolate, "name"), String::NewFromUtf8(isolate, group->gr_name));
+  groupInfo->Set(String::NewFromUtf8(isolate, "passwd"), String::NewFromUtf8(isolate, group->gr_passwd));
+  groupInfo->Set(String::NewFromUtf8(isolate, "gid"), Integer::New(isolate, group->gr_gid));
+  Local<Array> members = Array::New(isolate);
+  groupInfo->Set(String::NewFromUtf8(isolate, "members"), members);
+  for (int i = 0; group->gr_mem[i]; i++) {
+    members->Set(members->Length(), String::NewFromUtf8(isolate, group->gr_mem[i]));
+  }
+
+  args.GetReturnValue().Set(groupInfo);
+  return;
+}
+
+void AcquireRecordLock(const FunctionCallbackInfo<Value>& args) {
+  Isolate* isolate = Isolate::GetCurrent();
+  HandleScope scope(isolate);
+
+  // Args: fd, lockType, whence, start, len
+
+  if (args.Length() < 5) {
+    isolate->ThrowException(Exception::Error(
+          String::NewFromUtf8(isolate, "acquireRecordLock requires 5 arguments")));
+    return;
+  }
+
+  int fd = args[0]->IntegerValue();
+  short lockType = args[1]->IntegerValue();
+  short whence = args[2]->IntegerValue();
+  off_t start = args[3]->IntegerValue();
+  off_t len = args[4]->IntegerValue();
+
+  struct flock flk;
+  flk.l_type = lockType;
+  flk.l_whence = whence;
+  flk.l_start = start;
+  flk.l_len = len;
+
+  if (-1 == fcntl(fd, F_SETLK, &flk)) {
+    if (errno == EACCES || errno == EAGAIN) {
+      args.GetReturnValue().Set(Boolean::New(isolate, false));
+      return;
+    } else {
+      isolate->ThrowException(UVException(isolate, errno, "acquireRecordLock"));
+      return;
+    }
+  } else {
+    args.GetReturnValue().Set(Boolean::New(isolate, true));
+    return;
+  }
+}
+
+void Initialize(Handle<Object> exports) {
+  NODE_SET_METHOD(exports, "getpwnam", GetPwNam);
+  NODE_SET_METHOD(exports, "getpwuid", GetPwUid);
+  NODE_SET_METHOD(exports, "getgrouplist", GetGroupList);
+  NODE_SET_METHOD(exports, "getgrnam", GetGrNam);
+  NODE_SET_METHOD(exports, "acquireRecordLock", AcquireRecordLock);
+}
+NODE_MODULE(posix, Initialize)
diff --git a/templates/config.html b/templates/config.html
new file mode 100644
index 0000000..c838ced
--- /dev/null
+++ b/templates/config.html
@@ -0,0 +1,48 @@
+    <p>Listed below are all the directives that are supported in Shiny Server config files.</p>
+    <p><strong>Applies to</strong> indicates the kind of parent scope that this directive normally appears inside.</p>
+    <p><strong>Inheritable</strong> means that you can put this directive at a higher level in the hierarchy and it will be inherited by any children to which it might apply. Inherited directives can be overridden by using the directive again in a child scope.</p>
+    <ul class="directives">
+      {{#each directives}}
+        <li>
+          <h3 class="code"><a name="{{name}}"></a>{{name}}</h3>
+          <p class="desc">{{{desc}}}</p>
+          {{#if params}}
+            <table class="params">
+              <colgroup>
+                <col style="width: 105px"></col>
+                <col style="width: 90px"></col>
+                <col style="width: 90px"></col>
+                <col></col>
+                <col style="width: 90px"></col>
+              </colgroup>
+              <tr>
+                <th>Parameter</th>
+                <th>Data type</th>
+                <th>Type</th>
+                <th>Description</th>
+                <th>Default</th>
+              </tr>
+              {{#each params}}
+                <tr>
+                  <td>{{name}}</td>
+                  <td>{{type}}</td>
+                  <td>{{#if optional}}optional{{else}}{{#if vararg}}multiple{{else}}required{{/if}}{{/if}}</td>
+                  <td>{{{desc}}}</td>
+                  <td>{{defaultValue}}</td>
+                </tr>
+              {{/each}}
+            </table>
+          {{else}}
+            <p class="noparams">This directive has no parameters.</p>
+          {{/if}}
+
+          <p>
+            <label>Applies to:</label> {{{scopelist at}}}<br/>
+            <label>Inheritable:</label> {{YesNo otherLocs}}<br/>
+            {{#if children}}
+              <label>Child directives:</label> {{{scopelist children}}}<br/>
+            {{/if}}
+          </p>
+        </li>
+      {{/each}}
+    </ul>
diff --git a/templates/directoryIndex.html b/templates/directoryIndex.html
new file mode 100644
index 0000000..c6c0232
--- /dev/null
+++ b/templates/directoryIndex.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>{{title}}</title>
+  <link href='https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700,400italic,700italic' rel='stylesheet' type='text/css'>
+  <link href='https://fonts.googleapis.com/css?family=Source+Code+Pro' rel='stylesheet' type='text/css'>
+  <style type="text/css">
+  body {
+    font-family: 'Source Sans Pro', sans-serif;
+  }
+  pre, tt, code, .code, #detail {
+    font-family: 'Source Code Pro', monospace;
+  }
+  h1 {
+    font-size: 40px;
+  }
+  a {
+    text-decoration: none;
+  }
+  </style>
+</head>
+<body>
+
+<h1>{{title}}</h1>
+
+<ul>
+  {{#each apps}}
+    <li><a class="code" href="{{this.url}}">{{this.name}}</a> (application)</li>
+  {{/each}}
+  {{#each dirs}}
+    <li><a class="code" href="{{this.url}}/">{{this.name}}/</a></li>
+  {{/each}}
+  {{#each files}}
+    <li><a class="code" href="{{this.url}}">{{this.name}}</a></li>
+  {{/each}}
+</ul>
+
+</body>
+</html>
diff --git a/templates/error.html b/templates/error.html
new file mode 100644
index 0000000..6604d52
--- /dev/null
+++ b/templates/error.html
@@ -0,0 +1,47 @@
+<html>
+<head>
+  <title>{{title}}</title>
+  <link href='http://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700,400italic,700italic' rel='stylesheet' type='text/css'>
+  <link href='http://fonts.googleapis.com/css?family=Source+Code+Pro' rel='stylesheet' type='text/css'>
+  <style type="text/css">
+  body {
+    font-family: 'Source Sans Pro', sans-serif;
+  }
+  pre, tt, code, #detail {
+    font-family: 'Source Code Pro', monospace;
+  }
+  h1 {
+    font-size: 40px;
+  }
+  #message {
+    font-size: 18px;
+  }
+  #detail {
+    font-size: 14px;
+  }
+  #console {
+    white-space: pre;
+    background-color: #2A2A2A;
+    color: #4F4;
+    padding: 12px;
+    border-radius: 4px;
+    overflow: auto;
+  }
+  </style>
+</head>
+<body>
+  <h1>{{title}}</h1>
+  {{#if message}}
+    <p id="message">{{message}}</p>
+  {{/if}}
+  {{#if detail}}
+    <p id="detail">{{detail}}</p>
+  {{/if}}
+  {{#if detailHTML}}
+    <p id="detail">{{{detailHTML}}}</p>
+  {{/if}}
+  {{#if console}}
+    <p id="console"><code>{{console}}</code></p>
+  {{/if}}
+</body>
+</html>
\ No newline at end of file
diff --git a/test/app-config.js b/test/app-config.js
new file mode 100644
index 0000000..1be7382
--- /dev/null
+++ b/test/app-config.js
@@ -0,0 +1,148 @@
+/*
+ * test/app-config.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+var AppConfig = require('../lib/config/app-config').AppConfig;
+var SimpleEventBus = require('../lib/events/simple-event-bus');
+
+describe('AppConfig', function(){
+  describe('#addLocalConfig', function(){
+    it('properly supplements data.', function(){
+      var appConfig = new AppConfig(new SimpleEventBus());
+      var merged = appConfig.addLocalConfig({
+            appDir: '/dir',
+            runAs: 'user',
+            settings: {
+              scheduler: {
+                simple: {}
+              }
+            }
+          },
+          {
+            appDefaults: {
+              initTimeout: 50,
+              idleTimeout: 10
+            }
+          });
+
+      merged.should.have.keys('appDir','runAs', 'settings');
+      merged.settings.should.have.keys('scheduler', 'appDefaults');
+      merged.settings.appDefaults.initTimeout.should.equal(50);
+      Object.keys(merged.settings.scheduler).should.eql(['simple']);
+
+    }),
+    it('properly overrides data.', function(){
+      var appConfig = new AppConfig(new SimpleEventBus());
+      var merged = appConfig.addLocalConfig({
+            appDir: '/dir',
+            runAs: 'user',
+            settings: {
+              scheduler: {
+                simple: {}
+              },
+              appDefaults: {
+                initTimeout: 20,
+                idleTimeout: 20
+              }
+            }
+          },
+          {
+            appDefaults: {
+              initTimeout: 50,
+              idleTimeout: 10
+            }
+          });
+
+      merged.should.have.keys('appDir','runAs', 'settings');
+      merged.settings.should.have.keys('scheduler', 'appDefaults');
+      merged.settings.appDefaults.initTimeout.should.equal(50);
+      Object.keys(merged.settings.scheduler).should.eql(['simple']);
+    }),
+    it('properly merges data.', function(){
+      var appConfig = new AppConfig(new SimpleEventBus());
+      var merged = appConfig.addLocalConfig({
+          appDir: '/dir',
+          runAs: 'user',
+          settings: {
+            appDefaults: {
+              initTimeout: 20
+            }
+          }
+        },
+        {
+          scheduler: {
+            simple: {}
+          },
+          appDefaults: {
+            initTimeout: 50,
+            idleTimeout: 10
+          }
+        });
+
+      merged.should.have.keys('appDir','runAs', 'settings');
+      merged.settings.should.have.keys('scheduler', 'appDefaults');
+      merged.settings.appDefaults.initTimeout.should.equal(50);
+      merged.settings.appDefaults.idleTimeout.should.equal(10);
+      Object.keys(merged.settings.scheduler).should.eql(['simple']);
+    }),
+    it('only overrides specific fields.', function(){
+      var appConfig = new AppConfig(new SimpleEventBus());
+      var merged = appConfig.addLocalConfig({
+          appDir: '/dir',
+          runAs: 'user',
+          settings: {
+            appDefaults: {
+              initTimeout: 20
+            }
+          }
+        },
+        {
+          scheduler: {
+            simple: {}
+          },
+          appDefaults: {
+            initTimeout: 50,
+            idleTimeout: 10
+          },
+          logDir: '/abc'
+        });
+      merged.should.have.keys('appDir','runAs', 'settings');
+      merged.settings.should.have.keys('scheduler', 'appDefaults'); //not logDir
+      merged.settings.appDefaults.initTimeout.should.equal(50);
+      merged.settings.appDefaults.idleTimeout.should.equal(10);
+      Object.keys(merged.settings.scheduler).should.eql(['simple']);
+    }),
+    it('passes app settings through when replacing sched.', function(){
+      var appConfig = new AppConfig(new SimpleEventBus());
+      var merged = appConfig.addLocalConfig({
+          appDir: '/dir',
+          runAs: 'user',
+          settings: {
+            appDefaults: {
+              initTimeout: 60,
+              idleTimeout: 20
+            },
+            scheduler: {simple: {maxRequests: 100}},
+            restart: 1232132
+          }
+        },
+        {
+          scheduler: {
+            simple: {maxRequests: 3}
+          },
+          appDefaults: { },
+          logDir: '/abc'
+        });
+      Object.keys(merged.settings.appDefaults).length.should.equal(2);
+    });
+  });
+});
\ No newline at end of file
diff --git a/test/apps/01_hello/server.R b/test/apps/01_hello/server.R
new file mode 100644
index 0000000..c71a436
--- /dev/null
+++ b/test/apps/01_hello/server.R
@@ -0,0 +1,20 @@
+library(shiny)
+
+# Define server logic required to generate and plot a random distribution
+shinyServer(function(input, output) {
+   
+  # Expression that generates a plot of the distribution. The expression
+  # is wrapped in a call to renderPlot to indicate that:
+  #
+  #  1) It is "reactive" and therefore should be automatically 
+  #     re-executed when inputs change
+  #  2) Its output type is a plot 
+  #
+  output$distPlot <- renderPlot({
+        
+    # generate an rnorm distribution and plot it
+    dist <- rnorm(input$obs)
+    hist(dist)
+  })
+  
+})
diff --git a/test/apps/01_hello/ui.R b/test/apps/01_hello/ui.R
new file mode 100644
index 0000000..161e161
--- /dev/null
+++ b/test/apps/01_hello/ui.R
@@ -0,0 +1,22 @@
+library(shiny)
+
+# Define UI for application that plots random distributions 
+shinyUI(pageWithSidebar(
+  
+  # Application title
+  headerPanel("Hello Shiny!"),
+  
+  # Sidebar with a slider input for number of observations
+  sidebarPanel(
+    sliderInput("obs", 
+                "Number of observations:", 
+                min = 1, 
+                max = 1000, 
+                value = 500)
+  ),
+  
+  # Show a plot of the generated distribution
+  mainPanel(
+    plotOutput("distPlot")
+  )
+))
diff --git a/test/config-router-util.js b/test/config-router-util.js
new file mode 100644
index 0000000..eb389b2
--- /dev/null
+++ b/test/config-router-util.js
@@ -0,0 +1,160 @@
+/*
+ * test/config-router-util.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+var configRouterUtil = require('../lib/router/config-router-util');
+var sinon = require('sinon');
+var config = require('../lib/config/config'); 
+var ConfigNode = config.ConfigNode;
+var _ = require('underscore');
+
+var should = require('should'); // need the static functions.
+
+var APP_PATH = {appDir: "/somePath"};
+var SIMPLE_PARAMS = {maxRequests: 10};
+var APP_INIT_PARAMS = {timeout: 5};
+var APP_IDLE_PARAMS = {timeout: 8};
+var LOCATION_PARAMS = {path: '/'};
+
+/** 
+ * Helper function to init a sample config for testing.
+ * 
+ * @param scheduler Where to include a scheduler. If falsy, won't be
+ *   generated anywhere. If 'self', will be added to the app node. If
+ *   'parent', will be added to the parent node.
+ * @param idle Where to include the `app_idle_timeout` parameter. Same
+ *    options as are available for 'scheduler' param.
+ * @param init Where to include the `app_init_timeout` parameter. Same
+ *    options as are available for 'scheduler' param.
+ */
+function initConfig(scheduler, idle, init){
+
+  // Helper function that returns the appropriate node given the string parameter
+  function chooseNode(parentNode, thisNode, selection){
+    if (selection === "self"){
+      return thisNode;
+    } else if (selection === "parent"){
+      return parentNode;
+    } else{
+      throw new Error("Invalid selection: " + selection);
+    }
+  }
+
+  var parentNode = new ConfigNode(null, 'location', _.values(LOCATION_PARAMS), null);
+  parentNode.values = LOCATION_PARAMS;
+
+  var appNode = new ConfigNode(parentNode, 'application', _.values(APP_PATH), null);
+  appNode.values = APP_PATH;
+  // Since we're not doing the schole schema validation, we'll just 
+  // short-cirtuit and specify the values directly.
+
+  if (scheduler){
+    var simpleNode = new ConfigNode( chooseNode(parentNode, appNode, scheduler),
+        'simple_scheduler', _.values(SIMPLE_PARAMS), null);
+    simpleNode.values = SIMPLE_PARAMS;
+    chooseNode(parentNode, appNode, scheduler).children.push(simpleNode);
+  }
+
+  if(idle){
+    var idleNode = new ConfigNode( chooseNode(parentNode, appNode, idle)
+      , 'app_idle_timeout', _.values(APP_IDLE_PARAMS), null);
+    idleNode.values = APP_IDLE_PARAMS;
+    chooseNode(parentNode, appNode, idle).children.push(idleNode); 
+  }
+
+  if(init){
+    var initNode = new ConfigNode( chooseNode(parentNode, appNode, init), 
+        'app_init_timeout', _.values(APP_INIT_PARAMS), null);
+    initNode.values = APP_INIT_PARAMS;
+    chooseNode(parentNode, appNode, init).children.push(initNode);
+  }
+
+  return appNode;
+}
+
+
+describe('ConfigRouterUtil', function(){  
+  describe('#parseApplication', function(){
+    it('extracts params from local node w/ defaults', function(){
+      var appNode = initConfig("self", "self", "self");
+
+      var settings = configRouterUtil.parseApplication({}, appNode, true);
+
+      settings.scheduler.should.eql({simple: {maxRequests: 10}});
+      settings.appDefaults.initTimeout.should.equal(5);
+      settings.appDefaults.idleTimeout.should.equal(8);      
+    }),
+    it('extracts params from parent node w/ defaults', function(){
+      var appNode = initConfig("parent", "parent", "parent");
+
+      var settings = configRouterUtil.parseApplication({}, appNode, true);
+
+      settings.scheduler.should.eql({simple: {maxRequests: 10}});
+      settings.appDefaults.initTimeout.should.equal(5);
+      settings.appDefaults.idleTimeout.should.equal(8);
+    }),
+    it('extracts params from local node w/o defaults', function(){
+      var appNode = initConfig("self", "self", "self");
+
+      var settings = configRouterUtil.parseApplication({}, appNode, false);
+
+      settings.scheduler.should.eql({simple: {maxRequests: 10}});
+      settings.appDefaults.initTimeout.should.equal(5);
+      settings.appDefaults.idleTimeout.should.equal(8);
+    }),
+    it('extracts params from parent node w/o defaults', function(){
+      var appNode = initConfig("parent", "parent", "parent");
+
+      var settings = configRouterUtil.parseApplication({}, appNode, false);
+
+      settings.scheduler.should.eql({simple: {maxRequests: 10}});
+      settings.appDefaults.initTimeout.should.equal(5);
+      settings.appDefaults.idleTimeout.should.equal(8);
+    }),
+    it('provide idleTimeout if missing w/ provideDefaults', function(){
+      var appNode = initConfig("parent", null, "parent");
+
+      var settings = configRouterUtil.parseApplication({}, appNode, true);
+
+      settings.scheduler.should.eql({simple: {maxRequests: 10}});
+      settings.appDefaults.initTimeout.should.equal(5);
+      settings.appDefaults.idleTimeout.should.equal(5);
+    }),
+    it('provide all if missing w/ provideDefaults', function(){
+      var appNode = initConfig(null, null, null);
+
+      var settings = configRouterUtil.parseApplication({}, appNode, true);
+
+      settings.scheduler.should.eql({simple: {maxRequests: 100}});
+      settings.appDefaults.initTimeout.should.equal(60);
+      settings.appDefaults.idleTimeout.should.equal(5);
+    }),
+    it('provide only scheduler if all missing w/o provideDefaults', function(){
+      var appNode = initConfig(null, null, null);
+
+      var settings = configRouterUtil.parseApplication({}, appNode, false);
+
+      settings.scheduler.should.eql({simple: {maxRequests: 100}});
+      should.not.exist(settings.appDefaults.idleTimeout);
+      should.not.exist(settings.appDefaults.initTimeout);
+    }),
+    it('won\'t provide idleTimeout if missing w/o provideDefaults', function(){
+      var appNode = initConfig("parent", null, "parent");
+
+      var settings = configRouterUtil.parseApplication({}, appNode, false);
+
+      settings.scheduler.should.eql({simple: {maxRequests: 10}});
+      settings.appDefaults.initTimeout.should.equal(5);
+      should.not.exist(settings.appDefaults.idleTimeout);
+    })
+  })
+});
diff --git a/test/configs/bad1.config b/test/configs/bad1.config
new file mode 100644
index 0000000..1f9e723
--- /dev/null
+++ b/test/configs/bad1.config
@@ -0,0 +1,13 @@
+run_as shiny;
+
+server {
+  listen 3838;
+
+  location /a {
+    app_dir /c;
+
+    location /b {
+      # location /b inherits app_dir from loc /a, this is an error
+    }
+  }
+}
diff --git a/test/configs/bad2.config b/test/configs/bad2.config
new file mode 100644
index 0000000..57f9082
--- /dev/null
+++ b/test/configs/bad2.config
@@ -0,0 +1,11 @@
+run_as shiny;
+
+server {
+  listen 3838;
+
+  location /a {
+    
+    location /b {
+    }
+  }
+}
diff --git a/test/configs/testapps.config.in b/test/configs/testapps.config.in
new file mode 100644
index 0000000..7948007
--- /dev/null
+++ b/test/configs/testapps.config.in
@@ -0,0 +1,11 @@
+run_as $USER;
+
+server {
+  listen 3838;
+
+  location / {
+    site_dir $ROOT/test/apps;
+    log_dir /tmp/shiny-server-test;
+    directory_index on;
+  }
+}
diff --git a/test/configs/valid.config b/test/configs/valid.config
new file mode 100644
index 0000000..adf55a5
--- /dev/null
+++ b/test/configs/valid.config
@@ -0,0 +1,29 @@
+run_as shiny;
+
+server {
+  listen 3838;
+
+  location /a {
+    # Top-level location; this is a real one
+    site_dir /srv/shiny-server;
+    log_dir /var/log/shiny-server;
+    directory_index on;
+    app_idle_timeout 30;
+
+    location /b {
+      # Second-level location takes precedence over top
+      app_dir /b;
+      app_idle_timeout 5;
+    }
+
+    location /c {
+      # Second-level location takes precedence over top
+      app_dir /c;
+    }
+
+    location /d {
+      # Second-level location takes precedence over top
+      directory_index false;
+    }
+  }
+}
diff --git a/test/mocha.opts b/test/mocha.opts
new file mode 100644
index 0000000..1b139bf
--- /dev/null
+++ b/test/mocha.opts
@@ -0,0 +1,4 @@
+--require should
+--require ./lib/core/log.js
+--require ./lib/core/qutil
+--reporter spec
\ No newline at end of file
diff --git a/test/nested-locations.js b/test/nested-locations.js
new file mode 100644
index 0000000..fa9c83e
--- /dev/null
+++ b/test/nested-locations.js
@@ -0,0 +1,80 @@
+/*
+ * nested-locations.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+var assert = require('assert');
+var path = require('path');
+var util = require('util');
+var rewire = require("rewire");
+var Q = require('q');
+var paths = require('../lib/core/paths');
+
+var config_router = rewire('../lib/router/config-router');
+
+// Don't check if permissions are valid, as this would require us to
+// run the tests as root
+config_router.__set__("checkPermissions", function() {});
+
+function testBadConfig(desc, configFile, errorPattern) {
+  it(desc, function(done) {
+    config_router.createRouter_p(configFile, null)
+    .then(
+      function(router) {
+        done(new Error('expected an error'));
+      },
+      function(err) {
+        if (errorPattern.test(err.message)) {
+          done();
+        } else {
+          done(err);
+        }
+      }
+    )
+  });
+}
+
+describe('Nested locations', function() {
+  it('can parse valid configs', function(done) {
+    var configFile = paths.projectFile('test/configs/valid.config');
+    config_router.createRouter_p(configFile, null)
+    .then(function(router) {
+      var locs = router.servers[0].$locations;
+      var locB = locs[0];
+      var locC = locs[1];
+      var locD = locs[2];
+      var locA = locs[3];
+      locB.$prefix.should.equal('/a/b/');
+      locB.$settings.appDefaults.idleTimeout.should.equal(5);
+      locC.$prefix.should.equal('/a/c/');
+      locC.$settings.appDefaults.idleTimeout.should.equal(30);
+      locD.$router.$dirIndex.should.equal(false);
+      locD.$router.$root.should.equal('/srv/shiny-server');
+      locA.$dirIndex.should.equal(true);
+      locA.$root.should.equal('/srv/shiny-server');
+    })
+    .then(done, done)
+    .done();
+  });
+
+  testBadConfig(
+    'detects invalid nesting',
+    paths.projectFile('test/configs/bad1.config'),
+    /may not inherit the app_dir directive/
+  );
+  
+  testBadConfig(
+    'detects no hosting model',
+    paths.projectFile('test/configs/bad2.config'),
+    /must contain \(or inherit\)/
+  );
+  
+});
diff --git a/test/proxy-events.js b/test/proxy-events.js
new file mode 100644
index 0000000..d6c3d50
--- /dev/null
+++ b/test/proxy-events.js
@@ -0,0 +1,68 @@
+/*
+ * test/proxy-events.js
+ *
+ * Copyright (C) 2009-16 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+const child_process = require("child_process");
+
+const bash = require("bash");
+const _ = require("underscore");
+
+const proxy_http = require("../lib/proxy/http");
+
+describe("http-proxy", () => {
+
+  // Burned several times now by http-proxy changing its events. Let's
+  // at least fail some tests if they add or remove event names.
+  it("has not changed its supported events", (done) => {
+    let knownEvents = proxy_http.knownEvents;
+
+    let pipeline = [
+      // Find all instances of ".emit(..."
+      // Since we're using -o and -h, only the actual matches will be printed
+      ["grep", "-Roh", "\\.emit([^,]\\+", "node_modules/http-proxy"],
+      // Strip the ".emit(" prefix
+      ["sed", "s/\\.emit(//"],
+      // Strip quotation marks, whitespace
+      ["sed", "s/['\" ]//g"],
+      // Remove dupes with sort+uniq
+      ["sort"],
+      ["uniq"]
+    ];
+
+    pipeline = _.map(pipeline, (args) => bash.escape.apply(bash, args));
+
+    child_process.exec(pipeline.join("|"),
+      (error, stdout, stderr) => {
+        if (error) {
+          done(error);
+          return;
+        }
+
+        let actualEvents = stdout.trim().split("\n");
+
+        let fictional = _.difference(proxy_http.knownEvents, actualEvents);
+        let discovered = _.difference(actualEvents, proxy_http.knownEvents);
+
+        if (fictional.length > 0) {
+          done(new Error("Detected fictional event(s): " + fictional.join(", ")));
+          return;
+        }
+        if (discovered.length > 0) {
+          done(new Error("Discovered new event(s): " + discovered.join(", ")));
+          return;
+        }
+        done();
+      }
+    );
+
+  });
+});
\ No newline at end of file
diff --git a/test/render.js b/test/render.js
new file mode 100644
index 0000000..70dfadb
--- /dev/null
+++ b/test/render.js
@@ -0,0 +1,191 @@
+/*
+ * test/render.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+var rewire = require('rewire');
+var sinon = require('sinon');
+var _ = require('underscore');
+var Q = require('q');
+var should = require('should'); //need static function
+var paths = require('../lib/core/paths');
+var render = rewire('../lib/core/render');
+
+var PATH = '/some/path/';
+
+var TEMPLATE = require('fs').
+  readFileSync('templates/error.html', 'utf-8');
+
+var mockFS = {
+  existsSync : function(path){
+    
+  },
+  readFileSync : function(path){
+    
+  }
+}
+
+var readStub;
+var existsStub;
+
+render.__set__("fs", mockFS);
+
+var res = {
+  writeHead : sinon.spy(),
+  end : sinon.spy()
+};
+
+describe('Render', function(){  
+  beforeEach(function(){
+    res.writeHead.reset();
+    res.end.reset();
+    
+    render.flushCache();
+
+    if (readStub){
+      readStub.restore();
+      existsStub.restore();
+    }
+
+    readStub = sinon.stub(mockFS, "readFileSync").returns(TEMPLATE);
+    existsStub = sinon.stub(mockFS, "existsSync");
+  }),
+
+  describe('#sendPage', function(){
+    it('Reads custom dir/templates if they exist', function(){
+      existsStub.withArgs(PATH+'error-temp1.html').returns(true);
+
+      render.sendPage(res, 200, 'title', {
+        templateDir: PATH,
+        template: 'error-temp1'
+      });
+      
+      readStub.calledOnce.should.be.true;
+      readStub.firstCall.args[0].should.eql(PATH + 'error-temp1.html');
+      readStub.firstCall.args[1].should.eql('utf-8');
+      
+      res.writeHead.calledWith(200).should.be.true;
+      res.end.calledOnce.should.be.true;
+    }),
+    it('Reads provided templates if they exist', function(){
+      var templatePath =  paths.projectFile('templates/error-temp1.html');
+      existsStub.withArgs(PATH+'error-temp1.html').returns(false);
+      existsStub.withArgs(templatePath).returns(true);
+
+      render.sendPage(res, 200, 'title', {
+        templateDir: PATH,
+        template: 'error-temp1'
+      });
+
+      readStub.calledOnce.should.be.true;
+      readStub.firstCall.args[0].should.eql(templatePath);
+      readStub.firstCall.args[1].should.eql('utf-8');
+      
+      res.writeHead.calledWith(200).should.be.true;
+      res.end.calledOnce.should.be.true;
+    }),
+    it('Falls back to more general custom templates before using provided tmplts', function(){
+      existsStub.withArgs(PATH+'error-temp1.html').returns(false);
+      existsStub.withArgs(paths.projectFile('templates/error-temp1.html')).returns(false);
+      existsStub.withArgs(PATH+'error.html').returns(true);
+      existsStub.withArgs(paths.projectFile('templates/error.html')).returns(true);
+
+      render.sendPage(res, 200, 'title', {
+        templateDir: PATH,
+        template: 'error-temp1'
+      });
+
+      readStub.calledOnce.should.be.true;
+      readStub.firstCall.args[0].should.eql(PATH+'error.html');
+      readStub.firstCall.args[1].should.eql('utf-8');
+      
+      res.writeHead.calledWith(200).should.be.true;
+      res.end.calledOnce.should.be.true;
+    }),
+    it('Reads custom default error template if it exists', function(){
+      existsStub.withArgs(PATH+'error-temp1.html').returns(false);
+      existsStub.withArgs(paths.projectFile('templates/error-temp1.html')).returns(false);
+      existsStub.withArgs(PATH+'error.html').returns(true);
+
+      render.sendPage(res, 200, 'title', {
+        templateDir:PATH,
+        template: 'error-temp1'
+      });
+
+      readStub.calledOnce.should.be.true;
+      readStub.firstCall.args[0].should.eql(PATH+'error.html');
+      readStub.firstCall.args[1].should.eql('utf-8');
+      
+      res.writeHead.calledWith(200).should.be.true;
+      res.end.calledOnce.should.be.true;
+    }),
+    it('Reads provided default error template if nothing else exists', function(){
+      existsStub.withArgs(PATH+'error-temp1.html').returns(false);
+      existsStub.withArgs(paths.projectFile('templates/error-temp1.html')).returns(false);
+      existsStub.withArgs(PATH+'error.html').returns(false);
+      existsStub.withArgs(paths.projectFile('templates/error.html')).returns(true);
+      
+      render.sendPage(res, 200, 'title', {
+        templateDir:PATH,
+        template: 'error-temp1'
+      });
+      
+      readStub.calledOnce.should.be.true;
+      readStub.firstCall.args[0].should.eql(paths.projectFile('templates/error.html'));
+      readStub.firstCall.args[1].should.eql('utf-8');
+
+      res.writeHead.calledWith(200).should.be.true;
+      res.end.calledOnce.should.be.true;
+    }),
+    it('Caches templates', function(){
+      existsStub.withArgs(PATH+'temp1.html').returns(true);
+      
+      render.sendPage(res, 200, 'title', {
+        templateDir:PATH,
+        template: 'temp1'
+      });
+      
+      readStub.calledOnce.should.be.true;
+      readStub.firstCall.args[0].should.eql(PATH+'temp1.html');
+      readStub.firstCall.args[1].should.eql('utf-8');
+
+      render.sendPage(res, 200, 'title', {
+        templateDir:PATH,
+        template: 'temp1'
+      });
+
+      readStub.calledOnce.should.be.true;
+    }),
+    it('Flushes templates from cache on demand', function(){
+      existsStub.withArgs(PATH+'temp1.html').returns(true);
+      
+      render.sendPage(res, 200, 'title', {
+        templateDir:PATH,
+        template: 'temp1'
+      });
+      
+      readStub.calledOnce.should.be.true;
+      readStub.firstCall.args[0].should.eql(PATH+'temp1.html');
+      readStub.firstCall.args[1].should.eql('utf-8');
+
+      render.flushCache();
+
+      render.sendPage(res, 200, 'title', {
+        templateDir:PATH,
+        template: 'temp1'
+      });
+
+      readStub.callCount.should.eql(2);
+    })
+  });
+
+});
+      
\ No newline at end of file
diff --git a/test/robust-sockjs.js b/test/robust-sockjs.js
new file mode 100644
index 0000000..e6d7378
--- /dev/null
+++ b/test/robust-sockjs.js
@@ -0,0 +1,97 @@
+/*
+ * test/robust-sockjs.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+var RobustSockJS = require('../lib/proxy/robust-sockjs');
+var sinon = require('sinon');
+var should = require('should');
+var _ = require('underscore');
+
+describe('RobustSockJS', function(){
+  describe('#robustify', function(){
+    it('passes through on invalid URL', function(){
+      var rsjs = new RobustSockJS();
+      var conn = {url: 'blah', close: sinon.spy(), write: sinon.spy()};
+      var result = rsjs.robustify(conn);
+      (conn === result).should.be.true;
+    });
+    it('handles all 4 cases properly', function(){
+      var rsjs = new RobustSockJS();
+      _.size(rsjs._connections).should.equal(0);
+
+      // Fresh connections work
+      var conn = {
+        url: '/__sockjs__/n=1234/',
+        close: sinon.spy(),
+        write: function(){}
+      };
+      var rob = rsjs.robustify(conn);
+      _.size(rsjs._connections).should.equal(1);
+      (conn === rob).should.be.false;
+
+      // ID collisions fail
+      var rob2 = rsjs.robustify(conn);
+      _.size(rsjs._connections).should.equal(1);
+      (rob2 === undefined).should.be.true;
+
+      // Reconnects succeed.
+      conn.url = conn.url.replace(/\/n=/, '/o=');
+      var rob3 = rsjs.robustify(conn);
+      _.size(rsjs._connections).should.equal(1);
+      (rob3 === undefined).should.be.true;
+
+      // Reconnects of expired/invalid IDs fail.
+      conn.url = conn.url.replace(/1234/, 'abcd');
+      var rob4 = rsjs.robustify(conn);
+      _.size(rsjs._connections).should.equal(1);
+      (rob4 === undefined).should.be.true;
+    });
+    it('buffers disconnects', function(){
+      var clock = sinon.useFakeTimers();
+      var rsjs = new RobustSockJS(1); //Timeout after 1 sec
+      var conn = {
+        url: '/__sockjs__/n=1234/',
+        close: sinon.spy(),
+        write: function(){},
+        emit: function(){}
+      };
+
+      rob = rsjs.robustify(conn);
+      _.size(rsjs._connections).should.equal(1);
+
+      conn.emit('close');
+      conn.emit('end');
+
+      clock.tick(500);
+
+      // Should still be available
+      _.size(rsjs._connections).should.equal(1);
+
+      // Reconnect
+      conn.url = conn.url.replace(/\/n=/, '/o=');
+      rsjs.robustify(conn);
+
+      clock.tick(750);
+
+      conn.emit('close');
+      conn.emit('end');
+
+      _.size(rsjs._connections).should.equal(1);
+
+      clock.tick(1250);
+
+      _.size(rsjs._connections).should.equal(0);
+
+      clock.restore();
+    });
+  });
+});
diff --git a/test/scheduler-registry.js b/test/scheduler-registry.js
new file mode 100644
index 0000000..fc373f1
--- /dev/null
+++ b/test/scheduler-registry.js
@@ -0,0 +1,114 @@
+/*
+ * test/scheduler-registry.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+var _ = require('underscore');
+var SimpleEventBus = require('../lib/events/simple-event-bus');
+var rewire = require("rewire");
+var sinon = require('sinon');
+
+// Rewire the module in test so we can stub its modules.
+var SchedulerRegistry = rewire('../lib/scheduler/scheduler-registry');
+
+// Stub up a SimpleScheduler
+var SimpleScheduler =  function (eventBus, appSpec, timeout) {
+  this.type = "MockSimpleScheduler";
+};
+SimpleScheduler.prototype.acquireWorker_p = function(appSpec, url, worker){
+  return {type: "MockWorker"};
+}
+SchedulerRegistry.__set__("SimpleScheduler", SimpleScheduler);
+
+// Stub an appSpec
+var appSpec = {
+  getKey: function(){return "appSpecKey"},
+  settings: {appDefaults: {sessionTimeout: 10}}
+};
+
+//  Init an eventBus on which we can spy.
+var eventBus =  new SimpleEventBus();
+
+// Define some static params which will be passed in.
+var URL = "/URL";
+var WORKER = "SomeWorker";
+
+// Spy on the acquireWorker_p function.
+var acquireWorkerSpy = sinon.spy(SimpleScheduler.prototype, "acquireWorker_p");
+
+
+describe('SchedulerRegistry', function(){  
+  afterEach(function(){
+    acquireWorkerSpy.reset();
+  })
+
+  describe('#getWorker_p', function(){
+    it('Creates a new scheduler on initial request.', function(){
+      var schedReg = new SchedulerRegistry(eventBus);
+
+      _.size(schedReg.$schedulers).should.equal(0);
+      schedReg.getWorker_p(appSpec, URL, WORKER);
+      
+      // Confirm we created the scheduler in the right place and of the right type.
+      _.size(schedReg.$schedulers).should.equal(1);
+      _.keys(schedReg.$schedulers).should.eql([appSpec.getKey()]);
+      schedReg.$schedulers[appSpec.getKey()].should.containEql({type: "MockSimpleScheduler"});
+
+      acquireWorkerSpy.callCount.should.equal(1);
+    }),
+    it('Doesn\'t create a new scheduler for a repeat request.', function(){
+      var schedReg = new SchedulerRegistry(eventBus);
+
+      _.size(schedReg.$schedulers).should.equal(0);
+      schedReg.getWorker_p(appSpec, URL, WORKER);
+      schedReg.getWorker_p(appSpec, URL, WORKER);
+      
+      _.size(schedReg.$schedulers).should.equal(1);
+      // Confirm we created the scheduler in the right place and of the right type.
+      _.keys(schedReg.$schedulers).should.containEql(appSpec.getKey());
+      schedReg.$schedulers[appSpec.getKey()].should.containEql({type: "MockSimpleScheduler"});
+
+      acquireWorkerSpy.callCount.should.equal(2);
+    }),
+    it('Deletes vacant schedulers.', function(){
+      var schedReg = new SchedulerRegistry(eventBus);
+
+      _.size(schedReg.$schedulers).should.equal(0);
+      schedReg.getWorker_p(appSpec, URL, WORKER);
+      _.size(schedReg.$schedulers).should.equal(1);
+
+      eventBus.emit('vacantSched', appSpec.getKey());
+
+      _.size(schedReg.$schedulers).should.equal(0);
+    }),
+    it('Creates a new scheduler for a new AppSpec & creates respective workers', function(){
+      var schedReg = new SchedulerRegistry(eventBus);
+
+      var alternateAppSpec =  {
+        getKey: function(){return "alternateAppSpec"},
+        settings: {appDefaults: {sessionTimeout: 10}}
+      }; 
+
+      _.size(schedReg.$schedulers).should.equal(0);
+      schedReg.getWorker_p(appSpec, URL, WORKER);
+      schedReg.getWorker_p(alternateAppSpec, URL, WORKER);
+      
+      _.size(schedReg.$schedulers).should.equal(2);
+      // Confirm we created the scheduler in the right place and of the right type.
+      _.keys(schedReg.$schedulers).should.containEql(appSpec.getKey());
+      schedReg.$schedulers[appSpec.getKey()].should.containEql({type: "MockSimpleScheduler"});
+
+      acquireWorkerSpy.callCount.should.equal(2);
+      acquireWorkerSpy.firstCall.calledWithExactly(appSpec, URL, WORKER).should.be.true;
+      acquireWorkerSpy.secondCall.calledWithExactly(alternateAppSpec, URL, WORKER).should.be.true;
+    })
+  });
+})
diff --git a/test/scheduler.js b/test/scheduler.js
new file mode 100644
index 0000000..414e01a
--- /dev/null
+++ b/test/scheduler.js
@@ -0,0 +1,155 @@
+/*
+ * test/scheduler.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+var AppSpec = require('../lib/worker/app-spec.js');
+var sinon = require('sinon');
+var Q = require('q');
+var _ = require('underscore');
+var SimpleEventBus = require('../lib/events/simple-event-bus');
+var rewire = require("rewire");
+var sinon = require('sinon');
+
+// need the static functions
+var should = require('should');
+
+var Scheduler = rewire('../lib/scheduler/scheduler.js');
+
+var exitPromise = Q.defer();
+
+var killSpy = sinon.spy();
+Scheduler.__set__("app_worker", {launchWorker_p: function(){
+  return Q({
+    kill: killSpy,
+    getExit_p: function(){ return exitPromise.promise; },
+    isRunning: function(){ return true }
+  });
+}});
+
+var appSpec = new AppSpec("/var/shiny-www/01_hello/", "jeff", "", "/tmp", {})
+var scheduler;
+
+var clock = sinon.useFakeTimers();
+
+describe('Scheduler', function(){
+  beforeEach(function(){
+    scheduler = new Scheduler(new SimpleEventBus(), appSpec.getKey());
+    scheduler.setTransport({alloc_p: function(){
+      return Q({
+        getLogFileSuffix: function(){ return "" },
+        ToString: function(){ return "" },
+        toString: function(){ return "" },
+        connect_p: function(){ return Q(true) }
+      })
+    }});
+
+    killSpy.reset();
+    exitPromise = Q.defer();
+  });
+
+
+
+  describe('#spawnWorker_p', function(){
+    it('properly stores provided data.', function(done){
+      //check that we're starting off with no workers.
+      Object.keys(scheduler.$workers).should.be.empty;
+
+      //request a worker
+      scheduler.spawnWorker_p(appSpec, {a:5, b:"test"})
+      .then(function(wh){
+        //check that exactly one worker has been created
+        Object.keys(scheduler.$workers).should.have.length(1);
+
+        //check that the worker has the necessary fields created.
+        var worker = scheduler.$workers[Object.keys(scheduler.$workers)[0]];
+        worker.should.have.keys('data', 'promise');
+        worker.data.should.have.keys('a', 'b', 'sockConn', 'httpConn', 
+          'pendingConn', 'timer');
+      })      
+      .then(done, done).done();
+    }),
+    it('properly handles acquire and release.', function(done){
+      //request a worker
+      scheduler.spawnWorker_p(appSpec, {})
+      .then(function(wh){
+        var worker = scheduler.$workers[Object.keys(scheduler.$workers)[0]];
+
+        worker.data.httpConn.should.equal(0);
+        worker.data.sockConn.should.equal(0);
+
+        wh.acquire('http');
+        
+        worker.data.httpConn.should.equal(1);
+        worker.data.sockConn.should.equal(0);
+
+        wh.acquire('sock');
+        
+        worker.data.httpConn.should.equal(1);
+        worker.data.sockConn.should.equal(1);
+
+        wh.release('http');
+        
+        worker.data.httpConn.should.equal(0);
+        worker.data.sockConn.should.equal(1);
+
+        wh.release('sock');
+        
+        worker.data.httpConn.should.equal(0);
+        worker.data.sockConn.should.equal(0);        
+      })      
+      .then(done, done).done();
+    }),
+    it('sets timer after last connection.', function(done){
+      //request a worker
+      scheduler.spawnWorker_p(appSpec, {})
+      .then(function(wh){
+        // TODO: clean up 
+        // The old tests all have pending kill timers which the spy will
+        // capture if we just run this test now. Advance the clock to get
+        // those kills out of the way then reset the spy to get an accurate
+        // count from JUST this test.
+        clock.tick(5500);
+        killSpy.reset();
+
+        //check that the worker has the necessary fields created.
+        var worker = scheduler.$workers[Object.keys(scheduler.$workers)[0]];
+        
+        // make a connection so there should be no timer.
+        wh.acquire('sock');
+
+        should.not.exist(worker.data.timer);
+
+        // release the only connection which should trigger the timer
+        wh.release('sock');
+
+        should.exist(worker.data.timer);
+
+        // Advance time far enough that the process
+        // should have been killed
+        clock.tick(5500);
+
+        killSpy.callCount.should.equal(1);
+
+        // Mark the process as killed
+        exitPromise.resolve(true);
+        return exitPromise.promise.then(function(){
+          Object.keys(scheduler.$workers).should.have.length(0);
+        })
+      })      
+      .then(done, done).done();
+    })
+  })
+
+  after(function(){
+    clock.restore();
+  });
+})
diff --git a/test/simple-scheduler.js b/test/simple-scheduler.js
new file mode 100644
index 0000000..49ea657
--- /dev/null
+++ b/test/simple-scheduler.js
@@ -0,0 +1,177 @@
+/*
+ * test/simple-scheduler.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+var AppSpec = require('../lib/worker/app-spec');
+var Q = require('q');
+var map = require('../lib/core/map');
+var _ = require('underscore');
+var SimpleEventBus = require('../lib/events/simple-event-bus');
+var rewire = require("rewire");
+var sinon = require('sinon');
+var SimpleScheduler = require('../lib/scheduler/simple-scheduler');
+
+// Setup a simple scheduler with a max of 4 requests.
+var MAX_REQUESTS = 4;
+var appSpec;
+var key;
+
+// Spy on the spawnWorker_p function.
+var spawnWorkerSpy;
+
+// Scope a scheduler var here that will be reinitialized beforeEach().
+var scheduler;
+
+// Helper function to quickly add workers to a scheduler
+function addWorker(scheduler, id, sock, http, pending, isPending){
+  if (!scheduler.$workers){
+    scheduler.$workers = map.create();
+  }
+
+  scheduler.$workers[id] = map.create();
+  scheduler.$workers[id].data = map.create();
+  scheduler.$workers[id].data['sockConn'] = sock;
+  scheduler.$workers[id].data['httpConn'] = http;
+  scheduler.$workers[id].data['pendingConn'] = pending;
+  scheduler.$workers[id].promise = {isPending: function(){return isPending}};
+
+  scheduler.spawnWorker_p();
+
+  return scheduler.$workers[id];
+}
+
+describe('SimpleScheduler', function(){
+  beforeEach(function(){
+    scheduler = new SimpleScheduler(new SimpleEventBus(), appSpec);
+
+    // Would be much better to use rewire to overwrite the prototype 
+    // definition of this function. Unfortunately, it doesn't seem to 
+    // work properly in the context of util.inherit(), and it kept calling
+    // the actual spawnWorker_p code in Scheduler. So I'm resorting to this
+    // manual override.
+    scheduler.spawnWorker_p = function(appSpec){
+      return Q({type: "mockWorker"});
+    }
+
+    // Since we can't globally inject ourselves into scheduler, redefine
+    // the spy before each test.
+    spawnWorkerSpy = sinon.spy(scheduler, "spawnWorker_p");
+
+    appSpec = {
+      getKey: function(){return "simpleAppSpecKey"},
+      settings: {appDefaults: {sessionTimeout: 10}, scheduler: 
+        {simple: {maxRequests: MAX_REQUESTS}}}
+    };
+
+    var key =  appSpec.getKey();
+
+  }),
+  afterEach(function(){
+    spawnWorkerSpy.reset();
+  }),
+  describe('#acquireWorker_p()', function(done){
+    it('should initially create a new worker.', function(){
+      //request a worker
+      scheduler.acquireWorker_p(appSpec)
+      .then(function(wh){
+        spawnWorkerSpy.callCount.should.equal(1);
+      })
+      .then(done, done).done();
+    }),
+    it('should not create a new worker when one exists.', function(done){
+      var WORKER_ID = "WORKER";
+      var mockWorker = 
+        addWorker(scheduler, WORKER_ID, 0, 0, 0, false);
+      
+      // Reset after adding the initial worker.
+      spawnWorkerSpy.reset();
+
+      scheduler.acquireWorker_p(appSpec)
+      .then(function(wh){
+        // check that spawn() wasn't called when a
+        // worker already existed
+        spawnWorkerSpy.callCount.should.equal(0);
+
+        wh.should.equal(mockWorker.promise);
+      })
+      .then(done, done).done();
+
+    }),
+    it('should not limit if there is no max', function(done){
+      var WORKER_ID = "WORKER";
+      var mockWorker = 
+        addWorker(scheduler, WORKER_ID, 10000, 0, 0, false);
+      
+      // Reset after adding the initial worker.
+      spawnWorkerSpy.reset();      
+
+      appSpec.settings.scheduler = {simple: {maxRequests: 0}};
+
+      scheduler.acquireWorker_p(appSpec)
+      .then(function(wh){
+        // ensure there's no error and that we got the right
+        // data back.
+        wh.should.equal(mockWorker.promise);
+      })
+      .then(done, done).done();
+    }),
+    it('should approach the MAX_REQUESTS directive.', function(done){
+      var WORKER_ID = "WORKER";
+      var mockWorker = 
+        addWorker(scheduler, WORKER_ID, MAX_REQUESTS - 1, 0, 0, false);
+
+      //request a worker for the new app
+      scheduler.acquireWorker_p(appSpec, '/')
+      .then(function(wh){
+        // should succeed, there's room for one more.
+        wh.should.equal(mockWorker.promise);
+      })
+      .then(done, done).done();
+    }),
+    it('should not exceed the MAX_REQUESTS directive on the base URL.', function(){
+      var WORKER_ID = "WORKER";
+      var mockWorker = 
+        addWorker(scheduler, WORKER_ID, MAX_REQUESTS, 0, 0, false);
+
+      //request a worker for the new app
+      (function(){
+        scheduler.acquireWorker_p(appSpec, '/')
+      }).should.throw();
+    }),
+    it('should not surpass the MAX_REQUESTS directive with pending requests.', function(){
+      var WORKER_ID = "WORKER";
+      var mockWorker = 
+        addWorker(scheduler, WORKER_ID, 0, 0, MAX_REQUESTS, false);
+
+      //request a worker for the new app
+      (function(){
+        scheduler.acquireWorker_p(appSpec, '/')
+      }).should.throw();
+    }),
+    it('should not 503 non-/, non-ws traffic ever', function(done){
+      var WORKER_ID = "WORKER";
+      var mockWorker = 
+        addWorker(scheduler, WORKER_ID, MAX_REQUESTS*2, 0, 0, false);
+
+      appSpec.settings.scheduler = {simple: {maxRequests: 0}};
+
+      scheduler.acquireWorker_p(appSpec, 'SOMEURL')
+      .then(function(wh){
+        // ensure there's no error and that we got the right
+        // data back.
+        wh.should.equal(mockWorker.promise);
+      })
+      .then(done, done).done();
+    }),
+    it('should not assign traffic to a kill()ed worker before the process exits')
+  })
+})
diff --git a/test/squash-run-as-router.js b/test/squash-run-as-router.js
new file mode 100644
index 0000000..5235b99
--- /dev/null
+++ b/test/squash-run-as-router.js
@@ -0,0 +1,67 @@
+/*
+ * test/squash-run-as-router.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+var SquashRunAsRouter = require('../lib/router/squash-run-as-router.js');
+var Q = require('q');
+var should = require('should');
+
+/**
+ * Construct a router that returns the given string as the runAs setting.
+ **/
+function constructRouter(runas){
+  var router = new SquashRunAsRouter({getAppSpec_p : function(){ 
+    return Q({runAs: runas});
+  }});
+  return router;
+}
+
+describe('SquashRunAsRouter', function(){
+  describe('#getAppSpec_p', function(){
+    it('returns the first string in a non-special array', function(done){
+      constructRouter(['user1', 'user2', 'user3']).getAppSpec_p()
+      .then(function(appSpec){
+        appSpec.runAs.should.equal('user1');
+      })
+      .then(done, done);
+    }),
+    it('skips special users', function(done){
+      constructRouter([':HOME_USER:', 'user2', 'user3']).getAppSpec_p()
+      .then(function(appSpec){
+        appSpec.runAs.should.equal('user2');
+      })
+      .then(done, done);
+    }),
+    it('returns undefined if no users', function(done){
+      constructRouter([]).getAppSpec_p()
+      .then(function(appSpec){
+        should.not.exist(appSpec.runAs);
+      })
+      .then(done, done);
+    }),
+    it('returns undefined if only special users', function(done){
+      constructRouter([':HOME_USER:', ':SOMETHING_ELSE:']).getAppSpec_p()
+      .then(function(appSpec){
+        should.not.exist(appSpec.runAs);
+      })
+      .then(done, done);
+    }),
+    it('skips non-strings', function(done){
+      constructRouter([false, null, undefined, {a:1}, 14, 'user3']).getAppSpec_p()
+      .then(function(appSpec){
+        appSpec.runAs.should.equal('user3');
+      })
+      .then(done, done);
+    })
+  });
+});
+
diff --git a/tools/_setup-devenv-common.sh b/tools/_setup-devenv-common.sh
new file mode 100755
index 0000000..a5a0272
--- /dev/null
+++ b/tools/_setup-devenv-common.sh
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+
+set -e
+
+CMAKE=cmake
+if hash cmake28 2>/dev/null; then
+   CMAKE=cmake28
+fi
+
+cd "$(dirname $0)"
+
+# See if "shiny" user exists
+if id -u shiny >/dev/null 2>&1;
+then
+   echo User "shiny" already exists
+else
+   echo Creating user "shiny"
+   sudo useradd -r -m shiny
+fi
+
+sudo mkdir -p /var/log/shiny-server
+sudo mkdir -p /srv/shiny-server
+
+# Log dir must be writable by "shiny" user
+sudo chown shiny:shiny /var/log/shiny-server
+
+mkdir -p build
+(cd build && "$CMAKE" ../.. && make)
+
+(cd .. && bin/npm install)
diff --git a/tools/check-licenses.js b/tools/check-licenses.js
new file mode 100644
index 0000000..7079a33
--- /dev/null
+++ b/tools/check-licenses.js
@@ -0,0 +1,120 @@
+var fs = require("fs");
+var path = require("path");
+
+// Some package maintainers cannot be bothered to put license info
+// in their package.json, but have the info available elsewhere
+
+var KNOWN_LICENSES = {
+  "bash at 0.0.1":           "MIT",
+  "commander at 0.6.1":      "MIT",
+  "commander at 2.3.0":      "MIT",
+  "commander at 2.9.0":      "MIT",
+  "debug at 2.0.0":          "MIT",
+  "debug at 2.2.0":          "MIT",
+  "formatio at 1.1.1":       "BSD-3-Clause",
+  "growl at 1.8.1":          "MIT",
+  "growl at 1.9.2":          "MIT",
+  "jade at 0.26.3":          "MIT",
+  "keygrip at 1.0.1":        "MIT",
+  "ms at 0.6.2":             "MIT",
+  "ms at 0.7.1":             "MIT",
+  "regexp-quote at 0.0.0":   "MIT",
+  "samsam at 1.1.2":         "BSD-3-Clause",
+  "unixgroups at 0.2.0":     "MIT",
+};
+
+function readJSON(file) {
+  return JSON.parse(fs.readFileSync(file));
+}
+
+function getLicense(packageJson) {
+  var license = packageJson.license ||
+    (packageJson.licenses && packageJson.licenses[0]) ||
+    (packageJson.licenses && packageJson.licenses.type);
+  if (typeof(license) === "undefined") {
+    return KNOWN_LICENSES[packageJson.name + "@" + packageJson.version];
+  }
+  if (typeof(license) === "string") {
+    return license;
+  } else if (typeof(license) === "object" && license.type) {
+    return license.type;
+  } else {
+    throw new Error("Unknown license");
+  }
+}
+
+function getPackageJson(basedir) {
+  var result = [];
+  var children = fs.readdirSync(basedir);
+  children.forEach(function(child) {
+    child = basedir + "/" + child;
+    var lstat = fs.lstatSync(child);
+    if (lstat.isDirectory() && !lstat.isSymbolicLink()) {
+      result = result.concat(getPackageJson(child));
+    } else if (path.basename(child) === "package.json" &&
+        path.basename(path.dirname(path.dirname(child))) === "node_modules" &&
+        // rewire has some fake packages embedded
+        !/\/rewire\/testLib\//.test(child)) {
+      result.push(child);
+    }
+  });
+  return result;
+}
+
+function isApprovedLicense(license) {
+  return /^MIT|BSD|BSD-3-Clause|ISC|Apache[- ]2.0|WTFPL|Public Domain|MPL 2.0|zlib$/.test(license);
+}
+
+function getLicenses(path) {
+  return getPackageJson(path).map(function(package) {
+    try {
+      var packageInfo = readJSON(package);
+      var license = getLicense(packageInfo);
+      return {
+        name: packageInfo.name + "@" + packageInfo.version,
+        path: package,
+        license: license
+      };
+    } catch (err) {
+      throw new Error("Error reading " + package + ": " + err.message);
+    }
+  }).sort(function(a, b) {
+    a = a.name.toUpperCase();
+    b = b.name.toUpperCase();
+    return (a < b) ? -1 :
+      (b < a) ? 1 :
+      0;
+  });
+}
+
+function showAllLicenses(unapprovedOnly) {
+  getLicenses("node_modules").forEach(function(info) {
+    if (!unapprovedOnly || !isApprovedLicense(info.license)) {
+      console.log(
+        (info.license || "[NONE]") + "\t" +
+        info.name + "\t" +
+        info.path
+      );
+    }
+  });
+}
+
+function checkLicenses() {
+  var anyUnapproved = false;
+  getLicenses("node_modules").forEach(function(info) {
+    if (!isApprovedLicense(info.license) && !/^shiny-server-client@/.test(info.name)) {
+      anyUnapproved = true;
+      console.error(
+        (info.license || "[NONE]") + "\t" +
+        info.name + "\t" +
+        info.path
+      );      
+    }
+  });
+  if (anyUnapproved) {
+    console.error("ERROR: License check failed! See the above dependencies.");
+    process.exit(1);
+  }
+}
+
+checkLicenses();
diff --git a/tools/check-upstream.sh b/tools/check-upstream.sh
new file mode 100755
index 0000000..979c5eb
--- /dev/null
+++ b/tools/check-upstream.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+cd "`dirname "$0"`"
+
+grep -v '^\(#\|\s*$\)' ../upstream.txt | while read line
+do
+	echo "Upstream branch: $line"
+	git fetch -q $line
+	./is-merged.sh FETCH_HEAD HEAD
+done
diff --git a/tools/is-merged.sh b/tools/is-merged.sh
new file mode 100755
index 0000000..429eb81
--- /dev/null
+++ b/tools/is-merged.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+set -e
+
+if [ "$1" == "" ] || [ "$2" == "" ]; then
+  echo "Usage: is-merge.sh <source-ref> <target-ref>" 1>&2
+  exit 127
+fi
+
+BASE=`git merge-base $1 $2`
+REF=`git rev-parse $1`
+if [ "$BASE" != "$REF" ]; then
+  COUNT=`git log $BASE..$REF --pretty=oneline | wc -l`
+  echo "$2 is $COUNT commit(s) behind $1" 1>&2
+  exit 1
+fi
+
diff --git a/tools/makedocs.js b/tools/makedocs.js
new file mode 100644
index 0000000..8741ac9
--- /dev/null
+++ b/tools/makedocs.js
@@ -0,0 +1,138 @@
+#!/usr/bin/env node
+
+/*
+ * makedocs.js
+ *
+ * Copyright (C) 2009-13 by RStudio, Inc.
+ *
+ * This program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+/*
+ * This script is for generating config.html. It is designed to run under
+ * node-supervisor (https://github.com/isaacs/node-supervisor).
+ *
+ *   supervisor -n exit --extensions 'js|html' lib/makedocs.js
+ */
+
+var fs = require('fs');
+var path = require('path');
+var util = require('util');
+var htmlEscape = require('connect/lib/utils').escape;
+var Handlebars = require('handlebars');
+var _ = require('underscore');
+var map = require('../lib/core/map');
+var config = require('../lib/config/config');
+var schema = require('../lib/config/schema');
+
+function filterDesc(desc) {
+  if (!desc)
+    return desc;
+
+  var seen = {};
+  desc = desc.replace(/`(.*?)`/g, function(str, m) {
+    if (rulesByName[m] && !seen[m]) {
+      seen[m] = true;
+      return '<a class="code" href="#' + m + '">' + m + '</a>';
+    }
+    else
+      return '<code>' + m + '</code>';
+  });
+
+  desc = desc.replace(/\[(.*?)\]\((.*?)\)/g, function(str, m1, m2) {
+    return '<a href="' + htmlEscape(m2) + '">' + htmlEscape(m1) + '</a>';
+  });
+
+  return desc;
+
+  //'<code>$1</code>');
+}
+
+function scopelist(scopes) {
+  if (typeof scopes == 'string')
+    scopes = [scopes];
+  return _.map(scopes, function(scope) {
+    if (scope === '$')
+      return 'Top-level';
+    else
+      return '<code><a href="#' + scope + '">' + scope + '</a></code>';
+  }).join(', ');
+}
+Handlebars.registerHelper('scopelist', scopelist);
+Handlebars.registerHelper('yesno', function(x) {
+  return x ? 'yes' : 'no';
+});
+Handlebars.registerHelper('YesNo', function(x) {
+  return x ? 'Yes' : 'No';
+});
+
+
+var packageInfo =
+  JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json')));
+var version = packageInfo['version'];
+
+var rulesPath = path.join(__dirname, '../config/shiny-server-rules.config');
+var ruleConfig = config.parse(fs.readFileSync(rulesPath, 'utf-8'), rulesPath);
+
+var rules = _.map(ruleConfig.children, function(child) {
+  var name = child.name;
+  var desc = child.getOne('desc').args[0];
+  var params = _.map(child.getAll('param'), function(param) {
+    return new schema.ConfigSchemaParam(param);
+  });
+  var undocumented = !!child.getOne('undocumented');
+  var at = child.getOne('at').args;
+  var primaryLoc = _.last(at);
+  var otherLocs = _.clone(at);
+  otherLocs.pop();
+
+  return {
+    name: name,
+    version: packageInfo['version'],
+    desc: desc,
+    params: params,
+    at: at,
+    primaryLoc: primaryLoc,
+    otherLocs: otherLocs,
+    children: [],
+    inheritedChildren: [],
+    undocumented: undocumented
+  }
+});
+
+rules = _.filter(rules, function(rule) {
+  return !rule.undocumented;
+});
+
+var rulesByName = map.create();
+_.each(rules, function(rule) {
+  rulesByName[rule.name] = rule;
+});
+_.each(rules, function(rule) {
+  if (rule.primaryLoc != '$')
+    rulesByName[rule.primaryLoc].children.push(rule.name);
+  _.each(rule.otherLocs || [], function(parent) {
+    if (parent != '$')
+      rulesByName[parent].inheritedChildren.push(rule.name);
+  });
+  rule.desc = filterDesc(rule.desc);
+  _.each(rule.params, function(param) {
+    param.desc = filterDesc(param.desc);
+  });
+});
+
+var template = Handlebars.compile(fs.readFileSync(path.join(__dirname, '../templates/config.html'), 'utf-8'));
+fs.writeFileSync(
+  path.join(__dirname, '../config.html'),
+  template({
+    version: version,
+    directives: rules,
+    warning: "<!-- DO NOT EDIT BY HAND; automatically generated by makedocs.js -->"
+  }),
+  'utf-8'
+);
diff --git a/tools/memlog-view.R b/tools/memlog-view.R
new file mode 100644
index 0000000..1bc4e74
--- /dev/null
+++ b/tools/memlog-view.R
@@ -0,0 +1,22 @@
+library(ggplot2)
+library(reshape2)
+library(shiny)
+
+
+memlog <- file.choose()
+data <- reactiveFileReader(300, NULL, memlog, read.csv)
+
+runApp(list(ui = basicPage(
+    plotOutput("plot")
+  ),
+  server = function(input, output, session) {
+    output$plot <- renderPlot({
+      p <- ggplot(data = data(), aes(x = 1:nrow(data())))
+      p <- p + geom_line(aes(y = rss), color = 'red')
+      p <- p + geom_line(aes(y = heapTotal))
+      p <- p + geom_line(aes(y = heapUsed))
+      p <- p + geom_smooth(aes(y = rss), method = "loess")
+      print(p)
+    })
+  }
+), launch.browser = rstudio::viewer)
\ No newline at end of file
diff --git a/tools/preflight.sh b/tools/preflight.sh
new file mode 100755
index 0000000..65839fa
--- /dev/null
+++ b/tools/preflight.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+cd `dirname "$0"`
+cd ..
+
+echo Checking dependency licenses >&2
+bin/node tools/check-licenses.js
+
+echo Checking for unmerged changes from upstream >&2
+tools/check-upstream.sh
diff --git a/tools/setup-devenv-debian.sh b/tools/setup-devenv-debian.sh
new file mode 100755
index 0000000..1a2ae9c
--- /dev/null
+++ b/tools/setup-devenv-debian.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+set -e
+
+cd "$(dirname $0)"
+
+sudo apt-get install make gcc g++ git python libssl-dev cmake
+
+./_setup-devenv-common.sh
\ No newline at end of file
diff --git a/tools/setup-devenv-redhat.sh b/tools/setup-devenv-redhat.sh
new file mode 100755
index 0000000..164fa5d
--- /dev/null
+++ b/tools/setup-devenv-redhat.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+set -e
+
+cd "$(dirname $0)"
+
+sudo yum install make gcc gcc-c++ git python openssl-devel cmake28
+
+./_setup-devenv-common.sh
diff --git a/tools/test-config.sh b/tools/test-config.sh
new file mode 100755
index 0000000..1c4a495
--- /dev/null
+++ b/tools/test-config.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+set -e
+
+# Get the shiny-server directory
+ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )"
+
+if [ $# -eq 0 ]
+then
+  echo "No test config name supplied, using \"testapps\"" >&2
+  TEST="testapps"
+else
+  # The name of the config template in test/configs (e.g. "testapps" for testapps.config.in)
+  TEST="$1"
+  shift
+fi
+
+CONFIG_IN="$ROOT/test/configs/$TEST.config.in"
+mkdir -p /tmp/shiny-server-test
+CONFIG_OUT="/tmp/shiny-server-test/$TEST.config"
+(sed -e "s/\$USER/$USER/g" | sed -e "s/\$ROOT/$(echo $ROOT | sed -e 's/[\/&]/\\&/g')/g") < "$CONFIG_IN" > "$CONFIG_OUT"
+
+"$ROOT/bin/shiny-server" $CONFIG_OUT $@
diff --git a/upstream.txt b/upstream.txt
new file mode 100644
index 0000000..2ab8112
--- /dev/null
+++ b/upstream.txt
@@ -0,0 +1,6 @@
+# Each line should list a repo/branch pair (separated by space) that
+# must be merged into this branch for tools/check-upstream.sh to pass.
+
+# Example:
+# https://github.com/rstudio/shiny-server master
+
diff --git a/vagrant/build-servers/ubuntu12.04/Vagrantfile b/vagrant/build-servers/ubuntu12.04/Vagrantfile
new file mode 100644
index 0000000..5dd0130
--- /dev/null
+++ b/vagrant/build-servers/ubuntu12.04/Vagrantfile
@@ -0,0 +1,70 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+####################################
+#       Ubuntu 12.04 Build
+#
+# A build machine with all the
+# tooling needed to build SSOpen on 
+# Ubuntu 12.04.
+#
+# IP: 10.0.0.74
+#
+####################################
+
+
+# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
+VAGRANTFILE_API_VERSION = "2"
+
+Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
+  # All Vagrant configuration is done here. The most common configuration
+  # options are documented and commented below. For a complete reference,
+  # please see the online documentation at vagrantup.com.
+
+  # Every Vagrant virtual environment requires a box to build off of.
+  config.vm.box = "ubuntu-12.04"
+
+  # The url from where the 'config.vm.box' box will be fetched if it
+  # doesn't already exist on the user's system.
+  config.vm.box_url = "http://cloud-images.ubuntu.com/vagrant/precise/current/precise-server-cloudimg-amd64-vagrant-disk1.box"
+
+  config.vm.host_name = "sso-ubuntu1204-build"
+  
+  config.vm.provision "shell", path: "setup.sh"
+  config.vm.provision "shell", path: "build-sso.sh", privileged: false
+
+  # Create a private network, which allows host-only access to the machine
+  # using a specific IP.
+  config.vm.network :private_network, ip: "10.0.0.74"
+  
+  # Create a public network, which generally matched to bridged network.
+  # Bridged networks make the machine appear as another physical device on
+  # your network.
+  # config.vm.network :public_network
+
+  # If true, then any SSH connections made will enable agent forwarding.
+  # Default value: false
+  # config.ssh.forward_agent = true
+
+  # Share an additional folder to the guest VM. The first argument is
+  # the path on the host to the actual folder. The second argument is
+  # the path on the guest to mount the folder. And the optional third
+  # argument is a set of non-required options.
+  # config.vm.synced_folder "../data", "/vagrant_data"
+
+  # Provider-specific configuration so you can fine-tune various
+  # backing providers for Vagrant. These expose provider-specific options.
+  # Example for VirtualBox:
+  #
+  config.vm.provider :virtualbox do |vb|
+    # Don't boot with headless mode
+    # vb.gui = true
+  
+    # Use VBoxManage to customize the VM. For example to change memory:
+    vb.customize ["modifyvm", :id, "--memory", "1024"]
+  end
+  #
+  # View the documentation for the provider you're using for more
+  # information on available options.
+
+end
diff --git a/vagrant/build-servers/ubuntu12.04/build-sso.sh b/vagrant/build-servers/ubuntu12.04/build-sso.sh
new file mode 100644
index 0000000..733d960
--- /dev/null
+++ b/vagrant/build-servers/ubuntu12.04/build-sso.sh
@@ -0,0 +1,9 @@
+
+# Clone the repository from GitHub
+git clone https://github.com/rstudio/shiny-server.git
+cd shiny-server
+
+git remote add trestle https://github.com/trestletech/shiny-server.git
+
+./make-package.sh
+
diff --git a/vagrant/build-servers/ubuntu12.04/setup.sh b/vagrant/build-servers/ubuntu12.04/setup.sh
new file mode 100644
index 0000000..66ae945
--- /dev/null
+++ b/vagrant/build-servers/ubuntu12.04/setup.sh
@@ -0,0 +1,27 @@
+echo "deb http://cran.rstudio.com/bin/linux/ubuntu precise/" >> /etc/apt/sources.list
+
+apt-get update
+
+# --force-yes to handle the un-verified deb
+apt-get install r-base-dev r-base -y --force-yes
+
+apt-get install gdebi git gcc g++ -y
+
+# R is too old for CRAN's latest Rcpp
+wget http://cran.r-project.org/src/contrib/Archive/Rcpp/Rcpp_0.10.5.tar.gz -O Rcpp_0.10.5.tar.gz
+R CMD INSTALL Rcpp_0.10.5.tar.gz
+
+R -e "install.packages('shiny', repos='http://cran.rstudio.com/')"
+
+mkdir -p /srv/shiny-server
+
+cp -R /usr/local/lib/R/site-library/shiny/examples/* /srv/shiny-server/
+
+# Get and build cmake
+wget http://www.cmake.org/files/v2.8/cmake-2.8.11.2.tar.gz
+tar xzf cmake-2.8.11.2.tar.gz
+cd cmake-2.8.11.2
+./configure
+make
+make install
+
diff --git a/vagrant/centos6/Vagrantfile b/vagrant/centos6/Vagrantfile
new file mode 100644
index 0000000..36719ad
--- /dev/null
+++ b/vagrant/centos6/Vagrantfile
@@ -0,0 +1,74 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+####################################
+#         CentOS 6 Build
+#
+# Downloads and installs the latest
+# CentOS 6 build posted to S3 from 
+# Jenkins in a CentOS6 VM.
+#
+# IP: 10.0.0.61
+#
+####################################
+
+
+# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
+VAGRANTFILE_API_VERSION = "2"
+
+Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
+  # All Vagrant configuration is done here. The most common configuration
+  # options are documented and commented below. For a complete reference,
+  # please see the online documentation at vagrantup.com.
+
+  # Every Vagrant virtual environment requires a box to build off of.
+  config.vm.box = "centos-6.3-x86-64"
+
+  # The url from where the 'config.vm.box' box will be fetched if it
+  # doesn't already exist on the user's system.
+  config.vm.box_url = "https://s3.amazonaws.com/itmat-public/centos-6.3-chef-10.14.2.box"
+
+  config.vm.host_name = "sso-centos6-latest"
+  
+  config.vm.provision "shell", path: "setup.sh"
+
+  # Create a forwarded port mapping which allows access to a specific port
+  # within the machine from a port on the host machine. In the example below,
+  # accessing "localhost:8080" will access port 80 on the guest machine.
+  # config.vm.network :forwarded_port, guest: 80, host: 8080
+
+  # Create a private network, which allows host-only access to the machine
+  # using a specific IP.
+  config.vm.network :private_network, ip: "10.0.0.61"
+  
+  # Create a public network, which generally matched to bridged network.
+  # Bridged networks make the machine appear as another physical device on
+  # your network.
+  # config.vm.network :public_network
+
+  # If true, then any SSH connections made will enable agent forwarding.
+  # Default value: false
+  # config.ssh.forward_agent = true
+
+  # Share an additional folder to the guest VM. The first argument is
+  # the path on the host to the actual folder. The second argument is
+  # the path on the guest to mount the folder. And the optional third
+  # argument is a set of non-required options.
+  # config.vm.synced_folder "../data", "/vagrant_data"
+
+  # Provider-specific configuration so you can fine-tune various
+  # backing providers for Vagrant. These expose provider-specific options.
+  # Example for VirtualBox:
+  #
+  config.vm.provider :virtualbox do |vb|
+    # Don't boot with headless mode
+    # vb.gui = true
+  
+    # Use VBoxManage to customize the VM. For example to change memory:
+    vb.customize ["modifyvm", :id, "--memory", "1024"]
+  end
+  #
+  # View the documentation for the provider you're using for more
+  # information on available options.
+
+end
diff --git a/vagrant/centos6/setup.sh b/vagrant/centos6/setup.sh
new file mode 100644
index 0000000..ba7ea97
--- /dev/null
+++ b/vagrant/centos6/setup.sh
@@ -0,0 +1,25 @@
+# This box didn't include root in 'sudoers', so any command with 'sudo' would fail 
+# (since it runs as root). Add root to approved sudoers list so it's not a problem.
+echo "root            ALL=(ALL)               NOPASSWD: ALL" >> /etc/sudoers
+
+# IPTables is enabled on this box by default. Stop.
+sudo /etc/init.d/iptables stop
+sudo chkconfig iptables off
+
+# Enable EPEL
+rpm -Uvh http://download.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
+
+# Install R
+yum install R -y
+
+wget https://s3.amazonaws.com/rstudio-shiny-server-os-build/centos-6.3/x86_64/VERSION -O "version.txt"
+VERSION=`cat version.txt`
+
+# Install the latest SSP build
+wget "https://s3.amazonaws.com/rstudio-shiny-server-os-build/centos-6.3/x86_64/shiny-server-$VERSION-x86_64.rpm" -O ss-latest.rpm
+yum install --nogpgcheck ss-latest.rpm -y
+
+sudo su - \
+    -c "R -e \"install.packages('shiny', repos='http://cran.rstudio.com/')\""
+
+sudo cp -R /usr/lib64/R/library/shiny/examples/* /srv/shiny-server/
diff --git a/vagrant/centos7/Vagrantfile b/vagrant/centos7/Vagrantfile
new file mode 100644
index 0000000..f57fddb
--- /dev/null
+++ b/vagrant/centos7/Vagrantfile
@@ -0,0 +1,82 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+####################################
+#         CentOS 7 Build
+#
+# Downloads and installs the latest
+# CentOS 6 build posted to S3 from 
+# Jenkins in a CentOS 7 VM.
+#
+# IP: 10.0.0.68
+# Admin dash username: admin
+# Admin dash password: password
+#
+# NOTE that this image uses a custom box. It's based off of the
+# minimal CentOS 7 box on vagrantbox.es, but we bumped to guest
+# additions to match 4.3.20. On my Ubuntu 14.04 machine, I needed
+# 4.3.20 Virtual box on the host machine and this custom image
+# on the guest machine to get shared folders to work.
+#
+####################################
+
+
+# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
+VAGRANTFILE_API_VERSION = "2"
+
+Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
+  # All Vagrant configuration is done here. The most common configuration
+  # options are documented and commented below. For a complete reference,
+  # please see the online documentation at vagrantup.com.
+
+  # Every Vagrant virtual environment requires a box to build off of.
+  config.vm.box = "centos7-20"
+
+  # The url from where the 'config.vm.box' box will be fetched if it
+  # doesn't already exist on the user's system.
+  config.vm.box_url = "https://s3-us-west-2.amazonaws.com/rstudio-vagrant-boxes/boxes/centos7.box"
+
+  config.vm.host_name = "sso-centos7-latest"
+  
+  config.vm.provision "shell", path: "setup.sh"
+
+  # Create a forwarded port mapping which allows access to a specific port
+  # within the machine from a port on the host machine. In the example below,
+  # accessing "localhost:8080" will access port 80 on the guest machine.
+  # config.vm.network :forwarded_port, guest: 80, host: 8080
+
+  # Create a private network, which allows host-only access to the machine
+  # using a specific IP.
+  config.vm.network :private_network, ip: "10.0.0.62"
+  
+  # Create a public network, which generally matched to bridged network.
+  # Bridged networks make the machine appear as another physical device on
+  # your network.
+  # config.vm.network :public_network
+
+  # If true, then any SSH connections made will enable agent forwarding.
+  # Default value: false
+  # config.ssh.forward_agent = true
+
+  # Share an additional folder to the guest VM. The first argument is
+  # the path on the host to the actual folder. The second argument is
+  # the path on the guest to mount the folder. And the optional third
+  # argument is a set of non-required options.
+  # config.vm.synced_folder "../data", "/vagrant_data"
+
+  # Provider-specific configuration so you can fine-tune various
+  # backing providers for Vagrant. These expose provider-specific options.
+  # Example for VirtualBox:
+  #
+  config.vm.provider :virtualbox do |vb|
+    # Don't boot with headless mode
+    # vb.gui = true
+  
+    # Use VBoxManage to customize the VM. For example to change memory:
+    vb.customize ["modifyvm", :id, "--memory", "1024"]
+  end
+  #
+  # View the documentation for the provider you're using for more
+  # information on available options.
+
+end
diff --git a/vagrant/centos7/setup.sh b/vagrant/centos7/setup.sh
new file mode 100644
index 0000000..40f123c
--- /dev/null
+++ b/vagrant/centos7/setup.sh
@@ -0,0 +1,26 @@
+# Enable EPEL
+rpm -Uvh http://mirror.pnl.gov/epel/7/x86_64/e/epel-release-7-2.noarch.rpm
+
+# On this minimal install, we need wget
+yum install wget -y
+
+# Install R
+yum install R -y
+
+wget https://s3.amazonaws.com/rstudio-shiny-server-os-build/centos-6.3/x86_64/VERSION -O "version.txt"
+VERSION=`cat version.txt`
+
+# Install the latest SS build
+wget "https://s3.amazonaws.com/rstudio-shiny-server-os-build/centos-6.3/x86_64/shiny-server-$VERSION-rh6-x86_64.rpm" -O ss-latest.rpm
+yum install --nogpgcheck ss-latest.rpm -y
+
+echo "password" | /opt/shiny-server/bin/sspasswd /etc/shiny-server/passwd "admin"
+
+sudo su - \
+    -c "R -e \"install.packages('shiny', repos='http://cran.rstudio.com/')\""
+
+sudo cp -R /usr/lib64/R/library/shiny/examples/* /srv/shiny-server/
+
+systemctl disable firewalld 
+systemctl stop firewalld
+sed -i 's/enforcing/disabled/g' /etc/selinux/config
diff --git a/vagrant/nfs/README.md b/vagrant/nfs/README.md
new file mode 100644
index 0000000..c128370
--- /dev/null
+++ b/vagrant/nfs/README.md
@@ -0,0 +1,15 @@
+
+
+Spin up the NFS server first:
+
+```
+vagrant up nfs
+```
+
+Then you can bring up the Shiny Server machine:
+
+```
+vagrant up sso
+```
+
+At this point, you should have a Shiny Server instance which mounts the directory `/home/shiny` over NFS with `root_squash` which can be used to test user_dirs over NFS with root_squash.
diff --git a/vagrant/nfs/Vagrantfile b/vagrant/nfs/Vagrantfile
new file mode 100644
index 0000000..e31f9da
--- /dev/null
+++ b/vagrant/nfs/Vagrantfile
@@ -0,0 +1,29 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+Vagrant.configure(2) do |config|
+  # define primary development box
+  config.vm.define "sso", primary: true do |p|
+    p.vm.box = "ubuntu/trusty64"
+    p.vm.network "private_network", ip: "192.168.42.101"
+    p.vm.network "forwarded_port", guest: 3838, host: 3838, auto_correct: true
+    p.vm.provision :shell, path: "provision-sso.sh"
+  end
+
+  config.vm.define "nfs", autostart: false do |b|
+    b.vm.box = "ubuntu/trusty64"
+    b.vm.network "private_network", ip: "192.168.42.102"
+    b.vm.provision :shell, path: "provision-nfs.sh"
+  end
+
+  # give machine liberal cpu & core resources for virtualbox (adjust to taste)
+  config.vm.provider "virtualbox" do |vb|
+    vb.memory = "2048"
+    vb.cpus = "2"
+  end
+
+  config.vm.synced_folder "../../", "/shiny-server"
+  config.vm.synced_folder "../../", "/home/vagrant/shiny-server"
+
+  config.vm.provision :shell, path: "bootstrap-debian.sh"
+end
diff --git a/vagrant/nfs/bootstrap-debian.sh b/vagrant/nfs/bootstrap-debian.sh
new file mode 100755
index 0000000..12d8614
--- /dev/null
+++ b/vagrant/nfs/bootstrap-debian.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+# add repo for R 
+apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E084DAB9
+echo "deb https://cran.rstudio.com/bin/linux/ubuntu trusty/" >> /etc/apt/sources.list
+
+# bring apt database up to date with R packages
+apt-get update
+
+# install R
+apt-get install -y --force-yes r-base r-base-dev
+
+# install minimal packages needed to run bootstrap scripts
+apt-get install -y unzip
+apt-get install -y git
+apt-get install -y g++
+apt-get install -y wget
+
+# install packages needed to build and run devtools
+apt-get install -y libssh2-1-dev
+apt-get install -y curl 
+apt-get install -y libcurl4-openssl-dev
+
+
diff --git a/vagrant/nfs/provision-nfs.sh b/vagrant/nfs/provision-nfs.sh
new file mode 100755
index 0000000..0419c79
--- /dev/null
+++ b/vagrant/nfs/provision-nfs.sh
@@ -0,0 +1,17 @@
+#!/usr/bin/env bash
+
+# Create Shiny user
+useradd -r -m shiny
+mkdir /home/shiny/ShinyApps/
+chown -R shiny:shiny /home/shiny
+chmod 755 -R /home/shiny
+
+# install NFS server and export user home directories
+apt-get install -y nfs-kernel-server
+echo "/home/shiny   *(rw,sync,root_squash)" >> /etc/exports
+service nfs-kernel-server start
+
+# Install apps in Shiny user's personal dir
+git clone https://github.com/rstudio/shiny.git
+cp -R shiny/inst/examples/* /home/shiny/ShinyApps/
+
diff --git a/vagrant/nfs/provision-sso.sh b/vagrant/nfs/provision-sso.sh
new file mode 100755
index 0000000..fc7e325
--- /dev/null
+++ b/vagrant/nfs/provision-sso.sh
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+
+# install packages needed for development environment
+apt-get install -y vim
+
+# connect to NFS server already running on the primary machine
+apt-get install -y nfs-common
+mkdir -p /home/shiny
+echo "192.168.42.102:/home/shiny /home/shiny/ nfs rsize=8192,wsize=8192,timeo=14,intr" >> /etc/fstab
+mount -a 
+
+wget https://s3.amazonaws.com/rstudio-shiny-server-os-build/ubuntu-12.04/x86_64/VERSION -O "version.txt"
+VERSION=`cat version.txt`                                                          
+                                                                                   
+# Install the latest SSO build                                                     
+wget "https://s3.amazonaws.com/rstudio-shiny-server-os-build/ubuntu-12.04/x86_64/shiny-server-$VERSION-amd64.deb" -O ss-latest.deb
+                                                                                   
+apt-get install gdebi -y                                                           
+gdebi -n ss-latest.deb                                                             
+                                                                                   
+# R is too old for CRAN's latest Rcpp                                              
+wget http://cran.r-project.org/src/contrib/Archive/Rcpp/Rcpp_0.10.5.tar.gz -O Rcpp_0.10.5.tar.gz
+R CMD INSTALL Rcpp_0.10.5.tar.gz                                                   
+                                                                                   
+R -e "install.packages('shiny', repos='http://cran.rstudio.com/')"                 
+                                                                                   
+cp -R /usr/local/lib/R/site-library/shiny/examples/* /srv/shiny-server/
+
+cp /shiny-server/vagrant/nfs/shiny-server.conf /etc/shiny-server/shiny-server.conf
+restart shiny-server
diff --git a/vagrant/nfs/shiny-server.conf b/vagrant/nfs/shiny-server.conf
new file mode 100644
index 0000000..3883a19
--- /dev/null
+++ b/vagrant/nfs/shiny-server.conf
@@ -0,0 +1,20 @@
+# Instruct Shiny Server to run applications as the user "shiny"
+run_as shiny;
+
+log_as_user true;
+
+# Define a server that listens on port 3838
+server {
+  listen 3838;
+
+  # Define a location at the base URL
+  location / {
+
+    # Host the directory of Shiny Apps stored in this directory
+    user_apps;
+
+    # When a user visits the base URL rather than a particular application,
+    # an index of the applications available in this directory will be shown.
+    directory_index on;
+  }
+}
diff --git a/vagrant/ubuntu12.04/Vagrantfile b/vagrant/ubuntu12.04/Vagrantfile
new file mode 100644
index 0000000..348feda
--- /dev/null
+++ b/vagrant/ubuntu12.04/Vagrantfile
@@ -0,0 +1,69 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+####################################
+#       Ubutntu 13.04 Build
+#
+# Downloads and installs the latest
+# Ubuntu 13.04 build posted to S3 
+# from Jenkins in a Ubuntu13.04 VM.
+#
+# IP: 10.0.0.70
+#
+####################################
+
+
+# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
+VAGRANTFILE_API_VERSION = "2"
+
+Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
+  # All Vagrant configuration is done here. The most common configuration
+  # options are documented and commented below. For a complete reference,
+  # please see the online documentation at vagrantup.com.
+
+  # Every Vagrant virtual environment requires a box to build off of.
+  config.vm.box = "ubuntu-12.04"
+
+  # The url from where the 'config.vm.box' box will be fetched if it
+  # doesn't already exist on the user's system.
+  config.vm.box_url = "http://cloud-images.ubuntu.com/vagrant/precise/current/precise-server-cloudimg-amd64-vagrant-disk1.box"
+
+  config.vm.host_name = "sso-ubuntu1204-latest"
+  
+  config.vm.provision "shell", path: "setup.sh"
+
+  # Create a private network, which allows host-only access to the machine
+  # using a specific IP.
+  config.vm.network :private_network, ip: "10.0.0.71"
+  
+  # Create a public network, which generally matched to bridged network.
+  # Bridged networks make the machine appear as another physical device on
+  # your network.
+  # config.vm.network :public_network
+
+  # If true, then any SSH connections made will enable agent forwarding.
+  # Default value: false
+  # config.ssh.forward_agent = true
+
+  # Share an additional folder to the guest VM. The first argument is
+  # the path on the host to the actual folder. The second argument is
+  # the path on the guest to mount the folder. And the optional third
+  # argument is a set of non-required options.
+  # config.vm.synced_folder "../data", "/vagrant_data"
+
+  # Provider-specific configuration so you can fine-tune various
+  # backing providers for Vagrant. These expose provider-specific options.
+  # Example for VirtualBox:
+  #
+  config.vm.provider :virtualbox do |vb|
+    # Don't boot with headless mode
+    # vb.gui = true
+  
+    # Use VBoxManage to customize the VM. For example to change memory:
+    vb.customize ["modifyvm", :id, "--memory", "1024"]
+  end
+  #
+  # View the documentation for the provider you're using for more
+  # information on available options.
+
+end
diff --git a/vagrant/ubuntu12.04/setup.sh b/vagrant/ubuntu12.04/setup.sh
new file mode 100644
index 0000000..6334822
--- /dev/null
+++ b/vagrant/ubuntu12.04/setup.sh
@@ -0,0 +1,23 @@
+echo "deb http://cran.rstudio.com/bin/linux/ubuntu precise/" >> /etc/apt/sources.list
+
+apt-get update
+
+# --force-yes to handle the un-verified deb
+apt-get install r-base-dev r-base -y --force-yes
+
+wget https://s3.amazonaws.com/rstudio-shiny-server-os-build/ubuntu-12.04/x86_64/VERSION -O "version.txt"
+VERSION=`cat version.txt`
+
+# Install the latest SSO build
+wget "https://s3.amazonaws.com/rstudio-shiny-server-os-build/ubuntu-12.04/x86_64/shiny-server-$VERSION-amd64.deb" -O ss-latest.deb
+
+apt-get install gdebi -y
+gdebi -n ss-latest.deb
+
+# R is too old for CRAN's latest Rcpp
+wget http://cran.r-project.org/src/contrib/Archive/Rcpp/Rcpp_0.10.5.tar.gz -O Rcpp_0.10.5.tar.gz
+R CMD INSTALL Rcpp_0.10.5.tar.gz
+
+R -e "install.packages('shiny', repos='http://cran.rstudio.com/')"
+
+cp -R /usr/local/lib/R/site-library/shiny/examples/* /srv/shiny-server/
diff --git a/vagrant/ubuntu14.04/Vagrantfile b/vagrant/ubuntu14.04/Vagrantfile
new file mode 100644
index 0000000..96fe4d8
--- /dev/null
+++ b/vagrant/ubuntu14.04/Vagrantfile
@@ -0,0 +1,70 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+####################################
+#       Ubutntu 13.04 Build
+#
+# Downloads and installs the latest
+# Ubuntu 13.04 build posted to S3 
+# from Jenkins in a Ubuntu13.04 VM.
+#
+# IP: 10.0.0.70
+#
+####################################
+
+
+# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
+VAGRANTFILE_API_VERSION = "2"
+
+Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
+  # All Vagrant configuration is done here. The most common configuration
+  # options are documented and commented below. For a complete reference,
+  # please see the online documentation at vagrantup.com.
+
+  # Every Vagrant virtual environment requires a box to build off of.
+  config.vm.box = "ubuntu-14.04"
+
+  # The url from where the 'config.vm.box' box will be fetched if it
+  # doesn't already exist on the user's system.
+  config.vm.box_url = "https://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box"
+
+  config.vm.host_name = "sso-ubuntu1404-latest"
+  
+  config.vm.provision "shell", path: "setup.sh"
+
+  # Create a private network, which allows host-only access to the machine
+  # using a specific IP.
+  config.vm.network :private_network, ip: "10.0.0.72"
+  
+  # Create a public network, which generally matched to bridged network.
+  # Bridged networks make the machine appear as another physical device on
+  # your network.
+  # config.vm.network :public_network
+
+  # If true, then any SSH connections made will enable agent forwarding.
+  # Default value: false
+  # config.ssh.forward_agent = true
+
+  # Share an additional folder to the guest VM. The first argument is
+  # the path on the host to the actual folder. The second argument is
+  # the path on the guest to mount the folder. And the optional third
+  # argument is a set of non-required options.
+  # config.vm.synced_folder "../data", "/vagrant_data"
+  config.vm.synced_folder "../../", "/shiny-server"
+
+  # Provider-specific configuration so you can fine-tune various
+  # backing providers for Vagrant. These expose provider-specific options.
+  # Example for VirtualBox:
+  #
+  config.vm.provider :virtualbox do |vb|
+    # Don't boot with headless mode
+    # vb.gui = true
+  
+    # Use VBoxManage to customize the VM. For example to change memory:
+    vb.customize ["modifyvm", :id, "--memory", "1024"]
+  end
+  #
+  # View the documentation for the provider you're using for more
+  # information on available options.
+
+end
diff --git a/vagrant/ubuntu14.04/setup.sh b/vagrant/ubuntu14.04/setup.sh
new file mode 100644
index 0000000..405116a
--- /dev/null
+++ b/vagrant/ubuntu14.04/setup.sh
@@ -0,0 +1,23 @@
+echo "deb http://cran.rstudio.com/bin/linux/ubuntu trusty/" >> /etc/apt/sources.list
+
+apt-get update
+
+# --force-yes to handle the un-verified deb
+apt-get install r-base-dev r-base -y --force-yes
+
+wget https://s3.amazonaws.com/rstudio-shiny-server-os-build/ubuntu-12.04/x86_64/VERSION -O "version.txt"
+VERSION=`cat version.txt`
+
+# Install the latest SSO build
+wget "https://s3.amazonaws.com/rstudio-shiny-server-os-build/ubuntu-12.04/x86_64/shiny-server-$VERSION-amd64.deb" -O ss-latest.deb
+
+apt-get install gdebi -y
+gdebi -n ss-latest.deb
+
+# R is too old for CRAN's latest Rcpp
+wget http://cran.r-project.org/src/contrib/Archive/Rcpp/Rcpp_0.10.5.tar.gz -O Rcpp_0.10.5.tar.gz
+R CMD INSTALL Rcpp_0.10.5.tar.gz
+
+R -e "install.packages('shiny', repos='http://cran.rstudio.com/')"
+
+cp -R /usr/local/lib/R/site-library/shiny/examples/* /srv/shiny-server/
diff --git a/vagrant/ubuntu15.04/Vagrantfile b/vagrant/ubuntu15.04/Vagrantfile
new file mode 100644
index 0000000..4cd3d09
--- /dev/null
+++ b/vagrant/ubuntu15.04/Vagrantfile
@@ -0,0 +1,66 @@
+# -*- mode: ruby -*-
+# vi: set ft=ruby :
+
+####################################
+#       Ubutntu 15.04 Build
+#
+# Downloads and installs the latest
+# Ubuntu 12.04 build posted to S3 
+# from Jenkins in a Ubuntu15.04 VM.
+#
+# IP: 10.0.0.88
+#
+####################################
+
+
+# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
+VAGRANTFILE_API_VERSION = "2"
+
+Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
+  # All Vagrant configuration is done here. The most common configuration
+  # options are documented and commented below. For a complete reference,
+  # please see the online documentation at vagrantup.com.
+
+  # Every Vagrant virtual environment requires a box to build off of.
+  config.vm.box = "ubuntu/vivid64"
+
+  # jcheng 2015-05-04: Hostname config appears not to work
+  #config.vm.host_name = "sso-ubuntu1504-latest"
+  
+  config.vm.provision "shell", path: "setup.sh"
+
+  # Create a private network, which allows host-only access to the machine
+  # using a specific IP.
+  config.vm.network :private_network, ip: "10.0.0.88"
+  
+  # Create a public network, which generally matched to bridged network.
+  # Bridged networks make the machine appear as another physical device on
+  # your network.
+  # config.vm.network :public_network
+
+  # If true, then any SSH connections made will enable agent forwarding.
+  # Default value: false
+  # config.ssh.forward_agent = true
+
+  # Share an additional folder to the guest VM. The first argument is
+  # the path on the host to the actual folder. The second argument is
+  # the path on the guest to mount the folder. And the optional third
+  # argument is a set of non-required options.
+  # config.vm.synced_folder "../data", "/vagrant_data"
+
+  # Provider-specific configuration so you can fine-tune various
+  # backing providers for Vagrant. These expose provider-specific options.
+  # Example for VirtualBox:
+  #
+  config.vm.provider :virtualbox do |vb|
+    # Don't boot with headless mode
+    # vb.gui = true
+  
+    # Use VBoxManage to customize the VM. For example to change memory:
+    vb.customize ["modifyvm", :id, "--memory", "2048"]
+  end
+  #
+  # View the documentation for the provider you're using for more
+  # information on available options.
+
+end
diff --git a/vagrant/ubuntu15.04/setup.sh b/vagrant/ubuntu15.04/setup.sh
new file mode 100644
index 0000000..bcf718c
--- /dev/null
+++ b/vagrant/ubuntu15.04/setup.sh
@@ -0,0 +1,19 @@
+echo "deb http://cran.rstudio.com/bin/linux/ubuntu vivid/" >> /etc/apt/sources.list
+
+apt-get update
+
+# --force-yes to handle the un-verified deb
+apt-get install r-base-dev r-base -y --force-yes
+
+wget https://s3.amazonaws.com/rstudio-shiny-server-os-build/ubuntu-12.04/x86_64/VERSION -O "version.txt"
+VERSION=`cat version.txt`
+
+# Install the latest SSO build
+wget "https://s3.amazonaws.com/rstudio-shiny-server-os-build/ubuntu-12.04/x86_64/shiny-server-$VERSION-amd64.deb" -O ss-latest.deb
+
+apt-get install gdebi -y
+gdebi -n ss-latest.deb
+
+R -e "install.packages('shiny', repos='http://cran.rstudio.com/')"
+
+cp -R /usr/local/lib/R/site-library/shiny/examples/* /srv/shiny-server/

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-med/shiny-server.git



More information about the debian-med-commit mailing list