diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 00000000..0c8caa58 --- /dev/null +++ b/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "share/jupyter/components" +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6b7ca594..24651310 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,8 @@ node_modules *.py[co] *~ .DS_Store +build +dist +share/jupyter/components +*.egg-info +MANIFEST diff --git a/.travis.yml b/.travis.yml index fde5215f..44eb5431 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,10 @@ python: before_install: # workaround for https://github.com/travis-ci/travis-cookbooks/issues/155 - sudo rm -rf /dev/shm && sudo ln -s /run/shm /dev/shm - - time npm install + - time npm install -g bower . - time sudo apt-get install libzmq3-dev - - time pip install -f https://nipy.bic.berkeley.edu/wheelhouse/travis -r requirements.txt -r dev-requirements.txt + - time pip install -f https://nipy.bic.berkeley.edu/wheelhouse/travis -r dev-requirements.txt +install: + - time pip install . script: - py.test jupyterhub diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 00000000..5701ba87 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,24 @@ +include README.md +include COPYING.md +include setupegg.py + +graft jupyterhub +graft scripts + +# Load configproxy js +graft lib + +# Documentation +graft docs + +# docs subdirs we want to skip +prune docs/build +prune docs/gh-pages +prune docs/dist + +# Patterns to exclude from any directory +global-exclude *~ +global-exclude *.pyc +global-exclude *.pyo +global-exclude .git +global-exclude .ipynb_checkpoints diff --git a/README.md b/README.md index 46a4ffc3..b6016d58 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,15 @@ Basic principals: - Hub configures proxy to forward url prefixes to single-user servers ## dependencies - - npm install - pip install -r requirements.txt + + # get the dependencies of the nodejs proxy (-g for global install) + npm install [-g] + # install the Python pargs (-e for editable/development install) + pip install [-e] . ## to use - $> python -m jupyterhub + $> jupyterhub -visit `http://localhost:8000`, and login (any username, password=`password`). +visit `http://localhost:8000`, and login with your unix credentials. diff --git a/bower.json b/bower.json new file mode 100644 index 00000000..2bf0faec --- /dev/null +++ b/bower.json @@ -0,0 +1,9 @@ +{ + "name": "ipython-deps", + "version": "0.0.1", + "dependencies": { + "bootstrap": "components/bootstrap#~3.1", + "font-awesome": "components/font-awesome#~4.1", + "jquery": "components/jquery#~2.0" + } +} diff --git a/jupyterhub/__init__.py b/jupyterhub/__init__.py index e69de29b..0eea61cc 100644 --- a/jupyterhub/__init__.py +++ b/jupyterhub/__init__.py @@ -0,0 +1 @@ +from .version import * diff --git a/jupyterhub/_data.py b/jupyterhub/_data.py new file mode 100644 index 00000000..c205e792 --- /dev/null +++ b/jupyterhub/_data.py @@ -0,0 +1,17 @@ +"""Get the data files for this package.""" + +def get_data_files(): + """Walk up until we find share/jupyter""" + import os + path = os.path.abspath(os.path.dirname(__file__)) + while path != '/': + share_jupyter = os.path.join(path, 'share', 'jupyter') + if os.path.exists(share_jupyter): + return share_jupyter + path, _ = os.path.split(path) + return '' + + +# Package managers can just override this with the appropriate constant +DATA_FILES_PATH = get_data_files() + diff --git a/jupyterhub/app.py b/jupyterhub/app.py index 39436537..dc749be7 100644 --- a/jupyterhub/app.py +++ b/jupyterhub/app.py @@ -1,6 +1,10 @@ #!/usr/bin/env python """The multi-user notebook application""" +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + + import logging import os from subprocess import Popen @@ -29,10 +33,15 @@ from .handlers import ( ) from . import db +from ._data import DATA_FILES_PATH from .utils import url_path_join class JupyterHubApp(Application): """An Application for starting a Multi-User Notebook server.""" + data_files_path = Unicode(DATA_FILES_PATH, config=True, + help="The location of jupyter data files (e.g. /usr/local/share/jupyter)" + ) + ip = Unicode('localhost', config=True, help="The public facing ip of the proxy" ) @@ -42,6 +51,13 @@ class JupyterHubApp(Application): base_url = Unicode('/', config=True, help="The base URL of the entire application" ) + + proxy_cmd = Unicode('configurable-http-proxy', config=True, + help="""The command to start the http proxy. + + Only override if configurable-http-proxy is not on your PATH + """ + ) proxy_auth_token = Unicode(config=True, help="The Proxy Auth token" ) @@ -206,7 +222,7 @@ class JupyterHubApp(Application): """Actually start the configurable-http-proxy""" env = os.environ.copy() env['CONFIGPROXY_AUTH_TOKEN'] = self.proxy.auth_token - self.proxy = Popen(["node", os.path.join(here, 'js', 'main.js'), + self.proxy = Popen([self.proxy_cmd, '--port', str(self.proxy.public_server.port), '--api-port', str(self.proxy.api_server.port), '--upstream-port', str(self.hub.server.port), @@ -217,7 +233,7 @@ class JupyterHubApp(Application): base_url = self.base_url settings = dict( config=self.config, - log=self.log, + # log=self.log, db=self.db, hub=self.hub, authenticator=import_item(self.authenticator)(config=self.config), diff --git a/jupyterhub/auth.py b/jupyterhub/auth.py index c6d4efdf..1ba16fdb 100644 --- a/jupyterhub/auth.py +++ b/jupyterhub/auth.py @@ -1,5 +1,8 @@ """Simple PAM authenticator""" +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + from tornado import gen import simplepam diff --git a/jupyterhub/singleuserapp.py b/jupyterhub/singleuserapp.py index c820a68e..6c3ae067 100644 --- a/jupyterhub/singleuserapp.py +++ b/jupyterhub/singleuserapp.py @@ -1,6 +1,9 @@ #!/usr/bin/env python """Extend regular notebook server to be aware of multiuser things.""" +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + import os import requests diff --git a/jupyterhub/tests/test_auth.py b/jupyterhub/tests/test_auth.py index 67bd656b..35c905d7 100644 --- a/jupyterhub/tests/test_auth.py +++ b/jupyterhub/tests/test_auth.py @@ -1,5 +1,8 @@ """Tests for PAM authentication""" +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + import mock import simplepam from tornado.testing import AsyncTestCase, gen_test diff --git a/jupyterhub/utils.py b/jupyterhub/utils.py index a2a4d549..a45427a9 100644 --- a/jupyterhub/utils.py +++ b/jupyterhub/utils.py @@ -5,6 +5,7 @@ import socket import time +from subprocess import check_call, CalledProcessError, STDOUT, PIPE from IPython.html.utils import url_path_join @@ -27,4 +28,3 @@ def wait_for_server(ip, port, timeout=10): time.sleep(0.1) else: break - diff --git a/jupyterhub/version.py b/jupyterhub/version.py new file mode 100644 index 00000000..b2aecccf --- /dev/null +++ b/jupyterhub/version.py @@ -0,0 +1,15 @@ +"""jupyterhub version info""" + +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + +version_info = ( + 0, + 0, + 1, + 'dev', # comment-out this line for a release +) +__version__ = '.'.join(map(str, version_info[:3])) + +if len(version_info) > 3: + __version__ = '%s-%s' % (__version__, version_info[3]) diff --git a/jupyterhub/js/configproxy.js b/lib/configproxy.js similarity index 100% rename from jupyterhub/js/configproxy.js rename to lib/configproxy.js diff --git a/jupyterhub/js/main.js b/lib/main.js similarity index 100% rename from jupyterhub/js/main.js rename to lib/main.js diff --git a/package.json b/package.json index 1341b169..7f817437 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,17 @@ "author": "IPython Developers", "repository": { "type": "git", - "url": "https://github.com/minrk/multiuser" + "url": "https://github.com/jupyter/jupyterhub" }, "dependencies": { "http-proxy": "~1.1", "commander": "~2.2" + }, + "files": [ + "lib/configproxy.js", + "lib/main.js" + ], + "bin": { + "configurable-http-proxy": "lib/main.js" } } \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 905b1b0a..256deb7c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,6 @@ ipython[notebook] +tornado>=3.2 +jinja2 simplepam sqlalchemy requests diff --git a/scripts/jupyterhub b/scripts/jupyterhub new file mode 100644 index 00000000..283bbe40 --- /dev/null +++ b/scripts/jupyterhub @@ -0,0 +1,4 @@ +#!/usr/bin/env python + +from jupyterhub.app import main +main() diff --git a/scripts/jupyterhub-singleuser b/scripts/jupyterhub-singleuser new file mode 100644 index 00000000..48c70d10 --- /dev/null +++ b/scripts/jupyterhub-singleuser @@ -0,0 +1,4 @@ +#!/usr/bin/env python + +from jupyterhub.singleuserapp import main +main() diff --git a/setup.py b/setup.py new file mode 100755 index 00000000..2eb8d879 --- /dev/null +++ b/setup.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python +# coding: utf-8 + +# Copyright (c) Juptyer Development Team. +# Distributed under the terms of the Modified BSD License. + +#----------------------------------------------------------------------------- +# Minimal Python version sanity check (from IPython) +#----------------------------------------------------------------------------- + +from __future__ import print_function + +import os +import sys + +v = sys.version_info +if v[:2] < (2,7) or (v[0] >= 3 and v[:2] < (3,3)): + error = "ERROR: IPython requires Python version 2.7 or 3.3 or above." + print(error, file=sys.stderr) + sys.exit(1) + +PY3 = (sys.version_info[0] >= 3) + +if os.name in ('nt', 'dos'): + error = "ERROR: Windows is not supported" + print(error, file=sys.stderr) + +# At least we're on the python version we need, move on. + +import os + +from glob import glob + +from distutils.core import setup +from subprocess import check_call + +try: + execfile +except NameError: + # py3 + def execfile(fname, globs, locs=None): + locs = locs or globs + exec(compile(open(fname).read(), fname, "exec"), globs, locs) + +here = os.path.abspath(os.path.dirname(__file__)) +pjoin = os.path.join + +#--------------------------------------------------------------------------- +# Build basic package data, etc. +#--------------------------------------------------------------------------- + +def get_data_files(): + """Get data files in share/jupyter""" + + data_files = [] + share_jupyter = pjoin(here, 'share', 'jupyter') + ntrim = len(here) + 1 + + for (d, dirs, filenames) in os.walk(share_jupyter): + data_files.append(( + d[ntrim:], + [ pjoin(d, f) for f in filenames ] + )) + return data_files + + +ns = {} +execfile(pjoin(here, 'jupyterhub', 'version.py'), ns) + +setup_args = dict( + name = 'jupyterhub', + scripts = glob(pjoin('scripts', '*')), + packages = ['jupyterhub'], + package_data = {'jupyterhub' : ['templates/*']}, + # dummy, so that install_data doesn't get skipped + # this will be overridden when bower is run anyway + data_files = get_data_files() or ['dummy'], + version = ns['__version__'], + description = """JupyterHub: A multi-user server for Jupyter notebooks""", + long_description = "", + author = "Jupyter Development Team", + author_email = "ipython-dev@scipy.org", + url = "http://jupyter.org", + license = "BSD", + platforms = "Linux, Mac OS X", + keywords = ['Interactive', 'Interpreter', 'Shell', 'Web'], + classifiers = [ + 'Intended Audience :: Developers', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: BSD License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Topic :: System :: Shells', + ], +) + +#--------------------------------------------------------------------------- +# custom distutils commands +#--------------------------------------------------------------------------- + +# imports here, so they are after setuptools import if there was one +from distutils.cmd import Command +from distutils.command.install import install + +class Bower(Command): + description = "fetch static components with bower" + + user_options = [] + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + check_call(['bower', 'install']) + self.distribution.data_files = get_data_files() + + def get_outputs(self): + return [] + + def get_inputs(self): + return [] + +# ensure bower is run as part of install +install.sub_commands.insert(0, ('bower', None)) + +setup_args['cmdclass'] = { + 'bower': Bower, +} + +# setuptools requirements + +if 'setuptools' in sys.modules: + setup_args['zip_safe'] = False + + with open('requirements.txt') as f: + install_requires = [ line.strip() for line in f.readlines() ] + setup_args['install_requires'] = install_requires + +#--------------------------------------------------------------------------- +# setup +#--------------------------------------------------------------------------- + +def main(): + setup(**setup_args) + +if __name__ == '__main__': + main() diff --git a/setupegg.py b/setupegg.py new file mode 100755 index 00000000..7b4b5687 --- /dev/null +++ b/setupegg.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +"""Wrapper to run setup.py using setuptools.""" + +# Import setuptools and call the actual setup +import setuptools +with open('setup.py', 'rb') as f: + exec(compile(f.read(), 'setup.py', 'exec'))