Skip to content

Instantly share code, notes, and snippets.

@hcpadkins
Last active January 18, 2022 22:18
Show Gist options
  • Save hcpadkins/9ec4b64c9756a5575b02db7751c7f8b6 to your computer and use it in GitHub Desktop.
Save hcpadkins/9ec4b64c9756a5575b02db7751c7f8b6 to your computer and use it in GitHub Desktop.
JupyterLab start-up helper
#!/bin/bash
#
# NOTE: This script enables the JupterLab Extension Manager and installs extensions.
# There are code-execution risks with this, so make sure you only use trusted
# extensions and you're comfortable with these extensions before running!
#
# This script wrappers the JupterLab Docker container. It defines a few Docker volume
# mounts to ensure that JupyterLab configuration and notebooks are persisted between
# restarts.
#
# Additionally, it defines a requirements.txt which is automatically pip installed at
# container start to ensure that all dependencies are available between restarts, and
# installs JupyterLab extensions at the same time.
#
# Finally, this script also installs and configures GopherNotes for Jupter, enabling
# development of Go lang notebooks.
# If the lab container version changes, plugins and their associated Python dependencies
# may need updating.
LAB_CONTAINER="jupyter/base-notebook:lab-3.1.10"
function wait_for_jupyterlab {
while true ; do
if curl -s -m5 -o /dev/null -f http://127.0.0.1:8888; then
break
fi
echo -n "."
sleep 1
done
}
# Ensure the required directory structure exists.
mkdir -p "${PWD}/jupyterlab/work"
mkdir -p "${PWD}/jupyterlab/configuration"
mkdir -p "${PWD}/jupyterlab/start-up"
# Create the requirements list, if not present.
if [ ! -e "${PWD}/jupyterlab/requirements.txt" ]; then
cat > "${PWD}/jupyterlab/requirements.txt" <<EOF
requests
boto3
jmespath
python-lsp-server[all]
black
isort
jupyterlab-lsp==3.8.1
jupyterlab_code_formatter==1.4.10
EOF
fi
# Create the package bootstrap script, if not present.
if [ ! -e "${PWD}/jupyterlab/start-up/packages.sh" ]; then
cat > "${PWD}/jupyterlab/start-up/packages.sh" <<EOF
#!/bin/sh
pip install -r /home/jupyter/requirements.txt
jupyter labextension install @ryantam626/[email protected]
jupyter labextension install @krassowski/[email protected]
EOF
chmod a+x "${PWD}/jupyterlab/start-up/packages.sh"
fi
# Create the GopherNotes bootstrap script, if not present.
if [ ! -e "${PWD}/jupyterlab/start-up/gophernotes.sh" ]; then
cat > "${PWD}/jupyterlab/start-up/gophernotes.sh" <<'EOF'
#!/bin/sh
JUPYTER_USER="jovyan"
JUPYTER_PATH="/home/${JUPYTER_USER}"
# Root is unfortunately required due to these dependencies. start-notebook should
# drop the user into NB_USER when started however.
apt update -y && apt install -y build-essential
# Fetch and install Go.
wget -qO /tmp/go1.17.6.linux-amd64.tar.gz https://go.dev/dl/go1.17.6.linux-amd64.tar.gz
tar -C /usr/local -xf /tmp/go1.17.6.linux-amd64.tar.gz
rm /tmp/go1.17.6.linux-amd64.tar.gz
# Ensure Go is in the user's path right away.
grep -Eqi '^export PATH.*go/bin$' /etc/profile.d/99-golang.sh || \
echo 'export PATH=$PATH:/usr/local/go/bin' >> /etc/profile.d/99-golang.sh
source /etc/profile.d/99-golang.sh
# Ensure the kernels path exists.
mkdir -p "${JUPYTER_PATH}/.local/share/jupyter/kernels/gophernotes"
# Install GopherNotes.
go install github.com/gopherdata/[email protected]
cp "$(go env GOPATH)"/pkg/mod/github.com/gopherdata/[email protected]/kernel/* \
"${JUPYTER_PATH}/.local/share/jupyter/kernels/gophernotes/"
chmod 660 "${JUPYTER_PATH}/.local/share/jupyter/kernels/gophernotes/kernel.json"
# Fix path to GopherNotes.
sed "s|gophernotes|$(go env GOPATH)/bin/gophernotes|" \
< "${JUPYTER_PATH}/.local/share/jupyter/kernels/gophernotes/kernel.json.in" \
> "${JUPYTER_PATH}/.local/share/jupyter/kernels/gophernotes/kernel.json"
# Fix permissions.
chown -R ${JUPYTER_USER}: "${JUPYTER_PATH}/.local/share/jupyter/"
# Clean up.
apt -y remove build-essential && \
apt -y autoremove --purge && \
apt -y clean
EOF
chmod a+x "${PWD}/jupyterlab/start-up/gophernotes.sh"
fi
# Flag whether a restart is required after start-up. This is used as some extensions
# require a full restart of JupterLab before they will function. We set this based on
# the installation of configuration, rather than the plugin itself, for simplicity but
# at the cost of accuracy if no configuration is added for a given plugin.
JL_RECONFIG=0
# Install advanced settings, if not present.
CF_SETTINGS="${PWD}/jupyterlab/configuration/lab/user-settings/@ryantam626/jupyterlab_code_formatter/settings.jupyterlab-settings"
NE_SETTINGS="${PWD}/jupyterlab/configuration/lab/user-settings/@jupyterlab/notebook-extension/tracker.jupyterlab-settings"
EM_SETTINGS="${PWD}/jupyterlab/configuration/lab/user-settings/@jupyterlab/extensionmanager-extension/plugin.jupyterlab-settings"
if [ ! -e "${CF_SETTINGS}" ]; then
mkdir -p "$(dirname "${CF_SETTINGS}")"
JL_RECONFIG=1
cat > "${CF_SETTINGS}" <<EOF
{
"formatOnSave": true,
"preferences": {
"default_formatter": {
"python": ["isort", "black"]
}
}
}
EOF
fi
if [ ! -e "${NE_SETTINGS}" ]; then
mkdir -p "$(dirname "${NE_SETTINGS}")"
JL_RECONFIG=1
cat > "${NE_SETTINGS}" <<EOF
{
"codeCellConfig": {
"rulers": [88]
}
}
EOF
fi
if [ ! -e "${EM_SETTINGS}" ]; then
mkdir -p "$(dirname "${EM_SETTINGS}")"
JL_RECONFIG=1
cat > "${EM_SETTINGS}" <<EOF
{
"disclaimed": true
}
EOF
fi
# No container? Start one.
if ! docker start JupyterLab > /dev/null 2>&1; then
docker run \
-itd \
--name JupyterLab \
--user root \
-p 127.0.0.1:8888:8888 \
-e GRANT_SUDO=yes \
-e NB_USER=jupyter \
-w /home/jupyter/ \
-e CHOWN_HOME=yes \
-e JUPYTER_ENABLE_LAB=yes \
-e RESTARTABLE=yes \
-v"${PWD}/jupyterlab/work":/home/jupyter/work \
-v"${PWD}/jupyterlab/configuration":/home/jupyter/.jupyter \
-v"${PWD}/jupyterlab/requirements.txt":/home/jupyter/requirements.txt \
-v"${PWD}/jupyterlab/start-up":/usr/local/bin/start-notebook.d/ \
"${LAB_CONTAINER}"
fi
# Get token and print to STDOUT.
echo -n 'INFO: Waiting for JupyterLab to start...'
wait_for_jupyterlab
echo "DONE"
# If the JupterLab configuration has been changed, restart the container to make sure
# everything is correct.
if [ $JL_RECONFIG -gt 0 ]; then
echo -n 'INFO: JupterLab restarting due to configuration change...'
docker restart JupyterLab > /dev/null 2>&1
wait_for_jupyterlab
fi
echo "DONE"
# Grab the JupyterLab token / URL.
echo -n 'INFO: Fetching JupyterLab token from logs...'
JUPYTER_URL=$(docker logs JupyterLab | grep -Eio 'http.*?token=[0-9a-f]+' | tail -n 1)
echo "DONE"
# Finally good to go!
echo "INFO: JupyterLab available at ${JUPYTER_URL}"
echo 'INFO: Attempting to open JupyterLab in browser...'
if [ -n "$(which open)" ]; then
open "${JUPYTER_URL}"
else
echo "INFO: No 'open' on this system, not opening browser."
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment