diff --git a/base/build.sh b/base/build.sh deleted file mode 100644 index 4749334..0000000 --- a/base/build.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -branch=$(git symbolic-ref --short HEAD) -docker build --no-cache -t registry.spin.nersc.gov/das/jupyterhub-base.$branch:latest . diff --git a/base/Dockerfile b/jupyter-base/Dockerfile similarity index 85% rename from base/Dockerfile rename to jupyter-base/Dockerfile index 18d40a7..cacd415 100644 --- a/base/Dockerfile +++ b/jupyter-base/Dockerfile @@ -30,10 +30,11 @@ RUN \ RUN \ curl -s -o /tmp/miniconda3.sh https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh && \ - bash /tmp/miniconda3.sh -f -b -p /opt/anaconda3 && \ - rm -rf /tmp/anaconda3.sh && \ - /opt/anaconda3/bin/conda update --yes conda && \ - /opt/anaconda3/bin/conda install --yes \ + bash /tmp/miniconda3.sh -f -b -p /opt/anaconda3 && \ + rm -rf /tmp/miniconda3.sh && \ + echo "python 3.6.*" >> /opt/anaconda3/conda-meta/pinned && \ + /opt/anaconda3/bin/conda update --yes conda && \ + /opt/anaconda3/bin/conda install --yes \ alembic \ decorator \ jinja2 \ diff --git a/jupyter-base/build.sh b/jupyter-base/build.sh new file mode 100644 index 0000000..bcae179 --- /dev/null +++ b/jupyter-base/build.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +branch=$(git symbolic-ref --short HEAD) + +docker build \ + --no-cache \ + --tag registry.spin.nersc.gov/das/jupyter-base.$branch:latest . diff --git a/jupyter-dev/Dockerfile b/jupyter-dev-nersc/web/Dockerfile similarity index 92% rename from jupyter-dev/Dockerfile rename to jupyter-dev-nersc/web/Dockerfile index 3edaeaf..7b20602 100644 --- a/jupyter-dev/Dockerfile +++ b/jupyter-dev-nersc/web/Dockerfile @@ -1,6 +1,6 @@ ARG branch=unknown -FROM registry.spin.nersc.gov/das/jupyterhub-base.${branch}:latest +FROM registry.spin.nersc.gov/das/jupyter-base.${branch}:latest LABEL maintainer="Rollin Thomas " WORKDIR /srv diff --git a/jupyter-dev/build.sh b/jupyter-dev-nersc/web/build.sh similarity index 52% rename from jupyter-dev/build.sh rename to jupyter-dev-nersc/web/build.sh index 81a8225..284a4f5 100644 --- a/jupyter-dev/build.sh +++ b/jupyter-dev-nersc/web/build.sh @@ -4,5 +4,5 @@ branch=$(git symbolic-ref --short HEAD) docker build \ --build-arg branch=$branch \ - --no-cache \ - --tag registry.spin.nersc.gov/das/jupyterhub-jupyter-dev.$branch:latest . + "$@" \ + --tag registry.spin.nersc.gov/das/jupyter-dev-nersc-web.$branch:latest . diff --git a/jupyter-dev/docker-compose.yml b/jupyter-dev-nersc/web/docker-compose.yml similarity index 100% rename from jupyter-dev/docker-compose.yml rename to jupyter-dev-nersc/web/docker-compose.yml diff --git a/jupyter-dev/docker-entrypoint.sh b/jupyter-dev-nersc/web/docker-entrypoint.sh similarity index 100% rename from jupyter-dev/docker-entrypoint.sh rename to jupyter-dev-nersc/web/docker-entrypoint.sh diff --git a/jupyter-dev/hub-scripts/flush-certs.sh b/jupyter-dev-nersc/web/hub-scripts/flush-certs.sh similarity index 100% rename from jupyter-dev/hub-scripts/flush-certs.sh rename to jupyter-dev-nersc/web/hub-scripts/flush-certs.sh diff --git a/jupyter-dev/hub-scripts/kill-cori.sh b/jupyter-dev-nersc/web/hub-scripts/kill-cori.sh similarity index 100% rename from jupyter-dev/hub-scripts/kill-cori.sh rename to jupyter-dev-nersc/web/hub-scripts/kill-cori.sh diff --git a/jupyter-dev/hub-scripts/scram-user.sh b/jupyter-dev-nersc/web/hub-scripts/scram-user.sh similarity index 100% rename from jupyter-dev/hub-scripts/scram-user.sh rename to jupyter-dev-nersc/web/hub-scripts/scram-user.sh diff --git a/jupyter-dev/hub-scripts/test-user.sh b/jupyter-dev-nersc/web/hub-scripts/test-user.sh similarity index 100% rename from jupyter-dev/hub-scripts/test-user.sh rename to jupyter-dev-nersc/web/hub-scripts/test-user.sh diff --git a/jupyter-spin/jupyterhub_config.py b/jupyter-dev-nersc/web/jupyterhub_config.py similarity index 91% rename from jupyter-spin/jupyterhub_config.py rename to jupyter-dev-nersc/web/jupyterhub_config.py index 3de7f25..2576503 100644 --- a/jupyter-spin/jupyterhub_config.py +++ b/jupyter-dev-nersc/web/jupyterhub_config.py @@ -1,6 +1,9 @@ # Configuration file for jupyterhub. import os +import sys + +import requests def comma_split(string): """Handle env variables that may be None, empty string, or have spaces""" @@ -13,6 +16,13 @@ def comma_split(string): else: return list() +ip = requests.get('https://v4.ifconfig.co/json').json()['ip'] + +#bindir = '/global/common/gerty/software/python/3.6-anaconda-5.2/bin/' +bindir = '/global/common/cori/software/python/3.6-anaconda-5.2/bin/' +if 'BASE_PATH' in os.environ: + bindir = os.path.join(os.environ['BASE_PATH'], 'bin') + #------------------------------------------------------------------------------ # Application(SingletonConfigurable) configuration #------------------------------------------------------------------------------ @@ -91,6 +101,7 @@ def comma_split(string): # where `handler` is the calling web.RequestHandler, # and `data` is the POST form data from the login page. #c.JupyterHub.authenticator_class = 'jupyterhub.auth.PAMAuthenticator' +c.JupyterHub.authenticator_class = 'sshapiauthenticator.auth.SSHAPIAuthenticator' ## The base URL of the entire application. # @@ -155,6 +166,7 @@ def comma_split(string): ## Number of days for a login cookie to be valid. Default is two weeks. #c.JupyterHub.cookie_max_age_days = 14 +c.JupyterHub.cookie_max_age_days = 0.5 ## The cookie secret to use to encrypt cookies. # @@ -175,6 +187,9 @@ def comma_split(string): ## url for the database. e.g. `sqlite:///jupyterhub.sqlite` #c.JupyterHub.db_url = 'sqlite:///jupyterhub.sqlite' +c.JupyterHub.db_url = 'postgresql://jupyterhub:{}@db:5432/jupyterhub'.format( + os.getenv('POSTGRES_PASSWORD') +) ## log all database transactions. This has A LOT of output #c.JupyterHub.debug_db = False @@ -265,6 +280,7 @@ def comma_split(string): # See `hub_connect_ip` for cases where the bind and connect address should # differ, or `hub_bind_url` for setting the full bind URL. #c.JupyterHub.hub_ip = '127.0.0.1' +c.JupyterHub.hub_ip = '0.0.0.0' ## The internal port for the Hub process. # @@ -286,7 +302,6 @@ def comma_split(string): # .. deprecated: 0.9 # Use JupyterHub.bind_url #c.JupyterHub.ip = '' -c.JupyterHub.ip = '0.0.0.0' ## Supply extra arguments that will be passed to Jinja environment. #c.JupyterHub.jinja_environment_options = {} @@ -320,7 +335,6 @@ def comma_split(string): ## DEPRECATED since version 0.8 : Use ConfigurableHTTPProxy.api_url #c.JupyterHub.proxy_api_ip = '' -c.JupyterHub.proxy_api_ip = '127.0.0.1' ## DEPRECATED since version 0.8 : Use ConfigurableHTTPProxy.api_url #c.JupyterHub.proxy_api_port = 0 @@ -377,6 +391,11 @@ def comma_split(string): 'name': 'cull-idle', 'admin': True, 'command': 'cull_idle_servers.py --timeout=43200'.split(), + }, + { + 'name': 'announcement', + 'url': 'http://127.0.0.1:8888', + 'command': ["python", "-m", "announcement"], } ] @@ -384,6 +403,7 @@ def comma_split(string): # # Should be a subclass of Spawner. #c.JupyterHub.spawner_class = 'jupyterhub.spawner.LocalProcessSpawner' +c.JupyterHub.spawner_class = 'sshspawner.sshspawner.SSHSpawner' ## Path to SSL certificate file for the public facing interface of the proxy # @@ -422,6 +442,7 @@ def comma_split(string): ## Paths to search for jinja templates, before using the default templates. #c.JupyterHub.template_paths = [] +c.JupyterHub.template_paths = ["templates"] ## Extra variables to be passed into jinja templates #c.JupyterHub.template_vars = {} @@ -488,7 +509,7 @@ def comma_split(string): # environment variables. Most, including the default, do not. Consult the # documentation for your spawner to verify! #c.Spawner.cmd = ['jupyterhub-singleuser'] -c.Spawner.cmd = ['/opt/anaconda3/bin/jupyter-labhub'] +c.Spawner.cmd = [os.path.join(bindir, 'jupyter-labhub')] ## Maximum number of consecutive failures to allow before shutting down # JupyterHub. @@ -592,7 +613,7 @@ def comma_split(string): # The JupyterHub proxy implementation should be able to send packets to this # interface. #c.Spawner.ip = '' -c.Spawner.ip = '127.0.0.1' +c.Spawner.ip = '0.0.0.0' ## Minimum number of bytes a single-user notebook server is guaranteed to have # available. @@ -676,6 +697,7 @@ def comma_split(string): # JupyterHub modifies its own state accordingly and removes appropriate routes # from the configurable proxy. #c.Spawner.poll_interval = 30 +c.Spawner.poll_interval = 900 ## The port for single-user servers to listen on. # @@ -930,7 +952,6 @@ def comma_split(string): ## The name of the PAM service to use for authentication #c.PAMAuthenticator.service = 'login' -c.PAMAuthenticator.service = 'jupyterhub' #------------------------------------------------------------------------------ # CryptKeeper(SingletonConfigurable) configuration @@ -945,3 +966,51 @@ def comma_split(string): ## The number of threads to allocate for encryption #c.CryptKeeper.n_threads = 2 + +#------------------------------------------------------------------------------ +# SSHAPIAuthenticator(Authenticator) configuration +#------------------------------------------------------------------------------ + +c.SSHAPIAuthenticator.server = 'https://sshproxy.nersc.gov/create_pair/jupyter/' +c.SSHAPIAuthenticator.skey = os.environ.get('SKEY') +c.SSHAPIAuthenticator.cert_path = '/certs' + +#------------------------------------------------------------------------------ +# SSHSpawner(Spawner) configuration +#------------------------------------------------------------------------------ + +c.SSHSpawner.hub_api_url = "http://{}:8081/hub/api".format(ip) +c.SSHSpawner.path = bindir + ':/global/common/cori/das/jupyterhub/:/usr/common/usg/bin:/usr/bin:/bin' +c.SSHSpawner.ssh_keyfile = '/certs/{username}.key' + +import asyncssh, random +from tornado import web + +c.SSHSpawner.remote_hosts = ['cori19-224.nersc.gov'] +#c.SSHSpawner.remote_host = ['gert01-224.nersc.gov'] +c.SSHSpawner.remote_port_command = "python -c 'import socket; s=socket.socket(); s.bind((\"\", 0)); print(s.getsockname()[1]); s.close()'" + +# async def setup(spawner): +# username = spawner.user.name +# remote_host = random.choice(spawner.remote_hosts) +# keyfile = spawner.ssh_keyfile.format(username=username) +# certfile = keyfile + "-cert.pub" +# k = asyncssh.read_private_key(keyfile) +# c = asyncssh.read_certificate(certfile) +# print(username, remote_host, keyfile, certfile) +# async with asyncssh.connect(remote_host, +# username=username, +# client_keys=[(k,c)], +# known_hosts=None) as conn: +# result = await conn.run("myquota -c") +# retcode = result.exit_status +# result = await conn.run(spawner.remote_port_command) +# remote_port = int(result.stdout) +# if retcode: +# e = web.HTTPError(507,reason="Insufficient Storage") +# e.my_message = "There is insufficient space in your home directory; please clear up some files and try again." +# raise e +# spawner.remote_host = remote_host +# spawner.port = remote_port +# +# c.Spawner.pre_spawn_hook = setup diff --git a/jupyter-dev/templates/error.html b/jupyter-dev-nersc/web/templates/error.html similarity index 100% rename from jupyter-dev/templates/error.html rename to jupyter-dev-nersc/web/templates/error.html diff --git a/jupyter-dev/templates/login.html b/jupyter-dev-nersc/web/templates/login.html similarity index 100% rename from jupyter-dev/templates/login.html rename to jupyter-dev-nersc/web/templates/login.html diff --git a/jupyter-localhost/Dockerfile b/jupyter-localhost/Dockerfile index e37a2bf..0145036 100644 --- a/jupyter-localhost/Dockerfile +++ b/jupyter-localhost/Dockerfile @@ -1,4 +1,4 @@ -FROM registry.spin.nersc.gov/das/jupyterhub-base:latest +FROM registry.spin.nersc.gov/das/jupyter-base:latest MAINTAINER Rollin Thomas # Python 3 Anaconda and additional packages diff --git a/jupyter-spin/Dockerfile b/jupyter-nersc/app/Dockerfile similarity index 55% rename from jupyter-spin/Dockerfile rename to jupyter-nersc/app/Dockerfile index 76ef084..8063552 100644 --- a/jupyter-spin/Dockerfile +++ b/jupyter-nersc/app/Dockerfile @@ -1,24 +1,38 @@ -FROM registry.spin.nersc.gov/das/jupyterhub-base.gaffer:latest +ARG branch=unknown + +FROM registry.spin.nersc.gov/das/jupyter-base.${branch}:latest LABEL maintainer="Rollin Thomas " +WORKDIR /srv # Additional Ubuntu packages RUN \ - apt-get update && \ - apt-get install --yes \ - csh \ - dvipng \ - ldap-utils \ - libnss-ldapd \ - libpam-ldap \ - nscd \ - rsyslog \ - supervisor \ - texlive-xetex \ - zsh + apt-get --yes install \ + csh \ + ldap-utils \ + libnss-ldapd \ + libpam-ldap \ + nscd \ + openssh-server \ + supervisor # Python 3 Anaconda and additional packages +##### RUN \ +##### /opt/anaconda3/bin/conda update --yes conda && \ +##### /opt/anaconda3/bin/conda install --yes \ +##### ipykernel \ +##### ipywidgets \ +##### jupyterlab \ +##### notebook && \ +##### /opt/anaconda3/bin/ipython kernel install && \ +##### /opt/anaconda3/bin/conda clean --yes --all +##### +##### # Typical extension +##### +##### RUN \ +##### /opt/anaconda3/bin/jupyter nbextension enable --sys-prefix --py widgetsnbextension + ADD packages3.txt /tmp/packages3.txt RUN \ /opt/anaconda3/bin/conda update --yes conda && \ @@ -31,7 +45,7 @@ RUN \ ADD packages2.txt /tmp/packages2.txt RUN \ - curl -s -o /tmp/miniconda2.sh https://repo.continuum.io/miniconda/Miniconda2-latest-Linux-x86_64.sh && \ + wget -q -O /tmp/miniconda2.sh https://repo.continuum.io/miniconda/Miniconda2-latest-Linux-x86_64.sh && \ bash /tmp/miniconda2.sh -f -b -p /opt/anaconda2 && \ rm /tmp/miniconda2.sh && \ /opt/anaconda2/bin/conda update --yes conda && \ @@ -40,7 +54,20 @@ RUN \ /opt/anaconda2/bin/ipython kernel install && \ /opt/anaconda2/bin/conda clean --yes --all -# PAM, LDAP +# For ssh auth API + +ADD NERSC-keys-api /usr/lib/nersc-ssh-keys/ +RUN chmod a+x /usr/lib/nersc-ssh-keys/NERSC-keys-api + +# For sshd + +RUN \ + mkdir -p /var/run/sshd && \ + echo "AuthorizedKeysCommand /usr/lib/nersc-ssh-keys/NERSC-keys-api" >> /etc/ssh/sshd_config && \ + echo "AuthorizedKeysCommandUser nobody" >> /etc/ssh/sshd_config && \ + echo "TrustedUserCAKeys /etc/user_ca.pub" >> /etc/ssh/sshd_config + +# For PAM/LDAP COPY etc/ /etc/ @@ -53,10 +80,9 @@ RUN \ ln -s /global/common/datatran /usr/common && \ echo "datatran" > /etc/clustername -# Jupyterhub/lab features +# JupyterHub/lab features RUN \ - conda install --yes jupyterlab=0.33.8 && \ pip install --no-cache-dir ipympl && \ jupyter nbextension enable --sys-prefix --py widgetsnbextension && \ jupyter labextension install \ @@ -66,19 +92,14 @@ RUN \ @jupyterlab/toc \ jupyterlab_bokeh -# Not sure we need this - RUN \ /opt/anaconda2/bin/jupyter nbextension enable --sys-prefix --py widgetsnbextension -# Config and entrypoint script - -ADD docker-entrypoint.sh jupyterhub_config.py /srv/ - -WORKDIR /srv +# Get port script -# TEMPORARILY HERE: Set up supervisord +ADD get_port.py /opt/anaconda3/bin/ -COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf +# Supervisord to launch sshd and nslcd +ADD supervisord.conf /etc/supervisor/conf.d/supervisord.conf CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"] diff --git a/jupyter-nersc/app/NERSC-keys-api b/jupyter-nersc/app/NERSC-keys-api new file mode 100644 index 0000000..6718bd5 --- /dev/null +++ b/jupyter-nersc/app/NERSC-keys-api @@ -0,0 +1,7 @@ +#!/bin/sh + +URL=https://sshproxy.nersc.gov/get_keys +USER=$1 + + +curl $URL/$USER diff --git a/jupyter-spin/build.sh b/jupyter-nersc/app/build.sh similarity index 51% rename from jupyter-spin/build.sh rename to jupyter-nersc/app/build.sh index daf9890..b408866 100644 --- a/jupyter-spin/build.sh +++ b/jupyter-nersc/app/build.sh @@ -4,5 +4,5 @@ branch=$(git symbolic-ref --short HEAD) docker build \ --build-arg branch=$branch \ - --no-cache \ - --tag registry.spin.nersc.gov/das/jupyterhub-jupyter-spin.$branch:latest . + "$@" \ + --tag registry.spin.nersc.gov/das/jupyter-nersc-app.$branch:latest . diff --git a/jupyter-nersc/app/docker-entrypoint.sh b/jupyter-nersc/app/docker-entrypoint.sh new file mode 100644 index 0000000..9d8ad4d --- /dev/null +++ b/jupyter-nersc/app/docker-entrypoint.sh @@ -0,0 +1,2 @@ +#!/bin/bash +exec "$@" diff --git a/jupyter-spin/etc/ldap.conf b/jupyter-nersc/app/etc/ldap.conf similarity index 100% rename from jupyter-spin/etc/ldap.conf rename to jupyter-nersc/app/etc/ldap.conf diff --git a/jupyter-spin/etc/nslcd.conf b/jupyter-nersc/app/etc/nslcd.conf similarity index 97% rename from jupyter-spin/etc/nslcd.conf rename to jupyter-nersc/app/etc/nslcd.conf index 4d695ce..ba8efe6 100644 --- a/jupyter-spin/etc/nslcd.conf +++ b/jupyter-nersc/app/etc/nslcd.conf @@ -21,5 +21,3 @@ nss_initgroups_ignoreusers ALLLOCAL idle_timelimit 240 filter passwd (&(objectClass=posixAccount)(authorizedService=ssh)) - -log syslog debug diff --git a/jupyter-spin/etc/nsswitch.conf b/jupyter-nersc/app/etc/nsswitch.conf similarity index 100% rename from jupyter-spin/etc/nsswitch.conf rename to jupyter-nersc/app/etc/nsswitch.conf diff --git a/jupyter-spin/etc/pam.d/jupyterhub b/jupyter-nersc/app/etc/pam.d/jupyterhub similarity index 100% rename from jupyter-spin/etc/pam.d/jupyterhub rename to jupyter-nersc/app/etc/pam.d/jupyterhub diff --git a/jupyter-spin/etc/pam.d/password-auth b/jupyter-nersc/app/etc/pam.d/password-auth similarity index 100% rename from jupyter-spin/etc/pam.d/password-auth rename to jupyter-nersc/app/etc/pam.d/password-auth diff --git a/jupyter-nersc/app/etc/user_ca.pub b/jupyter-nersc/app/etc/user_ca.pub new file mode 100644 index 0000000..ff89c20 --- /dev/null +++ b/jupyter-nersc/app/etc/user_ca.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA2yKBpvRbdD9MWiu+7wg17vBsKy46AjjuL27DmdpDYiCqRE2mN0om9b0jn4eI91RGykbcRa9wUKJ2qaD0zsD08A8HM+R14H4UsZ5hi7S+xGqscJH7uTmXy5Igo5xEOahS9Z+ecgonDCgWKJnbd/FRu4vITYXrvTlIIGHGRBYj0GzbgLHBzedoMaGNRwhVyadH2SGRaZCgbH+Swevzy0GwYfZJA9zd7EX0jiAClkSYcflIOsygmI3gHv+b35mrvXcHDeQOR/wg8knfpSiFLCkVDpfgnj27Lemzxe6k61Brhv9CUiq+t7WApVDBovhdXZn6pBg+OKeDk1G1OLvRbxJ2bw== dunford@muppet.nersc.gov diff --git a/jupyter-nersc/app/get_port.py b/jupyter-nersc/app/get_port.py new file mode 100644 index 0000000..ab26463 --- /dev/null +++ b/jupyter-nersc/app/get_port.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +import socket + +sock = socket.socket() +sock.bind(('', 0)) +print(sock.getsockname()[1]) +sock.close() diff --git a/jupyter-spin/packages2.txt b/jupyter-nersc/app/packages2.txt similarity index 100% rename from jupyter-spin/packages2.txt rename to jupyter-nersc/app/packages2.txt diff --git a/jupyter-spin/packages3.txt b/jupyter-nersc/app/packages3.txt similarity index 60% rename from jupyter-spin/packages3.txt rename to jupyter-nersc/app/packages3.txt index 06193db..b447306 100644 --- a/jupyter-spin/packages3.txt +++ b/jupyter-nersc/app/packages3.txt @@ -1,7 +1,10 @@ +astropy basemap biopython +blaze +jupyter +jupyterlab netcdf4 -r-car r-essentials rpy2 ujson diff --git a/jupyter-spin/supervisord.conf b/jupyter-nersc/app/supervisord.conf similarity index 84% rename from jupyter-spin/supervisord.conf rename to jupyter-nersc/app/supervisord.conf index c8eb242..9a67e11 100644 --- a/jupyter-spin/supervisord.conf +++ b/jupyter-nersc/app/supervisord.conf @@ -1,9 +1,9 @@ [supervisord] nodaemon=true -[program:jupyterhub] +[program:sshd] directory=/srv -command=jupyterhub --debug +command=/usr/sbin/sshd -p 22 -D stdout_logfile=/dev/fd/1 stdout_logfile_maxbytes=0 redirect_stderr=true diff --git a/jupyter-nersc/web/Dockerfile b/jupyter-nersc/web/Dockerfile new file mode 100644 index 0000000..7b20602 --- /dev/null +++ b/jupyter-nersc/web/Dockerfile @@ -0,0 +1,36 @@ +ARG branch=unknown + +FROM registry.spin.nersc.gov/das/jupyter-base.${branch}:latest +LABEL maintainer="Rollin Thomas " +WORKDIR /srv + +# Authenticator and spawner + +RUN \ + pip install git+https://github.com/nersc/sshapiauthenticator.git && \ + pip install git+https://github.com/nersc/sshspawner.git + +# MFA-enabled login template + +ADD templates templates + +# Install announcement service components + +RUN \ + cp /tmp/jupyterhub/examples/service-announcement/announcement.py . && \ + cp /tmp/jupyterhub/examples/service-announcement/templates/page.html templates/. + +# Hub scripts + +ADD hub-scripts/*.sh /srv/ + +# Volume for user cert/key files + +VOLUME /certs + +# Entrypoint and command + +ADD docker-entrypoint.sh jupyterhub_config.py /srv/ +RUN chmod +x docker-entrypoint.sh +ENTRYPOINT ["./docker-entrypoint.sh"] +CMD ["jupyterhub", "--debug"] diff --git a/jupyter-nersc/web/build.sh b/jupyter-nersc/web/build.sh new file mode 100644 index 0000000..b136616 --- /dev/null +++ b/jupyter-nersc/web/build.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +branch=$(git symbolic-ref --short HEAD) + +docker build \ + --build-arg branch=$branch \ + "$@" \ + --tag registry.spin.nersc.gov/das/jupyter-nersc-web.$branch:latest . diff --git a/jupyter-nersc/web/docker-entrypoint.sh b/jupyter-nersc/web/docker-entrypoint.sh new file mode 100644 index 0000000..057050a --- /dev/null +++ b/jupyter-nersc/web/docker-entrypoint.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# file_env VAR [DEFAULT] +# ---------------------- +# Treat the value of VAR_FILE as the path to a secrets file and initialize VAR +# with the contents of that file. From postgres docker-entrypoint.sh. + +file_env() { + local var="$1" + local fileVar="${var}_FILE" + local def="${2:-}" + if [ "${!var:-}" ] && [ "${!fileVar:-}" ]; then + echo >&2 "error: both $var and $fileVar are set (but are exclusive)" + exit 1 + fi + local val="$def" + if [ "${!var:-}" ]; then + val="${!var}" + elif [ "${!fileVar:-}" ]; then + val="$(< "${!fileVar}")" + fi + export "$var"="$val" + unset "$fileVar" +} + +file_env 'POSTGRES_PASSWORD' +file_env 'SKEY' + +exec "$@" diff --git a/jupyter-nersc/web/hub-scripts/flush-certs.sh b/jupyter-nersc/web/hub-scripts/flush-certs.sh new file mode 100644 index 0000000..5c1e2cf --- /dev/null +++ b/jupyter-nersc/web/hub-scripts/flush-certs.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# Get rid of any cert files older than 2 weeks + +limit=14 + +find /certs -type f -name '*.key' -mtime +$limit -exec rm {} \; +find /certs -type f -name '*.key-cert.pub' -mtime +$limit -exec rm {} \; +find /certs -type f -name '*.key.pub' -mtime +$limit -exec rm {} \; diff --git a/jupyter-nersc/web/hub-scripts/test-user.sh b/jupyter-nersc/web/hub-scripts/test-user.sh new file mode 100644 index 0000000..5dbab43 --- /dev/null +++ b/jupyter-nersc/web/hub-scripts/test-user.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Test user's ability to ssh + +hostname=app +username=$1 +cert=/certs/$username.key +echo $username $cert +if [ ! -f $cert ]; then + echo " ... no cert for $username" + exit 1 +fi +/usr/bin/ssh \ + -i $cert \ + -l $username \ + -o PreferredAuthentications=publickey \ + -o StrictHostKeyChecking=no \ + -p 22 \ + $hostname diff --git a/jupyter-dev/jupyterhub_config.py b/jupyter-nersc/web/jupyterhub_config.py similarity index 97% rename from jupyter-dev/jupyterhub_config.py rename to jupyter-nersc/web/jupyterhub_config.py index 9a5a9aa..cb086c1 100644 --- a/jupyter-dev/jupyterhub_config.py +++ b/jupyter-nersc/web/jupyterhub_config.py @@ -18,7 +18,7 @@ def comma_split(string): ip = requests.get('https://v4.ifconfig.co/json').json()['ip'] -bindir = '/global/common/cori/software/python/3.6-anaconda-5.2/bin/' +bindir = '/opt/anaconda3/bin/' if 'BASE_PATH' in os.environ: bindir = os.path.join(os.environ['BASE_PATH'], 'bin') @@ -978,26 +978,9 @@ def comma_split(string): # SSHSpawner(Spawner) configuration #------------------------------------------------------------------------------ -c.SSHSpawner.remote_hosts = ['cori19-224.nersc.gov'] +c.SSHSpawner.remote_hosts = ['app'] c.SSHSpawner.remote_port = '22' c.SSHSpawner.hub_api_url = "http://{}:8081/hub/api".format(ip) -c.SSHSpawner.path = bindir + ':/global/common/cori/das/jupyterhub/:/usr/common/usg/bin:/usr/bin:/bin' -c.SSHSpawner.remote_port_command = '/usr/bin/python /global/common/cori/das/jupyterhub/get_port.py' +c.SSHSpawner.path = bindir + ':/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' +c.SSHSpawner.remote_port_command = bindir + 'python /opt/anaconda3/bin/get_port.py' c.SSHSpawner.ssh_keyfile = '/certs/{username}.key' - -from tornado import web -import asyncio, asyncssh - -async def my_quota(spawner): - host = spawner.choose_remote_host() - username = spawner.user.name - k = asyncssh.read_private_key(spawner.ssh_keyfile.format(username=username)) - async with asyncssh.connect(host,username=username,client_keys=[k],known_hosts=None) as conn: - result = await conn.run("myquota -c") - retcode = result.exit_status - if retcode: - e = web.HTTPError(507,reason="Insufficient Storage") - e.my_message = "There is insufficient space in your home directory; please clear up some files and try again." - raise e - -c.Spawner.pre_spawn_hook = my_quota \ No newline at end of file diff --git a/jupyter-nersc/web/templates/error.html b/jupyter-nersc/web/templates/error.html new file mode 100644 index 0000000..25aa9a5 --- /dev/null +++ b/jupyter-nersc/web/templates/error.html @@ -0,0 +1,12 @@ +{% extends "templates/error.html" %} + +{% block error_detail %} +{% if exception and exception.my_message %} +
+

