diff --git a/docs/tutorials/jupyterhub_jupyverse_deployment.md b/docs/tutorials/jupyterhub_jupyverse_deployment.md new file mode 100644 index 00000000..64d97ef6 --- /dev/null +++ b/docs/tutorials/jupyterhub_jupyverse_deployment.md @@ -0,0 +1,219 @@ +In this tutorial, we will deploy Jupyverse through JupyterHub on a public [OVHcloud](https://www.ovhcloud.com) instance, and allow authentication using a [GitHub](https://github.com) account. + +## OVH setup + +### Create and connect to a public instance + +Let's follow the guide on [Creating and connecting to your first Public Cloud instance](https://help.ovhcloud.com/csm/en-public-cloud-compute-getting-started?id=kb_article_view&sysparm_article=KB0051009). We first need to create SSH keys, so that we can connect to our instance using SSH. Enter in a terminal: + +```console +$ ssh-keygen -b 4096 +Generating public/private rsa key pair. +Enter file in which to save the key (/home/user/.ssh/id_rsa): +``` + +You can hit _Enter_. You are then asked to enter a passphrase, we will need it later. + +The public key can be accessed with: + +```console +$ cat ~/.ssh/id_rsa.pub +``` + +Copy this public key into your clipboard. + +In the OVHcloud Control Panel, click on "Instances" and then "Create an instance". Choose the "B2-7" model, which is a light and general use instance, and click "Next". + +Select a region of you choice and click "Next". + +Select the "Ubuntu 23.04" image and click "Add a key" under "SSH key". Give it a name an paste your public key, then click "Next". Your instance should already be configured, you can click "Next" again. In the network configuration, make sure "Public mode" is checked, and click "Next". Then select your preferred billing period and click "Create an instance". + +Your instance should activate shortly. You can see it has a public IP, something like `1.2.3.4`. Let's connect to the instance using this IP address: + +```console +$ ssh ubuntu@1.2.3.4 +The authenticity of host '1.2.3.4 (1.2.3.4)' can't be established. +ED25519 key fingerprint is SHA256:Q1&tbgX3fp9+7J90zyK0ctuKe1aqPoEY76Qi58uoSnA. +This key is not known by any other names +Are you sure you want to continue connecting (yes/no/[fingerprint])? +``` +Enter "yes", then enter your passphrase. You should now be connected to your instance. + +### Set up the environment + +Let's install [micromamba](https://mamba.readthedocs.io/en/latest/installation.html#micromamba) and configure it: + +```console +$ sudo apt install bzip2 +$ curl -Ls https://micro.mamba.pm/api/micromamba/linux-64/latest | tar -xvj bin/micromamba +$ bin/micromamba shell init --shell bash --root-prefix=~/micromamba +$ exec bash +``` + +Now create a conda environment and install Python and Node.js: + +```console +$ micromamba create -n jupyterhub +$ micromamba activate jupyterhub +$ micromamba install -c conda-forge python nodejs +``` + +And install JupyterHub and Jupyverse: + +```console +$ pip install -e "git+https://github.com/jupyter-server/jupyverse.git@main#egg=jupyverse_api&subdirectory=jupyverse_api" +$ pip install -e "git+https://github.com/davidbrochart/jupyverse.git@auth_jupyterhub#egg=fps_auth_jupyterhub&subdirectory=plugins/auth_jupyterhub" +$ pip install https://github.com/davidbrochart/jupyterhub/archive/jupyverse.zip +$ pip install jupyverse[jupyterlab] +% npm install -g configurable-http-proxy +``` + +Let's create a JupyterHub configuration file under the `jupyterhub` directory: + +```console +$ mkdir jupyterhub +$ cd jupyterhub +$ vim jupyterhub_config.py +``` + +With the following content: + +```py +# jupyterhub_config.py file +c = get_config() + +import os +pjoin = os.path.join + +runtime_dir = os.path.join('/srv/jupyterhub') +ssl_dir = pjoin(runtime_dir, 'ssl') +if not os.path.exists(ssl_dir): + os.makedirs(ssl_dir) + +# Allows multiple single-server per user +c.JupyterHub.allow_named_servers = True + +# https on :443 +c.JupyterHub.port = 443 +c.JupyterHub.ssl_key = pjoin(ssl_dir, 'ssl.key') +c.JupyterHub.ssl_cert = pjoin(ssl_dir, 'ssl.cert') + +# put the JupyterHub cookie secret and state db +# in /var/run/jupyterhub +c.JupyterHub.cookie_secret_file = pjoin(runtime_dir, 'cookie_secret') +c.JupyterHub.db_url = pjoin(runtime_dir, 'jupyterhub.sqlite') +# or `--db=/path/to/jupyterhub.sqlite` on the command-line + +# use GitHub OAuthenticator for local users +c.JupyterHub.authenticator_class = 'oauthenticator.LocalGitHubOAuthenticator' +c.GitHubOAuthenticator.oauth_callback_url = os.environ['OAUTH_CALLBACK_URL'] + +# create system users that don't exist yet +c.LocalAuthenticator.create_system_users = True + +# specify users and admin +c.Authenticator.allowed_users = {'rgbkrk', 'minrk', 'jhamrick'} +c.Authenticator.admin_users = {'jhamrick', 'rgbkrk'} + +# uses the default spawner +# To use a different spawner, uncomment `spawner_class` and set to desired +# spawner (e.g. SudoSpawner). Follow instructions for desired spawner +# configuration. +# c.JupyterHub.spawner_class = 'sudospawner.SudoSpawner' + +# start single-user notebook servers in ~/assignments, +# with ~/assignments/Welcome.ipynb as the default landing page +# this config could also be put in +# /etc/jupyter/jupyter_notebook_config.py +c.Spawner.notebook_dir = '~/assignments' +c.Spawner.args = ['--NotebookApp.default_url=/notebooks/Welcome.ipynb'] +``` + +```console +export GITHUB_CLIENT_ID=github_id +export GITHUB_CLIENT_SECRET=github_secret +export OAUTH_CALLBACK_URL=https://example.com/hub/oauth_callback +export CONFIGPROXY_AUTH_TOKEN=super-secret +# append log output to log file /var/log/jupyterhub.log +jupyterhub -f /etc/jupyterhub/jupyterhub_config.py &>> /var/log/jupyterhub.log +``` + +### Set up HTTPS and NGINX + +For this you will need a domain name, like [https://my.jupyverse.com](https://my.jupyverse.com), that must point to your instance through its IP address. + +We'll use [NGINX and Let's Encrypt](https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/) to manage SSL/TLS certificates. Enter in a terminal: + +```console +$ sudo apt install certbot nginx python3-certbot-nginx +``` + +Create a file at `/etc/nginx/conf.d/my.jupyverse.com.conf` (note that `my.jupyverse.com` is your domain name) with the following content: + +``` +server { + listen 80 default_server; + listen [::]:80 default_server; + root /var/www/html; + server_name my.jupyverse.com; +} +``` + +Save the file, then run this command to verify the syntax of your configuration and restart NGINX: + +```console +$ sudo nginx -t && sudo nginx -s reload +``` + +You may have to remove the _server_ section in `/etc/nginx/sites-enabled/default`, or simply remove this file. Now run the following command to generate certificates with the NGINX plug‑in: + +```console +$ sudo certbot --nginx -d my.jupyverse.com +``` + +After answering a few questions, you should be all set. If you look at `/etc/nginx/conf.d/my.jupyverse.com.conf` again, you should see that it was modified. Add the following `location` sections at the bottom: + +``` +server { + root /var/www/html; + server_name my.jupyverse.com; + + listen [::]:443 ssl ipv6only=on; # managed by Certbot + listen 443 ssl; # managed by Certbot + ssl_certificate /etc/letsencrypt/live/my.jupyverse.com/fullchain.pem; # managed by Certbot + ssl_certificate_key /etc/letsencrypt/live/my.jupyverse.com/privkey.pem; # managed by Certbot + include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot + + location / { + proxy_pass http://localhost:8000; + } + location ~ \/api\/kernels\/.+\/channels { + proxy_pass http://localhost:8000; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + } + location ~ \/terminals\/websocket\/.+ { + proxy_pass http://localhost:8000; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + } + location ~ \/api\/collaboration\/room\/.+ { + proxy_pass http://localhost:8000; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + } + +} +``` + +## Run the server + +```console +export JUPYTERHUB_SINGLEUSER_APP=jupyverse +jupyterhub +``` + +Now open a browser window at [https://my.jupyverse.com](https://my.jupyverse.com), and click "Sign in with GitHub". Enter your credentials and click "Sign in". If you have two-factor authentication enabled on your GitHub account, you may have to approve the request by entering a code e.g. in your mobile phone GitHub application. You should be redirected back to Fief, where you are asked to provide an email to finalize the sign up. It should be pre-filled with your GitHub email. Just click "Finalize sign up". + +After a while, JupyterLab should start. You should see your avatar in the top-right corner. Any other connected user should be visible in the "Collaboration" tab on the left, and if you work on the same notebook, you should see them collaborate live! diff --git a/docs/tutorials/deployment.md b/docs/tutorials/standalone_jupyverse_deployment.md similarity index 97% rename from docs/tutorials/deployment.md rename to docs/tutorials/standalone_jupyverse_deployment.md index a01ce9d9..b1d6bdb9 100644 --- a/docs/tutorials/deployment.md +++ b/docs/tutorials/standalone_jupyverse_deployment.md @@ -1,4 +1,4 @@ -In this tutorial, we will deploy Jupyverse on a public [OVHcloud](https://www.ovhcloud.com) instance using [Fief](https://fief.dev), and allow authentication using a [GitHub](https://github.com) account. +In this tutorial, we will deploy Jupyverse as a standalone server on a public [OVHcloud](https://www.ovhcloud.com) instance using [Fief](https://fief.dev), and allow authentication using a [GitHub](https://github.com) account. ## OVH setup diff --git a/mkdocs.yml b/mkdocs.yml index e350f1ea..460fb9d9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -36,7 +36,7 @@ nav: - usage/multi_user.md - usage/microservices.md - Turorials: - - tutorials/deployment.md + - tutorials/standalone_jupyverse_deployment.md - Plugins: - 'auth': plugins/auth.md - 'contents': plugins/contents.md diff --git a/plugins/auth_jupyterhub/fps_auth_jupyterhub/__init__.py b/plugins/auth_jupyterhub/fps_auth_jupyterhub/__init__.py index d3ec452c..68b94bb6 100644 --- a/plugins/auth_jupyterhub/fps_auth_jupyterhub/__init__.py +++ b/plugins/auth_jupyterhub/fps_auth_jupyterhub/__init__.py @@ -1 +1,3 @@ __version__ = "0.2.0" + +from .launch import launch diff --git a/plugins/auth_jupyterhub/fps_auth_jupyterhub/launch.py b/plugins/auth_jupyterhub/fps_auth_jupyterhub/launch.py new file mode 100644 index 00000000..40031ec1 --- /dev/null +++ b/plugins/auth_jupyterhub/fps_auth_jupyterhub/launch.py @@ -0,0 +1,22 @@ +import os +from urllib.parse import urlparse + + +def launch(): + from jupyverse_api.cli import main as jupyverse + + service_url = os.environ.get("JUPYTERHUB_SERVICE_URL") + url = urlparse(service_url) + try: + return jupyverse.callback( + open_browser=True, + host=url.hostname, + port=url.port, + set_=[ + f"frontend.base_url={url.path}", + f"app.mount_path={url.path}", + ], + disable=[], + ) + except Exception: + return diff --git a/plugins/auth_jupyterhub/pyproject.toml b/plugins/auth_jupyterhub/pyproject.toml index 62b5037b..f659726d 100644 --- a/plugins/auth_jupyterhub/pyproject.toml +++ b/plugins/auth_jupyterhub/pyproject.toml @@ -11,7 +11,7 @@ requires-python = ">=3.8" dependencies = [ "asphalt-sqlalchemy >=5.0.1,<6", "httpx >=0.24.1,<1", - "jupyterhub >=4.0.1,<5", + "jupyterhub >=5,<6", "jupyverse-api >=0.1.2,<1", ]