Files
jupyterhub/examples/bootstrap-script
Min RK 5e166970fa easier linting in jupyterhub_config
noqa get_import satisfies most linters
2024-03-11 09:16:02 +01:00
..
2022-10-26 15:44:52 +11:00

Bootstrapping your users

Before spawning a notebook to the user, it could be useful to do some preparation work in a bootstrapping process.

Common use cases are:

Providing writeable storage for LDAP users

Your Jupyterhub is configured to use the LDAPAuthenticator and DockerSpawer.

  • The user has no file directory on the host since you are using LDAP.
  • When a user has no directory and DockerSpawner wants to mount a volume, the spawner will use docker to create a directory. Since the docker daemon is running as root, the generated directory for the volume mount will not be writeable by the jovyan user inside of the container. For the directory to be useful to the user, the permissions on the directory need to be modified for the user to have write access.

Prepopulating Content

Another use would be to copy initial content, such as tutorial files or reference material, into the user's space when a notebook server is newly spawned.

You can define your own bootstrap process by implementing a pre_spawn_hook on any spawner. The Spawner itself is passed as a parameter to your hook and you can easily get the contextual information out of the spawning process.

Similarly, there may be cases where you would like to clean up after a spawner stops. You may implement a post_stop_hook that is always executed after the spawner stops.

If you implement a hook, make sure that it is idempotent. It will be executed every time a notebook server is spawned to the user. That means you should somehow ensure that things which should run only once are not running again and again. For example, before you create a directory, check if it exists.

Bootstrapping examples:

Example #1 - Create a user directory

Create a directory for the user, if none exists


# in jupyterhub_config.py
import os
def create_dir_hook(spawner):
    username = spawner.user.name # get the username
    volume_path = os.path.join('/volumes/jupyterhub', username)
    if not os.path.exists(volume_path):
        # create a directory with umask 0755
        # hub and container user must have the same UID to be writeable
        # still readable by other users on the system
        os.mkdir(volume_path, 0o755)
        # now do whatever you think your user needs
        # ...
        pass

# attach the hook function to the spawner
c.Spawner.pre_spawn_hook = create_dir_hook

Example #2 - Run mkhomedir_helper

Many Linux distributions provide a script that is responsible for user homedir bootstrapping: /sbin/mkhomedir_helper. To make use of it, you can use

def create_dir_hook(spawner):
    username = spawner.user.name
    if not os.path.exists(os.path.join('/volumes/jupyterhub', username)):
        subprocess.call(["sudo", "/sbin/mkhomedir_helper", spawner.user.name])

# attach the hook function to the spawner
c.Spawner.pre_spawn_hook = create_dir_hook

and make sure to add

jupyterhub ALL = (root) NOPASSWD: /sbin/mkhomedir_helper

in a new file in /etc/sudoers.d, or simply in /etc/sudoers.

All new home directories will be created from /etc/skel, so make sure to place any custom homedir-contents in there.

Example #3 - Run a shell script

You can specify a plain ole' shell script (or any other executable) to be run by the bootstrap process.

For example, you can execute a shell script and as first parameter pass the name of the user:


# in jupyterhub_config.py
from subprocess import check_call
import os
def my_script_hook(spawner):
    username = spawner.user.name # get the username
    script = os.path.join(os.path.dirname(__file__), 'bootstrap.sh')
    check_call([script, username])

# attach the hook function to the spawner
c.Spawner.pre_spawn_hook = my_script_hook

Here's an example on what you could do in your shell script. See also /examples/bootstrap-script/

#!/bin/bash

# Bootstrap example script
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.

# - The first parameter for the Bootstrap Script is the USER.
USER=$1
if [ "$USER" == "" ]; then
    exit 1
fi
# ----------------------------------------------------------------------------


# This example script will do the following:
# - create one directory for the user $USER in a BASE_DIRECTORY (see below)
# - create a "tutorials" directory within and download and unzip
#   the PythonDataScienceHandbook from GitHub

# Start the Bootstrap Process
echo "bootstrap process running for user $USER ..."

# Base Directory: All Directories for the user will be below this point
BASE_DIRECTORY=/volumes/jupyterhub/

# User Directory: That's the private directory for the user to be created, if none exists
USER_DIRECTORY=$BASE_DIRECTORY/$USER

if [ -d "$USER_DIRECTORY" ]; then
    echo "...directory for user already exists. skipped"
    exit 0 # all good. nothing to do.
else
    echo "...creating a directory for the user: $USER_DIRECTORY"
    mkdir $USER_DIRECTORY

    echo "...initial content loading for user ..."
    mkdir $USER_DIRECTORY/tutorials
    cd $USER_DIRECTORY/tutorials
    wget https://github.com/jakevdp/PythonDataScienceHandbook/archive/HEAD.zip
    unzip -o HEAD.zip
    rm HEAD.zip
fi

exit 0