{{ exception.my_message }}

+
+{% else %} +{{ super() }} +{% endif %} + +{% endblock error_detail %} \ No newline at end of file diff --git a/jupyter-nersc/web/templates/login.html b/jupyter-nersc/web/templates/login.html new file mode 100644 index 0000000..92d57dc --- /dev/null +++ b/jupyter-nersc/web/templates/login.html @@ -0,0 +1,99 @@ +{% extends "page.html" %} {# This extends page.html, which has announcements in it. #} +{% if announcement_login %} + {% set announcement = announcement_login %} +{% endif %} + +{% block login_widget %} +{% endblock %} + +{% block main %} + +{% block login %} +
+{% if custom_html %} +{{ custom_html | safe }} +{% elif login_service %} + +{% else %} +
+
+ Sign in +
+
+ + + + {% if login_error %} + + {% endif %} + + + + + + + + + +
+
+{% endif %} +
+{% endblock login %} + +{% endblock %} + +{% block script %} +{{ super() }} + + + +{% endblock %} diff --git a/jupyter-off/Dockerfile b/jupyter-off/Dockerfile index bf88d31..72a684e 100644 --- a/jupyter-off/Dockerfile +++ b/jupyter-off/Dockerfile @@ -1,4 +1,47 @@ -FROM nginx:1.13.7-alpine -MAINTAINER Rollin Thomas -COPY html /usr/share/nginx/html -COPY nginx.conf /etc/nginx +FROM ubuntu:16.04 +LABEL maintainer="Rollin Thomas " + +# Base Ubuntu packages + +ENV DEBIAN_FRONTEND noninteractive +ENV LANG C.UTF-8 + +RUN \ + apt-get update && \ + apt-get --yes upgrade && \ + apt-get --yes install \ + bzip2 \ + curl \ + tzdata \ + vim + +# Timezone to Berkeley + +ENV TZ=America/Los_Angeles +RUN \ + ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \ + echo $TZ > /etc/timezone + +# Add flask and gunicorn + +RUN \ + curl -s -o /tmp/miniconda3.sh https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh && \ + bash /tmp/miniconda3.sh -f -b -p /opt/anaconda3 && \ + rm -rf /tmp/miniconda3.sh && \ + echo "python 3.6.*" >> /opt/anaconda3/conda-meta/pinned && \ + /opt/anaconda3/bin/conda update --yes conda && \ + /opt/anaconda3/bin/conda install --yes \ + flask \ + gunicorn && \ + /opt/anaconda3/bin/conda clean --yes --all + +ENV PATH=/opt/anaconda3/bin:$PATH + +# Application + +WORKDIR /srv +ADD app.py /srv/ +ADD templates /srv/templates +ADD static /srv/static + +CMD ["gunicorn", "app:app", "-b", ":8000", "--name", "app", "--workers=4", "--log-file=-"] diff --git a/jupyter-off/README.md b/jupyter-off/README.md new file mode 100644 index 0000000..93f68aa --- /dev/null +++ b/jupyter-off/README.md @@ -0,0 +1,5 @@ + +Jupyter is Offline +================== + +Set the `MESSAGE` environment variable if you want to customize it and upgrade the container! diff --git a/jupyter-off/app.py b/jupyter-off/app.py new file mode 100644 index 0000000..967035d --- /dev/null +++ b/jupyter-off/app.py @@ -0,0 +1,17 @@ +import os + +from flask import Flask, render_template + +app = Flask(__name__) + +@app.route('/', defaults={'path': ''}) +@app.route('/') +def catch_all(path): + default_message = "NERSC's Jupyter service is offline. It will return when maintenance is over. Please try again later." + message = os.environ.get("MESSAGE", default_message).strip() + if not message: + message = default_message + return render_template("index.html", message=message), 503 + +if __name__ == '__main__': + app.run(host="0.0.0.0") diff --git a/jupyter-off/build.sh b/jupyter-off/build.sh new file mode 100644 index 0000000..4884cac --- /dev/null +++ b/jupyter-off/build.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +docker build \ + "$@" \ + --tag registry.spin.nersc.gov/das/jupyter-off:latest . diff --git a/jupyter-off/nginx.conf b/jupyter-off/nginx.conf deleted file mode 100644 index c83a12f..0000000 --- a/jupyter-off/nginx.conf +++ /dev/null @@ -1,52 +0,0 @@ - -user nginx; -worker_processes 1; - -error_log /var/log/nginx/error.log warn; -pid /var/run/nginx.pid; - - -events { - worker_connections 1024; -} - - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log /var/log/nginx/access.log main; - - sendfile on; - #tcp_nopush on; - - keepalive_timeout 65; - - #gzip on; - - server { - - listen 80; - listen 443; - listen 8000; - server_name localhost; - - location / { - root /usr/share/nginx/html; - index index.html index.htm; - rewrite ^/(.*\.png)$ /$1 break; - rewrite ^/.*$ /index.html break; - } - - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root /usr/share/nginx/html; - } - - } - -} diff --git a/jupyter-off/html/logo.png b/jupyter-off/static/logo.png similarity index 100% rename from jupyter-off/html/logo.png rename to jupyter-off/static/logo.png diff --git a/jupyter-off/html/rectanglelogo-greytext-orangebody-greymoons.png b/jupyter-off/static/rectanglelogo-greytext-orangebody-greymoons.png similarity index 100% rename from jupyter-off/html/rectanglelogo-greytext-orangebody-greymoons.png rename to jupyter-off/static/rectanglelogo-greytext-orangebody-greymoons.png diff --git a/jupyter-off/html/index.html b/jupyter-off/templates/index.html similarity index 75% rename from jupyter-off/html/index.html rename to jupyter-off/templates/index.html index a3bb0a8..b8c4ea4 100644 --- a/jupyter-off/html/index.html +++ b/jupyter-off/templates/index.html @@ -16,20 +16,17 @@
- +
- +

Jupyter is Offline

-

Sorry for the inconvenience. - NERSC's Jupyter service is offline. - It will return when maintenance is over. - Please try again later.

+

Sorry for the inconvenience. {{ message }}

diff --git a/jupyter-spin/docker-entrypoint.sh b/jupyter-spin/docker-entrypoint.sh deleted file mode 100755 index 97c0439..0000000 --- a/jupyter-spin/docker-entrypoint.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -service rsyslog restart -service nslcd restart - -ip addr - -exec "$@" diff --git a/jupyter-sshspawner/app/Dockerfile b/jupyter-sshspawner/app/Dockerfile index e21dd2f..698d650 100644 --- a/jupyter-sshspawner/app/Dockerfile +++ b/jupyter-sshspawner/app/Dockerfile @@ -1,4 +1,4 @@ -FROM registry.spin.nersc.gov/das/jupyterhub-base:latest +FROM registry.spin.nersc.gov/das/jupyter-base:latest MAINTAINER Rollin Thomas WORKDIR /tmp diff --git a/jupyter-sshspawner/web/Dockerfile b/jupyter-sshspawner/web/Dockerfile index 849e301..1109e07 100644 --- a/jupyter-sshspawner/web/Dockerfile +++ b/jupyter-sshspawner/web/Dockerfile @@ -1,4 +1,4 @@ -FROM registry.spin.nersc.gov/das/jupyterhub-jupyter-dev.deploy-18-10:latest +FROM registry.spin.nersc.gov/das/jupyter-dev-nersc.deploy-18-10:latest MAINTAINER Rollin Thomas # JupyterHub components