From d1ff010ab9a01bbb40615ec20ef5a84850351d98 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 17 Oct 2023 11:45:30 +0530 Subject: [PATCH 01/29] Migrate start-notebook.sh to bash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Based on > Stop using bash, haha 👍 from https://github.com/jupyter/docker-stacks/issues/1532. If there's more apetite for this, I'll try to migrate `start.sh` and `start-singleuser.sh` as well - I think they should all be merged together. We can remove the `.sh` suffixes for accuracy, and keep symlinks in so old config still works. Since the shebang is what is used to launch the correct interpreter, the `.sh` doesn't matter. Will help fix https://github.com/jupyter/docker-stacks/issues/1532, as I believe all those things are going to be easier to do from python than bash --- images/base-notebook/start-notebook.sh | 50 ++++++++++++++++++-------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/images/base-notebook/start-notebook.sh b/images/base-notebook/start-notebook.sh index 4f673d22a5..87539991b4 100755 --- a/images/base-notebook/start-notebook.sh +++ b/images/base-notebook/start-notebook.sh @@ -1,22 +1,42 @@ -#!/bin/bash +#!/usr/bin/env python -u # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. +import os +import sys +import shlex -set -e -# The Jupyter command to launch -# JupyterLab by default -DOCKER_STACKS_JUPYTER_CMD="${DOCKER_STACKS_JUPYTER_CMD:=lab}" +# If we are in a JupyterHub, we pass on to `start-singleuser.sh` instead so it does the right thing +if "JUPYTERHUB_API_TOKEN" not in os.environ: + print( + "WARNING: using start-singleuser.sh instead of start-notebook.sh to start a server associated with JupyterHub.", + file=sys.stderr, + ) + os.execvp("/usr/local/bin/start-singleuser.sh", sys.argv[1:]) -if [[ -n "${JUPYTERHUB_API_TOKEN}" ]]; then - echo "WARNING: using start-singleuser.sh instead of start-notebook.sh to start a server associated with JupyterHub." - exec /usr/local/bin/start-singleuser.sh "$@" -fi -wrapper="" -if [[ "${RESTARTABLE}" == "yes" ]]; then - wrapper="run-one-constantly" -fi +# Wrap everything in start.sh, no matter what +command = ["/usr/local/bin/start.sh"] -# shellcheck disable=SC1091,SC2086 -exec /usr/local/bin/start.sh ${wrapper} jupyter ${DOCKER_STACKS_JUPYTER_CMD} ${NOTEBOOK_ARGS} "$@" +# If we want to survive restarts, tell that to start.sh +if os.environ.get("RESTARTABLE") == "yes": + command.append("run-one-constantly") + +# We always launch a jupyter subcommand from this script +command.append("jupyter") + +# Launch the configured subcommand. Note that this should be a single string, so we don't split it +# We default to lab +jupyter_command = os.environ.get("DOCKER_STACKS_JUPYTER_CMD", "lab") +command.append(jupyter_command) + +# Append any optional NOTEBOOK_ARGS we were passed in. This is supposed to be multiple args passed +# on to the notebook command, so we split it correctly with shlex +command += ( + [] + if "NOTEBOOK_ARGS" not in os.environ + else shlex.split(os.environ["NOTEBOOK_ARGS"]) +) + +# Execute the command! +os.execvp(command[0], command) From ed704632262fa204d00a1437ca7fb06949d7b25d Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 17 Oct 2023 11:58:25 +0530 Subject: [PATCH 02/29] Rename start-notebook.sh to start-notebook --- README.md | 2 +- docs/using/common.md | 18 +++++++++--------- docs/using/recipes.md | 6 +++--- docs/using/running.md | 2 +- docs/using/selecting.md | 2 +- .../notebook/letsencrypt-notebook.yml | 2 +- examples/make-deploy/Makefile | 2 +- examples/openshift/templates.json | 2 +- examples/source-to-image/README.md | 2 +- examples/source-to-image/run | 2 +- examples/source-to-image/templates.json | 2 +- images/base-notebook/Dockerfile | 7 +++++-- .../{start-notebook.sh => start-notebook} | 5 ++--- tests/base-notebook/test_container_options.py | 4 ++-- tests/base-notebook/test_healthcheck.py | 18 +++++++++--------- tests/pluto_check.py | 2 +- 16 files changed, 40 insertions(+), 38 deletions(-) rename images/base-notebook/{start-notebook.sh => start-notebook} (95%) diff --git a/README.md b/README.md index ada720e289..cee2b5a981 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ system when the container exits, but any changes made to the `~/work` directory By default, [jupyter's root_dir](https://jupyter-server.readthedocs.io/en/latest/other/full-config.html) is `/home/jovyan`. So, new notebooks will be saved there, unless you change the directory in the file browser. -To change the default directory, you will need to specify `ServerApp.root_dir` by adding this line to previous command: `start-notebook.sh --ServerApp.root_dir=/home/jovyan/work`. +To change the default directory, you will need to specify `ServerApp.root_dir` by adding this line to previous command: `start-notebook --ServerApp.root_dir=/home/jovyan/work`. ``` ## Contributing diff --git a/docs/using/common.md b/docs/using/common.md index dab66fed72..4172c88629 100644 --- a/docs/using/common.md +++ b/docs/using/common.md @@ -1,14 +1,14 @@ # Common Features Except for `jupyter/docker-stacks-foundation`, a container launched from any Jupyter Docker Stacks image runs a Jupyter Server with the JupyterLab frontend. -The container does so by executing a `start-notebook.sh` script. +The container does so by executing a `start-notebook` script. This script configures the internal container environment and then runs `jupyter lab`, passing any command-line arguments received. This page describes the options supported by the startup script and how to bypass it to run alternative commands. ## Jupyter Server Options -You can pass [Jupyter Server options](https://jupyter-server.readthedocs.io/en/latest/operators/public-server.html) to the `start-notebook.sh` script when launching the container. +You can pass [Jupyter Server options](https://jupyter-server.readthedocs.io/en/latest/operators/public-server.html) to the `start-notebook` script when launching the container. 1. For example, to secure the Jupyter Server with a [custom password](https://jupyter-server.readthedocs.io/en/latest/operators/public-server.html#preparing-a-hashed-password) hashed using `jupyter_server.auth.passwd()` instead of the default token, @@ -16,19 +16,19 @@ You can pass [Jupyter Server options](https://jupyter-server.readthedocs.io/en/l ```bash docker run -it --rm -p 8888:8888 jupyter/base-notebook \ - start-notebook.sh --PasswordIdentityProvider.hashed_password='argon2:$argon2id$v=19$m=10240,t=10,p=8$JdAN3fe9J45NvK/EPuGCvA$O/tbxglbwRpOFuBNTYrymAEH6370Q2z+eS1eF4GM6Do' + start-notebook --PasswordIdentityProvider.hashed_password='argon2:$argon2id$v=19$m=10240,t=10,p=8$JdAN3fe9J45NvK/EPuGCvA$O/tbxglbwRpOFuBNTYrymAEH6370Q2z+eS1eF4GM6Do' ``` 2. To set the [base URL](https://jupyter-server.readthedocs.io/en/latest/operators/public-server.html#running-the-notebook-with-a-customized-url-prefix) of the Jupyter Server, you can run the following: ```bash docker run -it --rm -p 8888:8888 jupyter/base-notebook \ - start-notebook.sh --ServerApp.base_url=/customized/url/prefix/ + start-notebook --ServerApp.base_url=/customized/url/prefix/ ``` ## Docker Options -You may instruct the `start-notebook.sh` script to customize the container environment before launching the Server. +You may instruct the `start-notebook` script to customize the container environment before launching the Server. You do so by passing arguments to the `docker run` command. ### User-related configurations @@ -104,7 +104,7 @@ You do so by passing arguments to the `docker run` command. You do **not** need this option to allow the user to `conda` or `pip` install additional packages. This option is helpful for cases when you wish to give `${NB_USER}` the ability to install OS packages with `apt` or modify other root-owned files in the container. You **must** run the container with `--user root` for this option to take effect. - (The `start-notebook.sh` script will `su ${NB_USER}` after adding `${NB_USER}` to sudoers.) + (The `start-notebook` script will `su ${NB_USER}` after adding `${NB_USER}` to sudoers.) **You should only enable `sudo` if you trust the user or if the container runs on an isolated host.** ### Additional runtime configurations @@ -147,7 +147,7 @@ For example, to mount a host folder containing a `notebook.key` and `notebook.cr docker run -it --rm -p 8888:8888 \ -v /some/host/folder:/etc/ssl/notebook \ jupyter/base-notebook \ - start-notebook.sh \ + start-notebook \ --ServerApp.keyfile=/etc/ssl/notebook/notebook.key \ --ServerApp.certfile=/etc/ssl/notebook/notebook.crt ``` @@ -159,7 +159,7 @@ For example: docker run -it --rm -p 8888:8888 \ -v /some/host/folder/notebook.pem:/etc/ssl/notebook.pem \ jupyter/base-notebook \ - start-notebook.sh \ + start-notebook \ --ServerApp.certfile=/etc/ssl/notebook.pem ``` @@ -220,7 +220,7 @@ docker run -it --rm \ ### `start.sh` -The `start-notebook.sh` script inherits most of its option handling capability from a more generic `start.sh` script. +The `start-notebook` script inherits most of its option handling capability from a more generic `start.sh` script. The `start.sh` script supports all the features described above but allows you to specify an arbitrary command to execute. For example, to run the text-based `ipython` console in a container, do the following: diff --git a/docs/using/recipes.md b/docs/using/recipes.md index cfeb4cedf5..2a457f86d1 100644 --- a/docs/using/recipes.md +++ b/docs/using/recipes.md @@ -375,14 +375,14 @@ Credit: [britishbadger](https://github.com/britishbadger) from [docker-stacks/is The default security is very good. There are use cases, encouraged by containers, where the jupyter container and the system it runs within lie inside the security boundary. It is convenient to launch the server without a password or token in these use cases. -In this case, you should use the `start-notebook.sh` script to launch the server with no token: +In this case, you should use the `start-notebook` script to launch the server with no token: For JupyterLab: ```bash docker run -it --rm \ jupyter/base-notebook \ - start-notebook.sh --IdentityProvider.token='' + start-notebook --IdentityProvider.token='' ``` For Jupyter Notebook: @@ -391,7 +391,7 @@ For Jupyter Notebook: docker run -it --rm \ -e DOCKER_STACKS_JUPYTER_CMD=notebook \ jupyter/base-notebook \ - start-notebook.sh --IdentityProvider.token='' + start-notebook --IdentityProvider.token='' ``` ## Enable nbclassic-extension spellchecker for markdown (or any other nbclassic-extension) diff --git a/docs/using/running.md b/docs/using/running.md index 4e764184ce..ad70c16f2b 100644 --- a/docs/using/running.md +++ b/docs/using/running.md @@ -69,7 +69,7 @@ Any other changes made in the container will be lost. By default, [jupyter's root_dir](https://jupyter-server.readthedocs.io/en/latest/other/full-config.html) is `/home/jovyan`. So, new notebooks will be saved there, unless you change the directory in the file browser. -To change the default directory, you will need to specify `ServerApp.root_dir` by adding this line to previous command: `start-notebook.sh --ServerApp.root_dir=/home/jovyan/work`. +To change the default directory, you will need to specify `ServerApp.root_dir` by adding this line to previous command: `start-notebook --ServerApp.root_dir=/home/jovyan/work`. ``` ### Example 3 diff --git a/docs/using/selecting.md b/docs/using/selecting.md index 450e463a8c..cf7e465b9d 100644 --- a/docs/using/selecting.md +++ b/docs/using/selecting.md @@ -56,7 +56,7 @@ It contains: - Everything in `jupyter/docker-stacks-foundation` - Minimally functional Server (e.g., no LaTeX support for saving notebooks as PDFs) - `notebook`, `jupyterhub` and `jupyterlab` packages -- A `start-notebook.sh` script as the default command +- A `start-notebook` script as the default command - A `start-singleuser.sh` script useful for launching containers in JupyterHub - Options for a self-signed HTTPS certificate diff --git a/examples/docker-compose/notebook/letsencrypt-notebook.yml b/examples/docker-compose/notebook/letsencrypt-notebook.yml index 1c47c99e0f..af52f16261 100644 --- a/examples/docker-compose/notebook/letsencrypt-notebook.yml +++ b/examples/docker-compose/notebook/letsencrypt-notebook.yml @@ -18,7 +18,7 @@ services: USE_HTTPS: "yes" PASSWORD: ${PASSWORD} command: > - start-notebook.sh + start-notebook --ServerApp.certfile=/etc/letsencrypt/fullchain.pem --ServerApp.keyfile=/etc/letsencrypt/privkey.pem diff --git a/examples/make-deploy/Makefile b/examples/make-deploy/Makefile index e937621c60..1c926fb678 100644 --- a/examples/make-deploy/Makefile +++ b/examples/make-deploy/Makefile @@ -13,7 +13,7 @@ define RUN_NOTEBOOK --name $(NAME) \ -v $(WORK_VOLUME):/home/jovyan/work \ $(DOCKER_ARGS) \ - $(IMAGE) bash -c "$(PRE_CMD) chown jovyan /home/jovyan/work && start-notebook.sh $(ARGS)" > /dev/null + $(IMAGE) bash -c "$(PRE_CMD) chown jovyan /home/jovyan/work && start-notebook $(ARGS)" > /dev/null @echo "DONE: Notebook '$(NAME)' listening on $$(docker-machine ip $$(docker-machine active)):$(PORT)" endef diff --git a/examples/openshift/templates.json b/examples/openshift/templates.json index 6c48e129be..adfb18c2af 100644 --- a/examples/openshift/templates.json +++ b/examples/openshift/templates.json @@ -80,7 +80,7 @@ "name": "jupyter-notebook", "image": "${NOTEBOOK_IMAGE}", "command": [ - "start-notebook.sh", + "start-notebook", "--config=/etc/jupyter/openshift/jupyter_server_config.py", "--no-browser", "--ip=0.0.0.0" diff --git a/examples/source-to-image/README.md b/examples/source-to-image/README.md index 1e9d3ba992..6513dbfa94 100644 --- a/examples/source-to-image/README.md +++ b/examples/source-to-image/README.md @@ -117,7 +117,7 @@ with the extra system packages, and then use that image with the S2I build to co The `run` script in this directory is very simple and just runs the notebook application. ```bash -exec start-notebook.sh "$@" +exec start-notebook "$@" ``` ## Integration with OpenShift diff --git a/examples/source-to-image/run b/examples/source-to-image/run index b5b641b8f6..b2a67c0f02 100755 --- a/examples/source-to-image/run +++ b/examples/source-to-image/run @@ -2,4 +2,4 @@ # Start up the notebook instance. -exec start-notebook.sh "$@" +exec start-notebook "$@" diff --git a/examples/source-to-image/templates.json b/examples/source-to-image/templates.json index e335fc6877..b069c88a86 100644 --- a/examples/source-to-image/templates.json +++ b/examples/source-to-image/templates.json @@ -274,7 +274,7 @@ "name": "jupyter-notebook", "image": "${APPLICATION_NAME}:latest", "command": [ - "start-notebook.sh", + "start-notebook", "--config=/etc/jupyter/openshift/jupyter_server_config.py", "--no-browser", "--ip=0.0.0.0" diff --git a/images/base-notebook/Dockerfile b/images/base-notebook/Dockerfile index 5f47a5d425..0f57e52d41 100644 --- a/images/base-notebook/Dockerfile +++ b/images/base-notebook/Dockerfile @@ -52,12 +52,15 @@ ENV JUPYTER_PORT=8888 EXPOSE $JUPYTER_PORT # Configure container startup -CMD ["start-notebook.sh"] +CMD ["start-notebook"] # Copy local files as late as possible to avoid cache busting -COPY start-notebook.sh start-singleuser.sh /usr/local/bin/ +COPY start-notebook start-singleuser.sh /usr/local/bin/ COPY jupyter_server_config.py docker_healthcheck.py /etc/jupyter/ +# Symlink start-notebook to start-notebook for backwards compatibility +RUN ln -s /usr/local/bin/start-notebook /usr/local/bin/start-notebook + # Fix permissions on /etc/jupyter as root USER root RUN fix-permissions /etc/jupyter/ diff --git a/images/base-notebook/start-notebook.sh b/images/base-notebook/start-notebook similarity index 95% rename from images/base-notebook/start-notebook.sh rename to images/base-notebook/start-notebook index 87539991b4..eca68a05b8 100755 --- a/images/base-notebook/start-notebook.sh +++ b/images/base-notebook/start-notebook @@ -2,14 +2,13 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. import os -import sys import shlex - +import sys # If we are in a JupyterHub, we pass on to `start-singleuser.sh` instead so it does the right thing if "JUPYTERHUB_API_TOKEN" not in os.environ: print( - "WARNING: using start-singleuser.sh instead of start-notebook.sh to start a server associated with JupyterHub.", + "WARNING: using start-singleuser.sh instead of start-notebook to start a server associated with JupyterHub.", file=sys.stderr, ) os.execvp("/usr/local/bin/start-singleuser.sh", sys.argv[1:]) diff --git a/tests/base-notebook/test_container_options.py b/tests/base-notebook/test_container_options.py index 5fa28d8913..d9fd206796 100644 --- a/tests/base-notebook/test_container_options.py +++ b/tests/base-notebook/test_container_options.py @@ -15,7 +15,7 @@ def test_cli_args(container: TrackedContainer, http_client: requests.Session) -> """Image should respect command line args (e.g., disabling token security)""" host_port = find_free_port() running_container = container.run_detached( - command=["start-notebook.sh", "--IdentityProvider.token=''"], + command=["start-notebook", "--IdentityProvider.token=''"], ports={"8888/tcp": host_port}, ) resp = http_client.get(f"http://localhost:{host_port}") @@ -102,7 +102,7 @@ def test_custom_internal_port( host_port = find_free_port() internal_port = env.get("JUPYTER_PORT", 8888) running_container = container.run_detached( - command=["start-notebook.sh", "--IdentityProvider.token=''"], + command=["start-notebook", "--IdentityProvider.token=''"], environment=env, ports={internal_port: host_port}, ) diff --git a/tests/base-notebook/test_healthcheck.py b/tests/base-notebook/test_healthcheck.py index 73de100303..40af5203e8 100644 --- a/tests/base-notebook/test_healthcheck.py +++ b/tests/base-notebook/test_healthcheck.py @@ -22,23 +22,23 @@ (["RESTARTABLE=yes"], None, None), (["JUPYTER_PORT=8171"], None, None), (["JUPYTER_PORT=8117", "DOCKER_STACKS_JUPYTER_CMD=notebook"], None, None), - (None, ["start-notebook.sh", "--ServerApp.base_url=/test"], None), - (None, ["start-notebook.sh", "--ServerApp.base_url=/test/"], None), - (["GEN_CERT=1"], ["start-notebook.sh", "--ServerApp.base_url=/test"], None), + (None, ["start-notebook", "--ServerApp.base_url=/test"], None), + (None, ["start-notebook", "--ServerApp.base_url=/test/"], None), + (["GEN_CERT=1"], ["start-notebook", "--ServerApp.base_url=/test"], None), ( ["GEN_CERT=1", "JUPYTER_PORT=7891"], - ["start-notebook.sh", "--ServerApp.base_url=/test"], + ["start-notebook", "--ServerApp.base_url=/test"], None, ), (["NB_USER=testuser", "CHOWN_HOME=1"], None, "root"), ( ["NB_USER=testuser", "CHOWN_HOME=1"], - ["start-notebook.sh", "--ServerApp.base_url=/test"], + ["start-notebook", "--ServerApp.base_url=/test"], "root", ), ( ["NB_USER=testuser", "CHOWN_HOME=1", "JUPYTER_PORT=8123"], - ["start-notebook.sh", "--ServerApp.base_url=/test"], + ["start-notebook", "--ServerApp.base_url=/test"], "root", ), ], @@ -85,7 +85,7 @@ def test_health( "HTTPS_PROXY=host.docker.internal", "HTTP_PROXY=host.docker.internal", ], - ["start-notebook.sh", "--ServerApp.base_url=/test"], + ["start-notebook", "--ServerApp.base_url=/test"], "root", ), ], @@ -122,12 +122,12 @@ def test_health_proxy( (["NB_USER=testuser", "CHOWN_HOME=1"], None, None), ( ["NB_USER=testuser", "CHOWN_HOME=1"], - ["start-notebook.sh", "--ServerApp.base_url=/test"], + ["start-notebook", "--ServerApp.base_url=/test"], None, ), ( ["NB_USER=testuser", "CHOWN_HOME=1", "JUPYTER_PORT=8123"], - ["start-notebook.sh", "--ServerApp.base_url=/test"], + ["start-notebook", "--ServerApp.base_url=/test"], None, ), ], diff --git a/tests/pluto_check.py b/tests/pluto_check.py index 5fc269de2e..7b7705adf3 100644 --- a/tests/pluto_check.py +++ b/tests/pluto_check.py @@ -18,7 +18,7 @@ def check_pluto_proxy( token = secrets.token_hex() container.run_detached( command=[ - "start-notebook.sh", + "start-notebook", f"--IdentityProvider.token={token}", ], ports={"8888/tcp": host_port}, From 00e751ec70d29738b111fb59a3db5425d5ab2123 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 17 Oct 2023 11:59:37 +0530 Subject: [PATCH 03/29] Cleanup start-notebook a little --- images/base-notebook/start-notebook | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/images/base-notebook/start-notebook b/images/base-notebook/start-notebook index eca68a05b8..b908f6c55c 100755 --- a/images/base-notebook/start-notebook +++ b/images/base-notebook/start-notebook @@ -31,11 +31,8 @@ command.append(jupyter_command) # Append any optional NOTEBOOK_ARGS we were passed in. This is supposed to be multiple args passed # on to the notebook command, so we split it correctly with shlex -command += ( - [] - if "NOTEBOOK_ARGS" not in os.environ - else shlex.split(os.environ["NOTEBOOK_ARGS"]) -) +if "NOTEBOOK_ARGS" in os.environ: + command += shlex.split(os.environ["NOTEBOOK_ARGS"]) # Execute the command! os.execvp(command[0], command) From ea06768c45a6982c6dbc77483c69b4a9ee879aca Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 17 Oct 2023 12:05:55 +0530 Subject: [PATCH 04/29] Fix typo --- images/base-notebook/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/base-notebook/Dockerfile b/images/base-notebook/Dockerfile index 0f57e52d41..d888bbc31f 100644 --- a/images/base-notebook/Dockerfile +++ b/images/base-notebook/Dockerfile @@ -59,7 +59,7 @@ COPY start-notebook start-singleuser.sh /usr/local/bin/ COPY jupyter_server_config.py docker_healthcheck.py /etc/jupyter/ # Symlink start-notebook to start-notebook for backwards compatibility -RUN ln -s /usr/local/bin/start-notebook /usr/local/bin/start-notebook +RUN ln -s /usr/local/bin/start-notebook /usr/local/bin/start-notebook.sh # Fix permissions on /etc/jupyter as root USER root From 14a1e8c191f24b7d1bee2cf53db2451f228808ab Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 17 Oct 2023 12:10:42 +0530 Subject: [PATCH 05/29] Migrate start-singleuser as well --- docs/using/selecting.md | 2 +- images/base-notebook/Dockerfile | 5 +++-- images/base-notebook/start-notebook | 6 +++--- images/base-notebook/start-singleuser | 20 ++++++++++++++++++++ images/base-notebook/start-singleuser.sh | 13 ------------- tests/base-notebook/test_start_container.py | 2 +- 6 files changed, 28 insertions(+), 20 deletions(-) create mode 100755 images/base-notebook/start-singleuser delete mode 100755 images/base-notebook/start-singleuser.sh diff --git a/docs/using/selecting.md b/docs/using/selecting.md index cf7e465b9d..968efa61e0 100644 --- a/docs/using/selecting.md +++ b/docs/using/selecting.md @@ -57,7 +57,7 @@ It contains: - Minimally functional Server (e.g., no LaTeX support for saving notebooks as PDFs) - `notebook`, `jupyterhub` and `jupyterlab` packages - A `start-notebook` script as the default command -- A `start-singleuser.sh` script useful for launching containers in JupyterHub +- A `start-singleuser` script useful for launching containers in JupyterHub - Options for a self-signed HTTPS certificate ### jupyter/minimal-notebook diff --git a/images/base-notebook/Dockerfile b/images/base-notebook/Dockerfile index d888bbc31f..35387917ef 100644 --- a/images/base-notebook/Dockerfile +++ b/images/base-notebook/Dockerfile @@ -55,11 +55,12 @@ EXPOSE $JUPYTER_PORT CMD ["start-notebook"] # Copy local files as late as possible to avoid cache busting -COPY start-notebook start-singleuser.sh /usr/local/bin/ +COPY start-notebook start-singleuser /usr/local/bin/ COPY jupyter_server_config.py docker_healthcheck.py /etc/jupyter/ -# Symlink start-notebook to start-notebook for backwards compatibility +# Symlink scripts to their older .sh variants for backwards compatibility RUN ln -s /usr/local/bin/start-notebook /usr/local/bin/start-notebook.sh +RUN ln -s /usr/local/bin/start-jupyterhub /usr/local/bin/start-jupyterhub.sh # Fix permissions on /etc/jupyter as root USER root diff --git a/images/base-notebook/start-notebook b/images/base-notebook/start-notebook index b908f6c55c..25d520bbd7 100755 --- a/images/base-notebook/start-notebook +++ b/images/base-notebook/start-notebook @@ -5,13 +5,13 @@ import os import shlex import sys -# If we are in a JupyterHub, we pass on to `start-singleuser.sh` instead so it does the right thing +# If we are in a JupyterHub, we pass on to `start-singleuser` instead so it does the right thing if "JUPYTERHUB_API_TOKEN" not in os.environ: print( - "WARNING: using start-singleuser.sh instead of start-notebook to start a server associated with JupyterHub.", + "WARNING: using start-singleuser instead of start-notebook to start a server associated with JupyterHub.", file=sys.stderr, ) - os.execvp("/usr/local/bin/start-singleuser.sh", sys.argv[1:]) + os.execvp("/usr/local/bin/start-singleuser", sys.argv[1:]) # Wrap everything in start.sh, no matter what diff --git a/images/base-notebook/start-singleuser b/images/base-notebook/start-singleuser new file mode 100755 index 0000000000..aec1c888fe --- /dev/null +++ b/images/base-notebook/start-singleuser @@ -0,0 +1,20 @@ +#!/usr/bin/env python -u +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. +import os +import shlex +import sys + +command = ["/usr/local/bin/start.sh"] + +# set default ip to 0.0.0.0 +if "--ip=" not in os.environ.get("NOTEBOOK_ARGS", ""): + command.append("--ip=0.0.0.0") + +# Append any optional NOTEBOOK_ARGS we were passed in. This is supposed to be multiple args passed +# on to the notebook command, so we split it correctly with shlex +if "NOTEBOOK_ARGS" in os.environ: + command += shlex.split(os.environ["NOTEBOOK_ARGS"]) + +# Execute the command! +os.execvp(command[0], command) diff --git a/images/base-notebook/start-singleuser.sh b/images/base-notebook/start-singleuser.sh deleted file mode 100755 index a2166e2c6d..0000000000 --- a/images/base-notebook/start-singleuser.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -# Copyright (c) Jupyter Development Team. -# Distributed under the terms of the Modified BSD License. - -set -e - -# set default ip to 0.0.0.0 -if [[ "${NOTEBOOK_ARGS} $*" != *"--ip="* ]]; then - NOTEBOOK_ARGS="--ip=0.0.0.0 ${NOTEBOOK_ARGS}" -fi - -# shellcheck disable=SC1091,SC2086 -. /usr/local/bin/start.sh jupyterhub-singleuser ${NOTEBOOK_ARGS} "$@" diff --git a/tests/base-notebook/test_start_container.py b/tests/base-notebook/test_start_container.py index 556f4c2c7c..6ca781775f 100644 --- a/tests/base-notebook/test_start_container.py +++ b/tests/base-notebook/test_start_container.py @@ -25,7 +25,7 @@ ["JUPYTERHUB_API_TOKEN=my_token"], "jupyterhub-singleuser", False, - ["WARNING: using start-singleuser.sh"], + ["WARNING: using start-singleuser"], ), ], ) From 40f37d7968b2c1f07cc89dd95edf3f36b25b0479 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 17 Oct 2023 12:12:42 +0530 Subject: [PATCH 06/29] Remove unused import --- images/base-notebook/start-singleuser | 1 - 1 file changed, 1 deletion(-) diff --git a/images/base-notebook/start-singleuser b/images/base-notebook/start-singleuser index aec1c888fe..c3760c6a74 100755 --- a/images/base-notebook/start-singleuser +++ b/images/base-notebook/start-singleuser @@ -3,7 +3,6 @@ # Distributed under the terms of the Modified BSD License. import os import shlex -import sys command = ["/usr/local/bin/start.sh"] From 33642945ae7095272d731a11f90d6d0c6e85d018 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 17 Oct 2023 12:21:18 +0530 Subject: [PATCH 07/29] Run symlink commands as root --- images/base-notebook/Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/images/base-notebook/Dockerfile b/images/base-notebook/Dockerfile index 35387917ef..66f8e9a572 100644 --- a/images/base-notebook/Dockerfile +++ b/images/base-notebook/Dockerfile @@ -58,14 +58,14 @@ CMD ["start-notebook"] COPY start-notebook start-singleuser /usr/local/bin/ COPY jupyter_server_config.py docker_healthcheck.py /etc/jupyter/ -# Symlink scripts to their older .sh variants for backwards compatibility -RUN ln -s /usr/local/bin/start-notebook /usr/local/bin/start-notebook.sh -RUN ln -s /usr/local/bin/start-jupyterhub /usr/local/bin/start-jupyterhub.sh - # Fix permissions on /etc/jupyter as root USER root RUN fix-permissions /etc/jupyter/ +# Symlink scripts to their older .sh variants for backwards compatibility +RUN ln -s /usr/local/bin/start-notebook /usr/local/bin/start-notebook.sh +RUN ln -s /usr/local/bin/start-jupyterhub /usr/local/bin/start-jupyterhub.sh + # HEALTHCHECK documentation: https://docs.docker.com/engine/reference/builder/#healthcheck # This healtcheck works well for `lab`, `notebook`, `nbclassic`, `server` and `retro` jupyter commands # https://github.com/jupyter/docker-stacks/issues/915#issuecomment-1068528799 From bb98d5e5c4c6124cac73331ee8f310a1e8b3032c Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 17 Oct 2023 12:25:45 +0530 Subject: [PATCH 08/29] Combine repetitive RUN commands --- images/base-notebook/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/images/base-notebook/Dockerfile b/images/base-notebook/Dockerfile index 66f8e9a572..0c6d429495 100644 --- a/images/base-notebook/Dockerfile +++ b/images/base-notebook/Dockerfile @@ -63,8 +63,8 @@ USER root RUN fix-permissions /etc/jupyter/ # Symlink scripts to their older .sh variants for backwards compatibility -RUN ln -s /usr/local/bin/start-notebook /usr/local/bin/start-notebook.sh -RUN ln -s /usr/local/bin/start-jupyterhub /usr/local/bin/start-jupyterhub.sh +RUN ln -s /usr/local/bin/start-notebook /usr/local/bin/start-notebook.sh && \ + ln -s /usr/local/bin/start-jupyterhub /usr/local/bin/start-jupyterhub.sh # HEALTHCHECK documentation: https://docs.docker.com/engine/reference/builder/#healthcheck # This healtcheck works well for `lab`, `notebook`, `nbclassic`, `server` and `retro` jupyter commands From 9e718398c80d150e84b1ce4fb463f6c7b46f9630 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 17 Oct 2023 12:43:36 +0530 Subject: [PATCH 09/29] Remove multiple args to env -u can not be set by shebang, we must set the env var instead --- images/base-notebook/start-notebook | 2 +- images/base-notebook/start-singleuser | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/images/base-notebook/start-notebook b/images/base-notebook/start-notebook index 25d520bbd7..af6edac4f0 100755 --- a/images/base-notebook/start-notebook +++ b/images/base-notebook/start-notebook @@ -1,4 +1,4 @@ -#!/usr/bin/env python -u +#!/usr/bin/env python # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. import os diff --git a/images/base-notebook/start-singleuser b/images/base-notebook/start-singleuser index c3760c6a74..0597921fd9 100755 --- a/images/base-notebook/start-singleuser +++ b/images/base-notebook/start-singleuser @@ -1,4 +1,4 @@ -#!/usr/bin/env python -u +#!/usr/bin/env python # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. import os From 67302e350c6019f1fbe1d40da835e7f8e1496251 Mon Sep 17 00:00:00 2001 From: Yuvi Panda Date: Tue, 17 Oct 2023 15:27:27 +0530 Subject: [PATCH 10/29] Fix conditional inversion Co-authored-by: Ayaz Salikhov --- images/base-notebook/start-notebook | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/base-notebook/start-notebook b/images/base-notebook/start-notebook index af6edac4f0..ac07e8cb6c 100755 --- a/images/base-notebook/start-notebook +++ b/images/base-notebook/start-notebook @@ -6,7 +6,7 @@ import shlex import sys # If we are in a JupyterHub, we pass on to `start-singleuser` instead so it does the right thing -if "JUPYTERHUB_API_TOKEN" not in os.environ: +if "JUPYTERHUB_API_TOKEN" in os.environ: print( "WARNING: using start-singleuser instead of start-notebook to start a server associated with JupyterHub.", file=sys.stderr, From 1804a8c5ea800461eab35357ae54af1dbc57027e Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 17 Oct 2023 15:38:57 +0530 Subject: [PATCH 11/29] Fix how start-singleuser is exec'd --- images/base-notebook/start-notebook | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/images/base-notebook/start-notebook b/images/base-notebook/start-notebook index ac07e8cb6c..cc084b5fa5 100755 --- a/images/base-notebook/start-notebook +++ b/images/base-notebook/start-notebook @@ -11,7 +11,8 @@ if "JUPYTERHUB_API_TOKEN" in os.environ: "WARNING: using start-singleuser instead of start-notebook to start a server associated with JupyterHub.", file=sys.stderr, ) - os.execvp("/usr/local/bin/start-singleuser", sys.argv[1:]) + command = ["/usr/local/bin/start-singleuser"] + sys.argv[1:] + os.execvp(command[0], command) # Wrap everything in start.sh, no matter what From 527b7564c1d6c318cfcd56a0ae23cd5365499ca4 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 17 Oct 2023 15:59:04 +0530 Subject: [PATCH 12/29] Actually call jupyterhub-singleuser in start-singleuser --- images/base-notebook/start-singleuser | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/base-notebook/start-singleuser b/images/base-notebook/start-singleuser index 0597921fd9..c5a6061298 100755 --- a/images/base-notebook/start-singleuser +++ b/images/base-notebook/start-singleuser @@ -4,7 +4,7 @@ import os import shlex -command = ["/usr/local/bin/start.sh"] +command = ["/usr/local/bin/start.sh", "jupyterhub-singleuser"] # set default ip to 0.0.0.0 if "--ip=" not in os.environ.get("NOTEBOOK_ARGS", ""): From 9f7bdb391e7742afb4497285c874d9280d83aa13 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 17 Oct 2023 15:59:37 +0530 Subject: [PATCH 13/29] Pass through any additional args we get --- images/base-notebook/start-notebook | 3 +++ images/base-notebook/start-singleuser | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/images/base-notebook/start-notebook b/images/base-notebook/start-notebook index cc084b5fa5..6ec8c3ca96 100755 --- a/images/base-notebook/start-notebook +++ b/images/base-notebook/start-notebook @@ -35,5 +35,8 @@ command.append(jupyter_command) if "NOTEBOOK_ARGS" in os.environ: command += shlex.split(os.environ["NOTEBOOK_ARGS"]) +# Pass through any other args we were passed on the commandline +command += sys.argv[1:] + # Execute the command! os.execvp(command[0], command) diff --git a/images/base-notebook/start-singleuser b/images/base-notebook/start-singleuser index c5a6061298..2dcf6c091c 100755 --- a/images/base-notebook/start-singleuser +++ b/images/base-notebook/start-singleuser @@ -3,6 +3,7 @@ # Distributed under the terms of the Modified BSD License. import os import shlex +import sys command = ["/usr/local/bin/start.sh", "jupyterhub-singleuser"] @@ -15,5 +16,8 @@ if "--ip=" not in os.environ.get("NOTEBOOK_ARGS", ""): if "NOTEBOOK_ARGS" in os.environ: command += shlex.split(os.environ["NOTEBOOK_ARGS"]) +# Pass any other args we have been passed through +command += sys.argv[1:] + # Execute the command! os.execvp(command[0], command) From 37d5b15d3e525be2a664effa829650627023a828 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 17 Oct 2023 16:13:43 +0530 Subject: [PATCH 14/29] Put .py suffix on the start-* scripts --- README.md | 2 +- docs/using/common.md | 18 +++++++++--------- docs/using/recipes.md | 6 +++--- docs/using/running.md | 2 +- docs/using/selecting.md | 4 ++-- .../notebook/letsencrypt-notebook.yml | 2 +- examples/make-deploy/Makefile | 2 +- examples/openshift/templates.json | 2 +- examples/source-to-image/README.md | 2 +- examples/source-to-image/run | 2 +- examples/source-to-image/templates.json | 2 +- images/base-notebook/Dockerfile | 8 ++++---- .../{start-notebook => start-notebook.py} | 7 +++---- .../{start-singleuser => start-singleuser.py} | 0 tests/base-notebook/test_container_options.py | 4 ++-- tests/base-notebook/test_healthcheck.py | 18 +++++++++--------- tests/base-notebook/test_start_container.py | 6 +++--- tests/pluto_check.py | 2 +- 18 files changed, 44 insertions(+), 45 deletions(-) rename images/base-notebook/{start-notebook => start-notebook.py} (79%) rename images/base-notebook/{start-singleuser => start-singleuser.py} (100%) diff --git a/README.md b/README.md index cee2b5a981..49934d917a 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ system when the container exits, but any changes made to the `~/work` directory By default, [jupyter's root_dir](https://jupyter-server.readthedocs.io/en/latest/other/full-config.html) is `/home/jovyan`. So, new notebooks will be saved there, unless you change the directory in the file browser. -To change the default directory, you will need to specify `ServerApp.root_dir` by adding this line to previous command: `start-notebook --ServerApp.root_dir=/home/jovyan/work`. +To change the default directory, you will need to specify `ServerApp.root_dir` by adding this line to previous command: `start-notebook.py --ServerApp.root_dir=/home/jovyan/work`. ``` ## Contributing diff --git a/docs/using/common.md b/docs/using/common.md index 4172c88629..e2fee1f503 100644 --- a/docs/using/common.md +++ b/docs/using/common.md @@ -1,14 +1,14 @@ # Common Features Except for `jupyter/docker-stacks-foundation`, a container launched from any Jupyter Docker Stacks image runs a Jupyter Server with the JupyterLab frontend. -The container does so by executing a `start-notebook` script. +The container does so by executing a `start-notebook.py` script. This script configures the internal container environment and then runs `jupyter lab`, passing any command-line arguments received. This page describes the options supported by the startup script and how to bypass it to run alternative commands. ## Jupyter Server Options -You can pass [Jupyter Server options](https://jupyter-server.readthedocs.io/en/latest/operators/public-server.html) to the `start-notebook` script when launching the container. +You can pass [Jupyter Server options](https://jupyter-server.readthedocs.io/en/latest/operators/public-server.html) to the `start-notebook.py` script when launching the container. 1. For example, to secure the Jupyter Server with a [custom password](https://jupyter-server.readthedocs.io/en/latest/operators/public-server.html#preparing-a-hashed-password) hashed using `jupyter_server.auth.passwd()` instead of the default token, @@ -16,19 +16,19 @@ You can pass [Jupyter Server options](https://jupyter-server.readthedocs.io/en/l ```bash docker run -it --rm -p 8888:8888 jupyter/base-notebook \ - start-notebook --PasswordIdentityProvider.hashed_password='argon2:$argon2id$v=19$m=10240,t=10,p=8$JdAN3fe9J45NvK/EPuGCvA$O/tbxglbwRpOFuBNTYrymAEH6370Q2z+eS1eF4GM6Do' + start-notebook.py --PasswordIdentityProvider.hashed_password='argon2:$argon2id$v=19$m=10240,t=10,p=8$JdAN3fe9J45NvK/EPuGCvA$O/tbxglbwRpOFuBNTYrymAEH6370Q2z+eS1eF4GM6Do' ``` 2. To set the [base URL](https://jupyter-server.readthedocs.io/en/latest/operators/public-server.html#running-the-notebook-with-a-customized-url-prefix) of the Jupyter Server, you can run the following: ```bash docker run -it --rm -p 8888:8888 jupyter/base-notebook \ - start-notebook --ServerApp.base_url=/customized/url/prefix/ + start-notebook.py --ServerApp.base_url=/customized/url/prefix/ ``` ## Docker Options -You may instruct the `start-notebook` script to customize the container environment before launching the Server. +You may instruct the `start-notebook.py` script to customize the container environment before launching the Server. You do so by passing arguments to the `docker run` command. ### User-related configurations @@ -104,7 +104,7 @@ You do so by passing arguments to the `docker run` command. You do **not** need this option to allow the user to `conda` or `pip` install additional packages. This option is helpful for cases when you wish to give `${NB_USER}` the ability to install OS packages with `apt` or modify other root-owned files in the container. You **must** run the container with `--user root` for this option to take effect. - (The `start-notebook` script will `su ${NB_USER}` after adding `${NB_USER}` to sudoers.) + (The `start-notebook.py` script will `su ${NB_USER}` after adding `${NB_USER}` to sudoers.) **You should only enable `sudo` if you trust the user or if the container runs on an isolated host.** ### Additional runtime configurations @@ -147,7 +147,7 @@ For example, to mount a host folder containing a `notebook.key` and `notebook.cr docker run -it --rm -p 8888:8888 \ -v /some/host/folder:/etc/ssl/notebook \ jupyter/base-notebook \ - start-notebook \ + start-notebook.py \ --ServerApp.keyfile=/etc/ssl/notebook/notebook.key \ --ServerApp.certfile=/etc/ssl/notebook/notebook.crt ``` @@ -159,7 +159,7 @@ For example: docker run -it --rm -p 8888:8888 \ -v /some/host/folder/notebook.pem:/etc/ssl/notebook.pem \ jupyter/base-notebook \ - start-notebook \ + start-notebook.py \ --ServerApp.certfile=/etc/ssl/notebook.pem ``` @@ -220,7 +220,7 @@ docker run -it --rm \ ### `start.sh` -The `start-notebook` script inherits most of its option handling capability from a more generic `start.sh` script. +The `start-notebook.py` script inherits most of its option handling capability from a more generic `start.sh` script. The `start.sh` script supports all the features described above but allows you to specify an arbitrary command to execute. For example, to run the text-based `ipython` console in a container, do the following: diff --git a/docs/using/recipes.md b/docs/using/recipes.md index 2a457f86d1..196cd425be 100644 --- a/docs/using/recipes.md +++ b/docs/using/recipes.md @@ -375,14 +375,14 @@ Credit: [britishbadger](https://github.com/britishbadger) from [docker-stacks/is The default security is very good. There are use cases, encouraged by containers, where the jupyter container and the system it runs within lie inside the security boundary. It is convenient to launch the server without a password or token in these use cases. -In this case, you should use the `start-notebook` script to launch the server with no token: +In this case, you should use the `start-notebook.py` script to launch the server with no token: For JupyterLab: ```bash docker run -it --rm \ jupyter/base-notebook \ - start-notebook --IdentityProvider.token='' + start-notebook.py --IdentityProvider.token='' ``` For Jupyter Notebook: @@ -391,7 +391,7 @@ For Jupyter Notebook: docker run -it --rm \ -e DOCKER_STACKS_JUPYTER_CMD=notebook \ jupyter/base-notebook \ - start-notebook --IdentityProvider.token='' + start-notebook.py --IdentityProvider.token='' ``` ## Enable nbclassic-extension spellchecker for markdown (or any other nbclassic-extension) diff --git a/docs/using/running.md b/docs/using/running.md index ad70c16f2b..b888c04309 100644 --- a/docs/using/running.md +++ b/docs/using/running.md @@ -69,7 +69,7 @@ Any other changes made in the container will be lost. By default, [jupyter's root_dir](https://jupyter-server.readthedocs.io/en/latest/other/full-config.html) is `/home/jovyan`. So, new notebooks will be saved there, unless you change the directory in the file browser. -To change the default directory, you will need to specify `ServerApp.root_dir` by adding this line to previous command: `start-notebook --ServerApp.root_dir=/home/jovyan/work`. +To change the default directory, you will need to specify `ServerApp.root_dir` by adding this line to previous command: `start-notebook.py --ServerApp.root_dir=/home/jovyan/work`. ``` ### Example 3 diff --git a/docs/using/selecting.md b/docs/using/selecting.md index 968efa61e0..d81ad1bbbc 100644 --- a/docs/using/selecting.md +++ b/docs/using/selecting.md @@ -56,8 +56,8 @@ It contains: - Everything in `jupyter/docker-stacks-foundation` - Minimally functional Server (e.g., no LaTeX support for saving notebooks as PDFs) - `notebook`, `jupyterhub` and `jupyterlab` packages -- A `start-notebook` script as the default command -- A `start-singleuser` script useful for launching containers in JupyterHub +- A `start-notebook.py` script as the default command +- A `start-singleuser.py` script useful for launching containers in JupyterHub - Options for a self-signed HTTPS certificate ### jupyter/minimal-notebook diff --git a/examples/docker-compose/notebook/letsencrypt-notebook.yml b/examples/docker-compose/notebook/letsencrypt-notebook.yml index af52f16261..06bab31966 100644 --- a/examples/docker-compose/notebook/letsencrypt-notebook.yml +++ b/examples/docker-compose/notebook/letsencrypt-notebook.yml @@ -18,7 +18,7 @@ services: USE_HTTPS: "yes" PASSWORD: ${PASSWORD} command: > - start-notebook + start-notebook.py --ServerApp.certfile=/etc/letsencrypt/fullchain.pem --ServerApp.keyfile=/etc/letsencrypt/privkey.pem diff --git a/examples/make-deploy/Makefile b/examples/make-deploy/Makefile index 1c926fb678..aa62dd0bbf 100644 --- a/examples/make-deploy/Makefile +++ b/examples/make-deploy/Makefile @@ -13,7 +13,7 @@ define RUN_NOTEBOOK --name $(NAME) \ -v $(WORK_VOLUME):/home/jovyan/work \ $(DOCKER_ARGS) \ - $(IMAGE) bash -c "$(PRE_CMD) chown jovyan /home/jovyan/work && start-notebook $(ARGS)" > /dev/null + $(IMAGE) bash -c "$(PRE_CMD) chown jovyan /home/jovyan/work && start-notebook.py $(ARGS)" > /dev/null @echo "DONE: Notebook '$(NAME)' listening on $$(docker-machine ip $$(docker-machine active)):$(PORT)" endef diff --git a/examples/openshift/templates.json b/examples/openshift/templates.json index adfb18c2af..e12036ecb8 100644 --- a/examples/openshift/templates.json +++ b/examples/openshift/templates.json @@ -80,7 +80,7 @@ "name": "jupyter-notebook", "image": "${NOTEBOOK_IMAGE}", "command": [ - "start-notebook", + "start-notebook.py", "--config=/etc/jupyter/openshift/jupyter_server_config.py", "--no-browser", "--ip=0.0.0.0" diff --git a/examples/source-to-image/README.md b/examples/source-to-image/README.md index 6513dbfa94..8639c10999 100644 --- a/examples/source-to-image/README.md +++ b/examples/source-to-image/README.md @@ -117,7 +117,7 @@ with the extra system packages, and then use that image with the S2I build to co The `run` script in this directory is very simple and just runs the notebook application. ```bash -exec start-notebook "$@" +exec start-notebook.py "$@" ``` ## Integration with OpenShift diff --git a/examples/source-to-image/run b/examples/source-to-image/run index b2a67c0f02..556efdda9d 100755 --- a/examples/source-to-image/run +++ b/examples/source-to-image/run @@ -2,4 +2,4 @@ # Start up the notebook instance. -exec start-notebook "$@" +exec start-notebook.py "$@" diff --git a/examples/source-to-image/templates.json b/examples/source-to-image/templates.json index b069c88a86..8daa0823d0 100644 --- a/examples/source-to-image/templates.json +++ b/examples/source-to-image/templates.json @@ -274,7 +274,7 @@ "name": "jupyter-notebook", "image": "${APPLICATION_NAME}:latest", "command": [ - "start-notebook", + "start-notebook.py", "--config=/etc/jupyter/openshift/jupyter_server_config.py", "--no-browser", "--ip=0.0.0.0" diff --git a/images/base-notebook/Dockerfile b/images/base-notebook/Dockerfile index 0c6d429495..fd7a265f4c 100644 --- a/images/base-notebook/Dockerfile +++ b/images/base-notebook/Dockerfile @@ -52,10 +52,10 @@ ENV JUPYTER_PORT=8888 EXPOSE $JUPYTER_PORT # Configure container startup -CMD ["start-notebook"] +CMD ["start-notebook.py"] # Copy local files as late as possible to avoid cache busting -COPY start-notebook start-singleuser /usr/local/bin/ +COPY start-notebook.py start-singleuser.py /usr/local/bin/ COPY jupyter_server_config.py docker_healthcheck.py /etc/jupyter/ # Fix permissions on /etc/jupyter as root @@ -63,8 +63,8 @@ USER root RUN fix-permissions /etc/jupyter/ # Symlink scripts to their older .sh variants for backwards compatibility -RUN ln -s /usr/local/bin/start-notebook /usr/local/bin/start-notebook.sh && \ - ln -s /usr/local/bin/start-jupyterhub /usr/local/bin/start-jupyterhub.sh +RUN ln -s /usr/local/bin/start-notebook.py /usr/local/bin/start-notebook.sh && \ + ln -s /usr/local/bin/start-singleuser.py /usr/local/bin/start-singleuser.sh # HEALTHCHECK documentation: https://docs.docker.com/engine/reference/builder/#healthcheck # This healtcheck works well for `lab`, `notebook`, `nbclassic`, `server` and `retro` jupyter commands diff --git a/images/base-notebook/start-notebook b/images/base-notebook/start-notebook.py similarity index 79% rename from images/base-notebook/start-notebook rename to images/base-notebook/start-notebook.py index 6ec8c3ca96..db1efc246c 100755 --- a/images/base-notebook/start-notebook +++ b/images/base-notebook/start-notebook.py @@ -5,13 +5,12 @@ import shlex import sys -# If we are in a JupyterHub, we pass on to `start-singleuser` instead so it does the right thing +# If we are in a JupyterHub, we pass on to `start-singleuser.py` instead so it does the right thing if "JUPYTERHUB_API_TOKEN" in os.environ: print( - "WARNING: using start-singleuser instead of start-notebook to start a server associated with JupyterHub.", - file=sys.stderr, + "WARNING: using start-singleuser.py instead of start-notebook.py to start a server associated with JupyterHub." ) - command = ["/usr/local/bin/start-singleuser"] + sys.argv[1:] + command = ["/usr/local/bin/start-singleuser.py"] + sys.argv[1:] os.execvp(command[0], command) diff --git a/images/base-notebook/start-singleuser b/images/base-notebook/start-singleuser.py similarity index 100% rename from images/base-notebook/start-singleuser rename to images/base-notebook/start-singleuser.py diff --git a/tests/base-notebook/test_container_options.py b/tests/base-notebook/test_container_options.py index d9fd206796..1ea501d85c 100644 --- a/tests/base-notebook/test_container_options.py +++ b/tests/base-notebook/test_container_options.py @@ -15,7 +15,7 @@ def test_cli_args(container: TrackedContainer, http_client: requests.Session) -> """Image should respect command line args (e.g., disabling token security)""" host_port = find_free_port() running_container = container.run_detached( - command=["start-notebook", "--IdentityProvider.token=''"], + command=["start-notebook.py", "--IdentityProvider.token=''"], ports={"8888/tcp": host_port}, ) resp = http_client.get(f"http://localhost:{host_port}") @@ -102,7 +102,7 @@ def test_custom_internal_port( host_port = find_free_port() internal_port = env.get("JUPYTER_PORT", 8888) running_container = container.run_detached( - command=["start-notebook", "--IdentityProvider.token=''"], + command=["start-notebook.py", "--IdentityProvider.token=''"], environment=env, ports={internal_port: host_port}, ) diff --git a/tests/base-notebook/test_healthcheck.py b/tests/base-notebook/test_healthcheck.py index 40af5203e8..0e54d30038 100644 --- a/tests/base-notebook/test_healthcheck.py +++ b/tests/base-notebook/test_healthcheck.py @@ -22,23 +22,23 @@ (["RESTARTABLE=yes"], None, None), (["JUPYTER_PORT=8171"], None, None), (["JUPYTER_PORT=8117", "DOCKER_STACKS_JUPYTER_CMD=notebook"], None, None), - (None, ["start-notebook", "--ServerApp.base_url=/test"], None), - (None, ["start-notebook", "--ServerApp.base_url=/test/"], None), - (["GEN_CERT=1"], ["start-notebook", "--ServerApp.base_url=/test"], None), + (None, ["start-notebook.py", "--ServerApp.base_url=/test"], None), + (None, ["start-notebook.py", "--ServerApp.base_url=/test/"], None), + (["GEN_CERT=1"], ["start-notebook.py", "--ServerApp.base_url=/test"], None), ( ["GEN_CERT=1", "JUPYTER_PORT=7891"], - ["start-notebook", "--ServerApp.base_url=/test"], + ["start-notebook.py", "--ServerApp.base_url=/test"], None, ), (["NB_USER=testuser", "CHOWN_HOME=1"], None, "root"), ( ["NB_USER=testuser", "CHOWN_HOME=1"], - ["start-notebook", "--ServerApp.base_url=/test"], + ["start-notebook.py", "--ServerApp.base_url=/test"], "root", ), ( ["NB_USER=testuser", "CHOWN_HOME=1", "JUPYTER_PORT=8123"], - ["start-notebook", "--ServerApp.base_url=/test"], + ["start-notebook.py", "--ServerApp.base_url=/test"], "root", ), ], @@ -85,7 +85,7 @@ def test_health( "HTTPS_PROXY=host.docker.internal", "HTTP_PROXY=host.docker.internal", ], - ["start-notebook", "--ServerApp.base_url=/test"], + ["start-notebook.py", "--ServerApp.base_url=/test"], "root", ), ], @@ -122,12 +122,12 @@ def test_health_proxy( (["NB_USER=testuser", "CHOWN_HOME=1"], None, None), ( ["NB_USER=testuser", "CHOWN_HOME=1"], - ["start-notebook", "--ServerApp.base_url=/test"], + ["start-notebook.py", "--ServerApp.base_url=/test"], None, ), ( ["NB_USER=testuser", "CHOWN_HOME=1", "JUPYTER_PORT=8123"], - ["start-notebook", "--ServerApp.base_url=/test"], + ["start-notebook.py", "--ServerApp.base_url=/test"], None, ), ], diff --git a/tests/base-notebook/test_start_container.py b/tests/base-notebook/test_start_container.py index 6ca781775f..830b36c7e7 100644 --- a/tests/base-notebook/test_start_container.py +++ b/tests/base-notebook/test_start_container.py @@ -25,7 +25,7 @@ ["JUPYTERHUB_API_TOKEN=my_token"], "jupyterhub-singleuser", False, - ["WARNING: using start-singleuser"], + ["WARNING: using start-singleuser.py"], ), ], ) @@ -37,9 +37,9 @@ def test_start_notebook( expected_start: bool, expected_warnings: list[str], ) -> None: - """Test the notebook start-notebook script""" + """Test the notebook start-notebook.py script""" LOGGER.info( - f"Test that the start-notebook launches the {expected_command} server from the env {env} ..." + f"Test that the start-notebook.py launches the {expected_command} server from the env {env} ..." ) host_port = find_free_port() running_container = container.run_detached( diff --git a/tests/pluto_check.py b/tests/pluto_check.py index 7b7705adf3..ebb558b4be 100644 --- a/tests/pluto_check.py +++ b/tests/pluto_check.py @@ -18,7 +18,7 @@ def check_pluto_proxy( token = secrets.token_hex() container.run_detached( command=[ - "start-notebook", + "start-notebook.py", f"--IdentityProvider.token={token}", ], ports={"8888/tcp": host_port}, From 81c67ef7caef54776b2a3dc43e3ff7eb0c2f894e Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 17 Oct 2023 16:15:40 +0530 Subject: [PATCH 15/29] Add .sh shims for the start-* scripts --- images/base-notebook/Dockerfile | 6 +----- images/base-notebook/start-notebook.sh | 5 +++++ images/base-notebook/start-singleuser.sh | 5 +++++ 3 files changed, 11 insertions(+), 5 deletions(-) create mode 100755 images/base-notebook/start-notebook.sh create mode 100755 images/base-notebook/start-singleuser.sh diff --git a/images/base-notebook/Dockerfile b/images/base-notebook/Dockerfile index fd7a265f4c..03c6eed9d9 100644 --- a/images/base-notebook/Dockerfile +++ b/images/base-notebook/Dockerfile @@ -55,17 +55,13 @@ EXPOSE $JUPYTER_PORT CMD ["start-notebook.py"] # Copy local files as late as possible to avoid cache busting -COPY start-notebook.py start-singleuser.py /usr/local/bin/ +COPY start-notebook.py start-notebook.sh start-singleuser.py start-singleuser.sh /usr/local/bin/ COPY jupyter_server_config.py docker_healthcheck.py /etc/jupyter/ # Fix permissions on /etc/jupyter as root USER root RUN fix-permissions /etc/jupyter/ -# Symlink scripts to their older .sh variants for backwards compatibility -RUN ln -s /usr/local/bin/start-notebook.py /usr/local/bin/start-notebook.sh && \ - ln -s /usr/local/bin/start-singleuser.py /usr/local/bin/start-singleuser.sh - # HEALTHCHECK documentation: https://docs.docker.com/engine/reference/builder/#healthcheck # This healtcheck works well for `lab`, `notebook`, `nbclassic`, `server` and `retro` jupyter commands # https://github.com/jupyter/docker-stacks/issues/915#issuecomment-1068528799 diff --git a/images/base-notebook/start-notebook.sh b/images/base-notebook/start-notebook.sh new file mode 100755 index 0000000000..c47ebba334 --- /dev/null +++ b/images/base-notebook/start-notebook.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# Shim to emit warning and call start-notebook.py +echo "WARNING: Use start-notebook.py instead" + +exec /usr/local/bin/start-notebook.py "$@" diff --git a/images/base-notebook/start-singleuser.sh b/images/base-notebook/start-singleuser.sh new file mode 100755 index 0000000000..ecf0e068ae --- /dev/null +++ b/images/base-notebook/start-singleuser.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# Shim to emit warning and call start-singleuser.py +echo "WARNING: Use start-singleuser.py instead" + +exec /usr/local/bin/start-singleuser.py "$@" From 219cf3633810a80b341a435e50c9304277e02beb Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 17 Oct 2023 16:30:30 +0530 Subject: [PATCH 16/29] Document start-notebook.sh and start-singleuser.sh --- docs/using/selecting.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/using/selecting.md b/docs/using/selecting.md index d81ad1bbbc..f77d9abb87 100644 --- a/docs/using/selecting.md +++ b/docs/using/selecting.md @@ -60,6 +60,13 @@ It contains: - A `start-singleuser.py` script useful for launching containers in JupyterHub - Options for a self-signed HTTPS certificate +```{warning} +`jupyter/base-notebook` also contains `start-notebook.sh` and `start-singleuser.sh` files to maintain backwards +compatibility. External config that explicitly refers to those files should instead update to refer to +`start-notebook.py` and `start-singleuser.py` instead. The shim `.sh` files will be removed at some future +date. +``` + ### jupyter/minimal-notebook [Source on GitHub](https://github.com/jupyter/docker-stacks/tree/main/images/minimal-notebook) | From d6519ac8312c35f30aa9cdca18dc97607e1300d3 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 17 Oct 2023 16:30:46 +0530 Subject: [PATCH 17/29] Partially test start-notebook.sh --- tests/base-notebook/test_healthcheck.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/base-notebook/test_healthcheck.py b/tests/base-notebook/test_healthcheck.py index 0e54d30038..50d83c27ad 100644 --- a/tests/base-notebook/test_healthcheck.py +++ b/tests/base-notebook/test_healthcheck.py @@ -22,6 +22,7 @@ (["RESTARTABLE=yes"], None, None), (["JUPYTER_PORT=8171"], None, None), (["JUPYTER_PORT=8117", "DOCKER_STACKS_JUPYTER_CMD=notebook"], None, None), + (None, ["start-notebook.sh"], None), (None, ["start-notebook.py", "--ServerApp.base_url=/test"], None), (None, ["start-notebook.py", "--ServerApp.base_url=/test/"], None), (["GEN_CERT=1"], ["start-notebook.py", "--ServerApp.base_url=/test"], None), From 6b96076af1e33c4d92af94edd5e15f6b4cfd9296 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 17 Oct 2023 17:08:06 +0530 Subject: [PATCH 18/29] Convert run-hooks to python - Primarily, implement the `source` method to emulate behavior of source command in bash - This won't actually be effective until start.sh is rewritten as well --- images/docker-stacks-foundation/Dockerfile | 2 +- images/docker-stacks-foundation/run-hooks.py | 112 +++++++++++++++++++ images/docker-stacks-foundation/run-hooks.sh | 43 +------ 3 files changed, 114 insertions(+), 43 deletions(-) create mode 100755 images/docker-stacks-foundation/run-hooks.py diff --git a/images/docker-stacks-foundation/Dockerfile b/images/docker-stacks-foundation/Dockerfile index 5fbcd86c82..b395b72413 100644 --- a/images/docker-stacks-foundation/Dockerfile +++ b/images/docker-stacks-foundation/Dockerfile @@ -127,7 +127,7 @@ ENTRYPOINT ["tini", "-g", "--"] CMD ["start.sh"] # Copy local files as late as possible to avoid cache busting -COPY run-hooks.sh start.sh /usr/local/bin/ +COPY run-hooks.py run-hooks.sh start.sh /usr/local/bin/ USER root diff --git a/images/docker-stacks-foundation/run-hooks.py b/images/docker-stacks-foundation/run-hooks.py new file mode 100755 index 0000000000..588d205dce --- /dev/null +++ b/images/docker-stacks-foundation/run-hooks.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + +# The run-hooks.sh script looks for *.sh scripts to source +# and executable files to run within a passed directory +import os +from textwrap import dedent +import json +import tempfile +import sys +import subprocess +from pathlib import PosixPath + + +def source(path: PosixPath): + """ + Emulate the bash `source` command accurately + + When used in bash, `source` executes the passed file in the current 'context' + of the script from where it is called. This primarily deals with how + bash (and thus environment variables) are modified. + + 1. Any bash variables (particularly any set via `export`) are passed on to the + sourced script as their values are at the point source is called + 2. The sourced script can itself use `export` to affect the bash variables of the + parent script that called it. + + (2) is the primary difference between `source` and just calling a shell script, + and makes it possible for a set of scripts running in sequence to share data by + passing bash variables across with `export`. + + Given bash variables are environment variables, we will simply look for all modified + environment variables in the script we have sourced, and update the calling python + script's environment variables to match. + + Args: + path (PosixPath): Valid bash script to source + """ + # We start a bash process and have it `source` the script we are given. Then, we + # use python (for convenience) to dump the environment variables from the bash process into + # json (we could use `env` but then handling multiline variable values becomes a nightmare). + # The json is written to a temporary file we create. We read this json, and update our python + # process' environment variable with whatever we get back from bash. + with tempfile.NamedTemporaryFile() as bash_file, tempfile.NamedTemporaryFile() as py_file, tempfile.NamedTemporaryFile() as env_vars_file: + py_file.write( + dedent( + f""" + import os + import json + with(open("{env_vars_file.name}", "w")) as f: + json.dump(dict(os.environ), f) + """ + ).encode() + ) + py_file.flush() + + bash_file.write( + dedent( + f""" + #!/bin/bash + source {path} + python {py_file.name} + """ + ).encode() + ) + bash_file.flush() + + run = subprocess.run(["/bin/bash", bash_file.name]) + + if run.returncode != 0: + print( + f"{path} has failed with return code {run.returncode}, continuing execution" + ) + return + + env_vars = json.load(env_vars_file) + os.environ.update(env_vars) + + +if len(sys.argv) != 2: + print("Should pass exactly one directory") + sys.exit(1) + +hooks_directory = PosixPath(sys.argv[1]) + +if not hooks_directory.exists(): + print(f"Directory {hooks_directory} does not exist") + +if not hooks_directory.is_dir(): + print(f"{hooks_directory} is not a directory") + +print(f"Running hooks in: {hooks_directory} as {os.getuid()} gid: {os.getgid()}") + +for f in hooks_directory.iterdir(): + if f.suffix == ".sh": + print(f"Sourcing shell script: {f}") + source(f) + elif os.access(f, os.X_OK): + print(f"Running executable: {f}") + run = subprocess.run([str(f)]) + if run.returncode != 0: + print( + f"{f} has failed with return code {run.returncode}, continuing execution" + ) + else: + print(f"Ignoring non-executable file {f}") + + +print(f"Done running hooks in: {hooks_directory}") + +print(os.environ['HELLO']) \ No newline at end of file diff --git a/images/docker-stacks-foundation/run-hooks.sh b/images/docker-stacks-foundation/run-hooks.sh index d5dc28eae9..bf327cdc45 100755 --- a/images/docker-stacks-foundation/run-hooks.sh +++ b/images/docker-stacks-foundation/run-hooks.sh @@ -2,45 +2,4 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. -# The run-hooks.sh script looks for *.sh scripts to source -# and executable files to run within a passed directory - -if [ "$#" -ne 1 ]; then - echo "Should pass exactly one directory" - return 1 -fi - -if [[ ! -d "${1}" ]] ; then - echo "Directory ${1} doesn't exist or is not a directory" - return 1 -fi - -echo "Running hooks in: ${1} as uid: $(id -u) gid: $(id -g)" -for f in "${1}/"*; do - # Hadling a case when the directory is empty - [ -e "${f}" ] || continue - case "${f}" in - *.sh) - echo "Sourcing shell script: ${f}" - # shellcheck disable=SC1090 - source "${f}" - # shellcheck disable=SC2181 - if [ $? -ne 0 ] ; then - echo "${f} has failed, continuing execution" - fi - ;; - *) - if [ -x "${f}" ] ; then - echo "Running executable: ${f}" - "${f}" - # shellcheck disable=SC2181 - if [ $? -ne 0 ] ; then - echo "${f} has failed, continuing execution" - fi - else - echo "Ignoring non-executable: ${f}" - fi - ;; - esac -done -echo "Done running hooks in: ${1}" +exec /usr/local/bin/run-hooks.py "$@" \ No newline at end of file From 73727f369701a9fdb491ba4f1387bc8d6d138c20 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 17 Oct 2023 17:44:03 +0530 Subject: [PATCH 19/29] Make more hook tests pass --- images/docker-stacks-foundation/run-hooks.py | 6 +++--- tests/docker-stacks-foundation/test_run_hooks.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/images/docker-stacks-foundation/run-hooks.py b/images/docker-stacks-foundation/run-hooks.py index 588d205dce..2b3901c414 100755 --- a/images/docker-stacks-foundation/run-hooks.py +++ b/images/docker-stacks-foundation/run-hooks.py @@ -86,9 +86,11 @@ def source(path: PosixPath): if not hooks_directory.exists(): print(f"Directory {hooks_directory} does not exist") + sys.exit(1) if not hooks_directory.is_dir(): print(f"{hooks_directory} is not a directory") + sys.exit(1) print(f"Running hooks in: {hooks_directory} as {os.getuid()} gid: {os.getgid()}") @@ -104,9 +106,7 @@ def source(path: PosixPath): f"{f} has failed with return code {run.returncode}, continuing execution" ) else: - print(f"Ignoring non-executable file {f}") + print(f"Ignoring non-executable: {f}") print(f"Done running hooks in: {hooks_directory}") - -print(os.environ['HELLO']) \ No newline at end of file diff --git a/tests/docker-stacks-foundation/test_run_hooks.py b/tests/docker-stacks-foundation/test_run_hooks.py index 0774f5f59d..c62cfc9622 100644 --- a/tests/docker-stacks-foundation/test_run_hooks.py +++ b/tests/docker-stacks-foundation/test_run_hooks.py @@ -44,7 +44,7 @@ def test_run_hooks_missing_dir(container: TrackedContainer) -> None: "source /usr/local/bin/run-hooks.sh /tmp/missing-dir/", ], ) - assert "Directory /tmp/missing-dir/ doesn't exist or is not a directory" in logs + assert "Directory /tmp/missing-dir does not exist" in logs def test_run_hooks_dir_is_file(container: TrackedContainer) -> None: @@ -58,7 +58,7 @@ def test_run_hooks_dir_is_file(container: TrackedContainer) -> None: "touch /tmp/some-file && source /usr/local/bin/run-hooks.sh /tmp/some-file", ], ) - assert "Directory /tmp/some-file doesn't exist or is not a directory" in logs + assert "/tmp/some-file is not a directory" in logs def test_run_hooks_empty_dir(container: TrackedContainer) -> None: @@ -91,7 +91,7 @@ def test_run_hooks_with_files(container: TrackedContainer) -> None: command=["bash", "-c", command], ) assert "Executable python file was successfully" in logs - assert "Ignoring non-executable: /home/jovyan/data-copy//non_executable.py" in logs + assert "Ignoring non-executable: /home/jovyan/data-copy/non_executable.py" in logs assert "SOME_VAR is 123" in logs From b011204b50dc666923790f2353690d8b78b9474a Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 17 Oct 2023 17:44:16 +0530 Subject: [PATCH 20/29] Rename some run-hooks.sh to run-hooks.py --- docs/using/common.md | 4 ++-- docs/using/selecting.md | 2 +- images/docker-stacks-foundation/run-hooks.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/using/common.md b/docs/using/common.md index e2fee1f503..d0eee08c8a 100644 --- a/docs/using/common.md +++ b/docs/using/common.md @@ -86,7 +86,7 @@ You do so by passing arguments to the `docker run` command. ```{note} `NB_UMASK` when set only applies to the Jupyter process itself - - you cannot use it to set a `umask` for additional files created during `run-hooks.sh`. + you cannot use it to set a `umask` for additional files created during `run-hooks.py`. For example, via `pip` or `conda`. If you need to set a `umask` for these, you **must** set the `umask` value for each command. ``` @@ -135,7 +135,7 @@ or executables (`chmod +x`) to be run to the paths below: - `/usr/local/bin/before-notebook.d/` - handled **after** all the standard options noted above are applied and ran right before the Server launches -See the `run-hooks.sh` script [here](https://github.com/jupyter/docker-stacks/blob/main/images/docker-stacks-foundation/run-hooks.sh) and how it's used in the [`start.sh`](https://github.com/jupyter/docker-stacks/blob/main/images/docker-stacks-foundation/start.sh) +See the `run-hooks.py` script [here](https://github.com/jupyter/docker-stacks/blob/main/images/docker-stacks-foundation/run-hooks.py) and how it's used in the [`start.sh`](https://github.com/jupyter/docker-stacks/blob/main/images/docker-stacks-foundation/start.sh) script for execution details. ## SSL Certificates diff --git a/docs/using/selecting.md b/docs/using/selecting.md index f77d9abb87..369479190a 100644 --- a/docs/using/selecting.md +++ b/docs/using/selecting.md @@ -36,7 +36,7 @@ It contains: with ownership over the `/home/jovyan` and `/opt/conda` paths - `tini` as the container entry point - A `start.sh` script as the default command - useful for running alternative commands in the container as applications are added (e.g. `ipython`, `jupyter kernelgateway`, `jupyter lab`) -- A `run-hooks.sh` script, which can source/run files in a given directory +- A `run-hooks.py` script, which can source/run files in a given directory - Options for a passwordless sudo - Common system libraries like `bzip2`, `ca-certificates`, `locales` - `wget` to download external files diff --git a/images/docker-stacks-foundation/run-hooks.py b/images/docker-stacks-foundation/run-hooks.py index 2b3901c414..e7d973bd94 100755 --- a/images/docker-stacks-foundation/run-hooks.py +++ b/images/docker-stacks-foundation/run-hooks.py @@ -2,7 +2,7 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. -# The run-hooks.sh script looks for *.sh scripts to source +# The run-hooks.py script looks for *.sh scripts to source # and executable files to run within a passed directory import os from textwrap import dedent @@ -109,4 +109,4 @@ def source(path: PosixPath): print(f"Ignoring non-executable: {f}") -print(f"Done running hooks in: {hooks_directory}") +print(f"Done running hooks in: {hooks_directory}") \ No newline at end of file From c002adc7bedca9739c979e3fab9e5224f0671956 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 17 Oct 2023 17:46:03 +0530 Subject: [PATCH 21/29] Run pre-commit --- images/docker-stacks-foundation/run-hooks.py | 11 ++++++----- images/docker-stacks-foundation/run-hooks.sh | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/images/docker-stacks-foundation/run-hooks.py b/images/docker-stacks-foundation/run-hooks.py index e7d973bd94..6d458b96b2 100755 --- a/images/docker-stacks-foundation/run-hooks.py +++ b/images/docker-stacks-foundation/run-hooks.py @@ -2,15 +2,16 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. +import json + # The run-hooks.py script looks for *.sh scripts to source # and executable files to run within a passed directory import os -from textwrap import dedent -import json -import tempfile -import sys import subprocess +import sys +import tempfile from pathlib import PosixPath +from textwrap import dedent def source(path: PosixPath): @@ -109,4 +110,4 @@ def source(path: PosixPath): print(f"Ignoring non-executable: {f}") -print(f"Done running hooks in: {hooks_directory}") \ No newline at end of file +print(f"Done running hooks in: {hooks_directory}") diff --git a/images/docker-stacks-foundation/run-hooks.sh b/images/docker-stacks-foundation/run-hooks.sh index bf327cdc45..8099727408 100755 --- a/images/docker-stacks-foundation/run-hooks.sh +++ b/images/docker-stacks-foundation/run-hooks.sh @@ -2,4 +2,4 @@ # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. -exec /usr/local/bin/run-hooks.py "$@" \ No newline at end of file +exec /usr/local/bin/run-hooks.py "$@" From 39a301c00ef24d75c4149148ff27a4952be415d3 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Tue, 17 Oct 2023 17:55:07 +0530 Subject: [PATCH 22/29] Don't rely on path to find python --- images/docker-stacks-foundation/run-hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/docker-stacks-foundation/run-hooks.py b/images/docker-stacks-foundation/run-hooks.py index 6d458b96b2..2cc93559af 100755 --- a/images/docker-stacks-foundation/run-hooks.py +++ b/images/docker-stacks-foundation/run-hooks.py @@ -61,7 +61,7 @@ def source(path: PosixPath): f""" #!/bin/bash source {path} - python {py_file.name} + {sys.executable} {py_file.name} """ ).encode() ) From 79f3ff94ea8e20580a4ddedf767dc7317c9bebd9 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Sat, 21 Oct 2023 15:34:51 +0530 Subject: [PATCH 23/29] Sort list of files before executing them --- images/docker-stacks-foundation/run-hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/docker-stacks-foundation/run-hooks.py b/images/docker-stacks-foundation/run-hooks.py index 2cc93559af..5eeaac493f 100755 --- a/images/docker-stacks-foundation/run-hooks.py +++ b/images/docker-stacks-foundation/run-hooks.py @@ -95,7 +95,7 @@ def source(path: PosixPath): print(f"Running hooks in: {hooks_directory} as {os.getuid()} gid: {os.getgid()}") -for f in hooks_directory.iterdir(): +for f in sorted(hooks_directory.iterdir()): if f.suffix == ".sh": print(f"Sourcing shell script: {f}") source(f) From 98e4ec67212ed97b060d7aaa5f75954c0dc9079a Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Sat, 21 Oct 2023 15:41:09 +0530 Subject: [PATCH 24/29] If any env vars are *unset* by a script, remove them as well --- images/docker-stacks-foundation/run-hooks.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/images/docker-stacks-foundation/run-hooks.py b/images/docker-stacks-foundation/run-hooks.py index 5eeaac493f..0457d56643 100755 --- a/images/docker-stacks-foundation/run-hooks.py +++ b/images/docker-stacks-foundation/run-hooks.py @@ -75,8 +75,17 @@ def source(path: PosixPath): ) return - env_vars = json.load(env_vars_file) - os.environ.update(env_vars) + # Get env vars of the sourced process after it exits + # This may contain *additional* env vars, or some may be *removed* + child_env_vars = json.load(env_vars_file) + + # Remove any env vars from our environment that were explicitly removed from the child + removed_env_vars = set(os.environ.keys()) - set(child_env_vars.keys()) + for name in removed_env_vars: + del os.environ[name] + + # Update our environment with any *new* or *modified* env vars from the child process + os.environ.update(child_env_vars) if len(sys.argv) != 2: From f91ee8f0ac00c8ebe29fc84e4d181b6eb45422d1 Mon Sep 17 00:00:00 2001 From: Yuvi Panda Date: Sat, 21 Oct 2023 15:44:31 +0530 Subject: [PATCH 25/29] Clarify how files are executed Co-authored-by: Ayaz Salikhov --- docs/using/selecting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/using/selecting.md b/docs/using/selecting.md index b833c5eb6a..f5c4f0bc44 100644 --- a/docs/using/selecting.md +++ b/docs/using/selecting.md @@ -36,7 +36,7 @@ It contains: with ownership over the `/home/jovyan` and `/opt/conda` paths - `tini` as the container entry point - A `start.sh` script as the default command - useful for running alternative commands in the container as applications are added (e.g. `ipython`, `jupyter kernelgateway`, `jupyter lab`) -- A `run-hooks.py` script, which can source/run files in a given directory +- A `run-hooks.py` script, which can source `.sh` files and call executable files in a given directory - Options for a passwordless sudo - Common system libraries like `bzip2`, `ca-certificates`, `locales` - `wget` to download external files From 88b23d0bb3454eb9ec5fe61c71828f0dc007bc8b Mon Sep 17 00:00:00 2001 From: Yuvi Panda Date: Sat, 21 Oct 2023 15:44:54 +0530 Subject: [PATCH 26/29] Fix missing uid: in printed message Co-authored-by: Ayaz Salikhov --- images/docker-stacks-foundation/run-hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/docker-stacks-foundation/run-hooks.py b/images/docker-stacks-foundation/run-hooks.py index 0457d56643..ce4618c250 100755 --- a/images/docker-stacks-foundation/run-hooks.py +++ b/images/docker-stacks-foundation/run-hooks.py @@ -102,7 +102,7 @@ def source(path: PosixPath): print(f"{hooks_directory} is not a directory") sys.exit(1) -print(f"Running hooks in: {hooks_directory} as {os.getuid()} gid: {os.getgid()}") +print(f"Running hooks in: {hooks_directory} as uid: {os.getuid()} gid: {os.getgid()}") for f in sorted(hooks_directory.iterdir()): if f.suffix == ".sh": From 1a22aa395872698c4f3e783bc3d8aae4a58e32d2 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Sat, 21 Oct 2023 15:45:34 +0530 Subject: [PATCH 27/29] Add warning to run-hooks.sh --- images/docker-stacks-foundation/run-hooks.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/images/docker-stacks-foundation/run-hooks.sh b/images/docker-stacks-foundation/run-hooks.sh index 8099727408..4cf2dc2d4d 100755 --- a/images/docker-stacks-foundation/run-hooks.sh +++ b/images/docker-stacks-foundation/run-hooks.sh @@ -1,5 +1,6 @@ #!/bin/bash # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. +echo "WARNING: Use run-hooks.py instead" exec /usr/local/bin/run-hooks.py "$@" From c5bbe1080bf1e578d178a0ce6e5f958fa6b77aeb Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Sat, 4 Nov 2023 11:09:35 +0100 Subject: [PATCH 28/29] Update run-hooks.sh --- images/docker-stacks-foundation/run-hooks.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/docker-stacks-foundation/run-hooks.sh b/images/docker-stacks-foundation/run-hooks.sh index 4cf2dc2d4d..6e760e4907 100755 --- a/images/docker-stacks-foundation/run-hooks.sh +++ b/images/docker-stacks-foundation/run-hooks.sh @@ -1,6 +1,6 @@ #!/bin/bash # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. -echo "WARNING: Use run-hooks.py instead" +# echo "WARNING: Use run-hooks.py instead" exec /usr/local/bin/run-hooks.py "$@" From 0dc09e291216af2ff10b462c717250c04919660d Mon Sep 17 00:00:00 2001 From: Ayaz Salikhov Date: Sat, 4 Nov 2023 11:09:58 +0100 Subject: [PATCH 29/29] Update run-hooks.py --- images/docker-stacks-foundation/run-hooks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/docker-stacks-foundation/run-hooks.py b/images/docker-stacks-foundation/run-hooks.py index ce4618c250..93ff490ae4 100755 --- a/images/docker-stacks-foundation/run-hooks.py +++ b/images/docker-stacks-foundation/run-hooks.py @@ -14,7 +14,7 @@ from textwrap import dedent -def source(path: PosixPath): +def source(path: PosixPath) -> None: """ Emulate the bash `source` command accurately